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.
Files changed (167) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +93 -37
  3. package/package.json +33 -7
  4. package/skills/INVENTORY.md +88 -57
  5. package/skills/README.md +26 -23
  6. package/skills/case-studies/beanstalk-governance/SKILL.md +52 -0
  7. package/skills/case-studies/bzx-flash-loan/SKILL.md +53 -0
  8. package/skills/case-studies/cream-finance/SKILL.md +52 -0
  9. package/skills/case-studies/curve-reentrancy/SKILL.md +52 -0
  10. package/skills/case-studies/dao-hack/SKILL.md +51 -0
  11. package/skills/case-studies/euler-finance/SKILL.md +52 -0
  12. package/skills/case-studies/harvest-finance/SKILL.md +52 -0
  13. package/skills/case-studies/level-finance/SKILL.md +51 -0
  14. package/skills/case-studies/mango-markets/SKILL.md +53 -0
  15. package/skills/case-studies/nomad-bridge/SKILL.md +51 -0
  16. package/skills/case-studies/parity-multisig/SKILL.md +55 -0
  17. package/skills/case-studies/poly-network/SKILL.md +51 -0
  18. package/skills/case-studies/rari-fuse/SKILL.md +51 -0
  19. package/skills/case-studies/ronin-bridge/SKILL.md +52 -0
  20. package/skills/case-studies/wormhole-bridge/SKILL.md +51 -0
  21. package/skills/manifests/smartbugs.json +1 -3
  22. package/skills/manifests/sunweb3sec.json +1 -3
  23. package/skills/vulnerability-patterns/access-control/SKILL.md +14 -0
  24. package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +13 -1
  25. package/skills/vulnerability-patterns/assert-violation/SKILL.md +8 -1
  26. package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +12 -1
  27. package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +2 -1
  28. package/skills/vulnerability-patterns/cross-chain-bridge-vulnerabilities/SKILL.md +217 -0
  29. package/skills/vulnerability-patterns/default-visibility/SKILL.md +13 -1
  30. package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +2 -1
  31. package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
  32. package/skills/vulnerability-patterns/dos-revert/SKILL.md +1 -0
  33. package/skills/vulnerability-patterns/erc4626-exchange-rate-manipulation/SKILL.md +64 -0
  34. package/skills/vulnerability-patterns/fee-on-transfer-tokens/SKILL.md +93 -0
  35. package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +1 -0
  36. package/skills/vulnerability-patterns/floating-pragma/SKILL.md +8 -1
  37. package/skills/vulnerability-patterns/front-running-attacks/SKILL.md +209 -0
  38. package/skills/vulnerability-patterns/gas-optimization-patterns/SKILL.md +203 -0
  39. package/skills/vulnerability-patterns/governance-attacks/SKILL.md +208 -0
  40. package/skills/vulnerability-patterns/hash-collision/SKILL.md +8 -1
  41. package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +12 -1
  42. package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +8 -1
  43. package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +8 -1
  44. package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +12 -1
  45. package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +7 -1
  46. package/skills/vulnerability-patterns/logic-errors/SKILL.md +10 -0
  47. package/skills/vulnerability-patterns/missing-parameter-bounds/SKILL.md +44 -0
  48. package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +17 -1
  49. package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +12 -1
  50. package/skills/vulnerability-patterns/off-by-one/SKILL.md +7 -1
  51. package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +9 -0
  52. package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
  53. package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +1 -0
  54. package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
  55. package/skills/vulnerability-patterns/reentrancy/SKILL.md +9 -0
  56. package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +8 -1
  57. package/skills/vulnerability-patterns/share-accounting-desynchronization/SKILL.md +44 -0
  58. package/skills/vulnerability-patterns/signature-malleability/SKILL.md +2 -1
  59. package/skills/vulnerability-patterns/stateful-parameter-update-drift/SKILL.md +44 -0
  60. package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +12 -1
  61. package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +2 -1
  62. package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +8 -1
  63. package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +8 -1
  64. package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +8 -1
  65. package/skills/vulnerability-patterns/unsafe-erc20-transfers/SKILL.md +132 -0
  66. package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +12 -1
  67. package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +12 -1
  68. package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +11 -1
  69. package/skills/vulnerability-patterns/unused-variables/SKILL.md +8 -1
  70. package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +8 -1
  71. package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +8 -1
  72. package/skills/vulnerability-patterns/weird-tokens/SKILL.md +10 -0
  73. package/skills/vulnerability-patterns/zero-address-misconfiguration/SKILL.md +48 -0
  74. package/src/agents/argus-prompt.ts +24 -7
  75. package/src/agents/pythia-prompt.ts +3 -4
  76. package/src/agents/scribe-prompt.ts +7 -2
  77. package/src/agents/sentinel-prompt.ts +32 -3
  78. package/src/cli/cli-program.ts +29 -26
  79. package/src/cli/commands/check-skills.ts +135 -0
  80. package/src/cli/commands/doctor.ts +48 -26
  81. package/src/cli/commands/init.ts +5 -3
  82. package/src/cli/commands/install.ts +7 -5
  83. package/src/cli/commands/lint-skills.ts +16 -12
  84. package/src/cli/index.ts +5 -5
  85. package/src/cli/types.ts +3 -3
  86. package/src/config/index.ts +1 -1
  87. package/src/config/loader.ts +4 -6
  88. package/src/config/schema.ts +4 -5
  89. package/src/config/types.ts +2 -2
  90. package/src/constants/defaults.ts +2 -0
  91. package/src/create-hooks.ts +145 -34
  92. package/src/create-managers.ts +10 -8
  93. package/src/create-tools.ts +13 -9
  94. package/src/features/background-agent/background-manager.ts +93 -87
  95. package/src/features/background-agent/index.ts +1 -1
  96. package/src/features/context-monitor/context-monitor.ts +3 -3
  97. package/src/features/context-monitor/index.ts +2 -2
  98. package/src/features/error-recovery/session-recovery.ts +2 -4
  99. package/src/features/error-recovery/tool-error-recovery.ts +12 -7
  100. package/src/features/index.ts +5 -5
  101. package/src/features/persistent-state/audit-state-manager.ts +143 -60
  102. package/src/features/persistent-state/global-run-index.ts +38 -0
  103. package/src/features/persistent-state/index.ts +1 -1
  104. package/src/features/persistent-state/run-journal.ts +86 -0
  105. package/src/hooks/config-handler.ts +28 -11
  106. package/src/hooks/context-budget.ts +2 -5
  107. package/src/hooks/event-hook.ts +47 -23
  108. package/src/hooks/hook-system.ts +4 -4
  109. package/src/hooks/index.ts +5 -5
  110. package/src/hooks/knowledge-sync-hook.ts +18 -21
  111. package/src/hooks/recon-context-builder.ts +2 -2
  112. package/src/hooks/safe-create-hook.ts +6 -7
  113. package/src/hooks/tool-tracking-hook.ts +104 -50
  114. package/src/hooks/types.ts +2 -1
  115. package/src/index.ts +23 -36
  116. package/src/knowledge/retry.ts +22 -22
  117. package/src/knowledge/scvd-client.ts +88 -95
  118. package/src/knowledge/scvd-errors.ts +35 -35
  119. package/src/knowledge/scvd-index.ts +78 -80
  120. package/src/knowledge/scvd-sync.ts +106 -101
  121. package/src/managers/index.ts +1 -1
  122. package/src/managers/types.ts +19 -14
  123. package/src/plugin-interface.ts +7 -9
  124. package/src/shared/binary-utils.ts +44 -35
  125. package/src/shared/deep-merge.ts +55 -36
  126. package/src/shared/file-utils.ts +21 -19
  127. package/src/shared/index.ts +11 -5
  128. package/src/shared/jsonc-parser.ts +123 -28
  129. package/src/shared/logger.ts +16 -3
  130. package/src/shared/project-utils.ts +30 -0
  131. package/src/skills/analysis/cluster.ts +414 -0
  132. package/src/skills/analysis/gates.ts +227 -0
  133. package/src/skills/analysis/index.ts +33 -0
  134. package/src/skills/analysis/normalize.ts +217 -0
  135. package/src/skills/analysis/similarity.ts +224 -0
  136. package/src/skills/argus-skill-resolver.ts +17 -6
  137. package/src/skills/skill-schema.ts +11 -10
  138. package/src/solodit-lifecycle.ts +202 -0
  139. package/src/state/audit-state.ts +8 -8
  140. package/src/state/finding-store.ts +68 -55
  141. package/src/state/types.ts +88 -67
  142. package/src/tools/argus-skill-load-tool.ts +12 -7
  143. package/src/tools/contract-analyzer-tool.ts +60 -77
  144. package/src/tools/forge-coverage-tool.ts +226 -0
  145. package/src/tools/forge-fuzz-tool.ts +127 -127
  146. package/src/tools/forge-test-tool.ts +153 -157
  147. package/src/tools/gas-analysis-tool.ts +264 -0
  148. package/src/tools/pattern-checker-tool.ts +185 -190
  149. package/src/tools/pattern-loader.ts +5 -111
  150. package/src/tools/proxy-detection-tool.ts +224 -0
  151. package/src/tools/report-generator-tool.ts +268 -200
  152. package/src/tools/slither-tool.ts +266 -218
  153. package/src/tools/solodit-search-tool.ts +216 -119
  154. package/src/tools/sync-knowledge-tool.ts +7 -11
  155. package/src/utils/audit-artifact-detector.ts +28 -29
  156. package/src/utils/dependency-scanner.ts +37 -37
  157. package/src/utils/project-detector.ts +111 -124
  158. package/src/utils/solidity-parser.ts +103 -74
  159. package/skills/patterns/access-control.yaml +0 -31
  160. package/skills/patterns/erc4626.yaml +0 -29
  161. package/skills/patterns/flash-loan.yaml +0 -20
  162. package/skills/patterns/oracle.yaml +0 -30
  163. package/skills/patterns/proxy.yaml +0 -30
  164. package/skills/patterns/reentrancy.yaml +0 -30
  165. package/skills/patterns/signature.yaml +0 -31
  166. package/src/hooks/event-hook-v2.ts +0 -99
  167. package/src/state/plugin-state.ts +0 -14
@@ -1,37 +1,31 @@
1
- import { tool, type ToolContext } from "@opencode-ai/plugin";
2
- import type { AuditState, Finding, FindingSeverity, ToolExecution } from "../state/types";
1
+ import { type ToolContext, tool } from "@opencode-ai/plugin"
2
+ import type { AuditState, Finding, FindingSeverity } from "../state/types"
3
3
 
4
- type SeverityThreshold = "critical" | "high" | "medium" | "low" | "informational";
4
+ type SeverityThreshold = "critical" | "high" | "medium" | "low" | "informational"
5
5
 
6
6
  type ReportGeneratorArgs = {
7
- project_name: string;
8
- scope: string[];
9
- include_executive_summary?: boolean;
10
- severity_threshold?: SeverityThreshold;
11
- audit_state: string;
12
- };
7
+ project_name: string
8
+ scope: string[]
9
+ include_executive_summary?: boolean
10
+ severity_threshold?: SeverityThreshold
11
+ audit_state: string
12
+ }
13
13
 
14
14
  type FindingsCount = {
15
- critical: number;
16
- high: number;
17
- medium: number;
18
- low: number;
19
- informational: number;
20
- };
15
+ critical: number
16
+ high: number
17
+ medium: number
18
+ low: number
19
+ informational: number
20
+ }
21
21
 
22
22
  export type ReportGenerationResult = {
23
- report: string;
24
- findingsCount: FindingsCount;
25
- filename: string;
26
- };
23
+ report: string
24
+ findingsCount: FindingsCount
25
+ filename: string
26
+ }
27
27
 
28
- const SEVERITY_ORDER: FindingSeverity[] = [
29
- "Critical",
30
- "High",
31
- "Medium",
32
- "Low",
33
- "Informational",
34
- ];
28
+ const SEVERITY_ORDER: FindingSeverity[] = ["Critical", "High", "Medium", "Low", "Informational"]
35
29
 
36
30
  const SEVERITY_PREFIX: Record<FindingSeverity, string> = {
37
31
  Critical: "CRIT",
@@ -39,7 +33,7 @@ const SEVERITY_PREFIX: Record<FindingSeverity, string> = {
39
33
  Medium: "MED",
40
34
  Low: "LOW",
41
35
  Informational: "INFO",
42
- };
36
+ }
43
37
 
44
38
  const THRESHOLD_WEIGHT: Record<SeverityThreshold, number> = {
45
39
  critical: 5,
@@ -47,7 +41,7 @@ const THRESHOLD_WEIGHT: Record<SeverityThreshold, number> = {
47
41
  medium: 3,
48
42
  low: 2,
49
43
  informational: 1,
50
- };
44
+ }
51
45
 
52
46
  const FINDING_WEIGHT: Record<FindingSeverity, number> = {
53
47
  Critical: 5,
@@ -55,7 +49,7 @@ const FINDING_WEIGHT: Record<FindingSeverity, number> = {
55
49
  Medium: 3,
56
50
  Low: 2,
57
51
  Informational: 1,
58
- };
52
+ }
59
53
 
60
54
  function emptyCounts(): FindingsCount {
61
55
  return {
@@ -64,7 +58,7 @@ function emptyCounts(): FindingsCount {
64
58
  medium: 0,
65
59
  low: 0,
66
60
  informational: 0,
67
- };
61
+ }
68
62
  }
69
63
 
70
64
  function emptyAuditState(findings: Finding[] = []): AuditState {
@@ -77,164 +71,247 @@ function emptyAuditState(findings: Finding[] = []): AuditState {
77
71
  currentPhase: "complete",
78
72
  scope: [],
79
73
  startTime: 0,
80
- };
74
+ }
75
+ }
76
+
77
+ function hasMinimumFindingFields(
78
+ f: unknown,
79
+ ): f is { check: string; file: string; lines: [number, number] } {
80
+ if (typeof f !== "object" || f === null) return false
81
+ const obj = f as Record<string, unknown>
82
+ return (
83
+ typeof obj.check === "string" &&
84
+ obj.check.length > 0 &&
85
+ typeof obj.file === "string" &&
86
+ Array.isArray(obj.lines) &&
87
+ obj.lines.length === 2
88
+ )
89
+ }
90
+
91
+ const VALID_SEVERITIES: ReadonlySet<string> = new Set([
92
+ "Critical",
93
+ "High",
94
+ "Medium",
95
+ "Low",
96
+ "Informational",
97
+ ])
98
+ const VALID_SOURCES: ReadonlySet<string> = new Set([
99
+ "slither",
100
+ "manual",
101
+ "pattern",
102
+ "scvd",
103
+ "solodit",
104
+ "fuzz",
105
+ ])
106
+
107
+ function normalizeFinding(f: Record<string, unknown>): Finding {
108
+ const severity =
109
+ typeof f.severity === "string" && VALID_SEVERITIES.has(f.severity)
110
+ ? (f.severity as Finding["severity"])
111
+ : "Informational"
112
+ const confidence =
113
+ typeof f.confidence === "string" && ["High", "Medium", "Low"].includes(f.confidence)
114
+ ? (f.confidence as Finding["confidence"])
115
+ : "Low"
116
+ const source =
117
+ typeof f.source === "string" && VALID_SOURCES.has(f.source)
118
+ ? (f.source as Finding["source"])
119
+ : "manual"
120
+ const description = typeof f.description === "string" ? f.description : (f.check as string)
121
+ const id = typeof f.id === "string" ? f.id : `${f.check}:${f.file}:${(f.lines as number[])[0]}`
122
+ return {
123
+ id,
124
+ check: f.check as string,
125
+ severity,
126
+ confidence,
127
+ description,
128
+ file: f.file as string,
129
+ lines: f.lines as [number, number],
130
+ source,
131
+ remediation: typeof f.remediation === "string" ? f.remediation : undefined,
132
+ exploitReference: typeof f.exploitReference === "string" ? f.exploitReference : undefined,
133
+ }
81
134
  }
82
135
 
83
136
  export function parseAuditState(auditState: string): AuditState {
84
- let parsed: unknown;
137
+ let parsed: unknown
85
138
  try {
86
- parsed = JSON.parse(auditState);
139
+ parsed = JSON.parse(auditState)
87
140
  } catch {
88
- throw new Error("audit_state is not valid JSON — expected an AuditState object or Finding[] array");
141
+ throw new Error(
142
+ "audit_state is not valid JSON — expected an AuditState object or Finding[] array",
143
+ )
89
144
  }
90
145
 
91
146
  if (Array.isArray(parsed)) {
92
- return emptyAuditState(parsed as Finding[]);
147
+ const validFindings = (parsed as unknown[])
148
+ .filter(hasMinimumFindingFields)
149
+ .map((f) => normalizeFinding(f as Record<string, unknown>))
150
+ return emptyAuditState(validFindings)
93
151
  }
94
152
 
95
- if (typeof parsed === "object" && parsed !== null && Array.isArray((parsed as AuditState).findings)) {
96
- const state = parsed as AuditState;
153
+ if (
154
+ typeof parsed === "object" &&
155
+ parsed !== null &&
156
+ Array.isArray((parsed as AuditState).findings)
157
+ ) {
158
+ const state = parsed as AuditState
159
+ const validFindings = state.findings
160
+ .filter(hasMinimumFindingFields)
161
+ .map((f) => normalizeFinding(f as unknown as Record<string, unknown>))
97
162
  return {
98
163
  ...emptyAuditState(),
99
164
  ...state,
100
- };
165
+ findings: validFindings,
166
+ }
101
167
  }
102
168
 
103
- return emptyAuditState();
169
+ return emptyAuditState()
104
170
  }
105
171
 
106
172
  function normalizeTitle(check: string): string {
173
+ if (!check || typeof check !== "string") return "Unknown Check"
107
174
  return check
108
175
  .split(/[-_\s]+/)
109
176
  .filter((part) => part.length > 0)
110
177
  .map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
111
- .join(" ");
178
+ .join(" ")
112
179
  }
113
180
 
114
181
  function formatLocation(finding: Finding): string {
115
- return `${finding.file}:${finding.lines[0]}-${finding.lines[1]}`;
182
+ if (!finding.file || !Array.isArray(finding.lines) || finding.lines.length < 2)
183
+ return "unknown location"
184
+ return `${finding.file}:${finding.lines[0]}-${finding.lines[1]}`
116
185
  }
117
186
 
118
187
  function shouldIncludeFinding(finding: Finding, threshold: SeverityThreshold): boolean {
119
- return FINDING_WEIGHT[finding.severity] >= THRESHOLD_WEIGHT[threshold];
188
+ return FINDING_WEIGHT[finding.severity] >= THRESHOLD_WEIGHT[threshold]
120
189
  }
121
190
 
122
191
  function calculateCounts(findings: Finding[]): FindingsCount {
123
- const counts = emptyCounts();
192
+ const counts = emptyCounts()
124
193
 
125
194
  for (const finding of findings) {
126
- if (finding.severity === "Critical") counts.critical += 1;
127
- if (finding.severity === "High") counts.high += 1;
128
- if (finding.severity === "Medium") counts.medium += 1;
129
- if (finding.severity === "Low") counts.low += 1;
130
- if (finding.severity === "Informational") counts.informational += 1;
195
+ if (finding.severity === "Critical") counts.critical += 1
196
+ if (finding.severity === "High") counts.high += 1
197
+ if (finding.severity === "Medium") counts.medium += 1
198
+ if (finding.severity === "Low") counts.low += 1
199
+ if (finding.severity === "Informational") counts.informational += 1
131
200
  }
132
201
 
133
- return counts;
202
+ return counts
134
203
  }
135
204
 
136
205
  function overallRiskAssessment(counts: FindingsCount): string {
137
- if (counts.critical > 0) return "Critical risk";
138
- if (counts.high > 0) return "High risk";
139
- if (counts.medium > 0) return "Medium risk";
140
- if (counts.low > 0) return "Low risk";
141
- if (counts.informational > 0) return "Informational only";
142
- return "No significant risk identified";
206
+ if (counts.critical > 0) return "Critical risk"
207
+ if (counts.high > 0) return "High risk"
208
+ if (counts.medium > 0) return "Medium risk"
209
+ if (counts.low > 0) return "Low risk"
210
+ if (counts.informational > 0) return "Informational only"
211
+ return "No significant risk identified"
143
212
  }
144
213
 
145
214
  function genericImpact(severity: FindingSeverity): string {
146
215
  if (severity === "Critical") {
147
- return "Could lead to immediate and severe compromise of funds or protocol control.";
216
+ return "Could lead to immediate and severe compromise of funds or protocol control."
148
217
  }
149
218
  if (severity === "High") {
150
- return "Could materially impact protocol security, user funds, or system integrity.";
219
+ return "Could materially impact protocol security, user funds, or system integrity."
151
220
  }
152
221
  if (severity === "Medium") {
153
- return "Could cause operational issues or increase exploitability under specific conditions.";
222
+ return "Could cause operational issues or increase exploitability under specific conditions."
154
223
  }
155
224
  if (severity === "Low") {
156
- return "Limited direct impact but should be addressed to improve security posture.";
225
+ return "Limited direct impact but should be addressed to improve security posture."
157
226
  }
158
- return "No immediate exploit impact, but useful for hardening and maintainability.";
227
+ return "No immediate exploit impact, but useful for hardening and maintainability."
159
228
  }
160
229
 
161
230
  function genericRecommendation(severity: FindingSeverity): string {
162
231
  if (severity === "Critical" || severity === "High") {
163
- return "Prioritize remediation before production deployment and validate with focused regression tests.";
232
+ return "Prioritize remediation before production deployment and validate with focused regression tests."
164
233
  }
165
234
  if (severity === "Medium") {
166
- return "Address in the near term and include unit/integration tests to prevent regressions.";
235
+ return "Address in the near term and include unit/integration tests to prevent regressions."
167
236
  }
168
237
  if (severity === "Low") {
169
- return "Schedule remediation in regular hardening cycles.";
238
+ return "Schedule remediation in regular hardening cycles."
170
239
  }
171
- return "Track and resolve during routine code quality and documentation improvements.";
240
+ return "Track and resolve during routine code quality and documentation improvements."
172
241
  }
173
242
 
174
243
  function buildRecommendations(counts: FindingsCount): string[] {
175
- const items: string[] = [];
244
+ const items: string[] = []
176
245
 
177
246
  if (counts.critical > 0) {
178
- items.push("1. Immediately remediate all Critical findings and block release until fixes are verified.");
247
+ items.push(
248
+ "1. Immediately remediate all Critical findings and block release until fixes are verified.",
249
+ )
179
250
  }
180
251
  if (counts.high > 0) {
181
- items.push("2. Prioritize High findings in the next patch cycle with dedicated security test coverage.");
252
+ items.push(
253
+ "2. Prioritize High findings in the next patch cycle with dedicated security test coverage.",
254
+ )
182
255
  }
183
256
  if (counts.medium > 0) {
184
- items.push("3. Resolve Medium findings to reduce attack surface and improve resilience.");
257
+ items.push("3. Resolve Medium findings to reduce attack surface and improve resilience.")
185
258
  }
186
259
  if (counts.low > 0 || counts.informational > 0) {
187
- items.push("4. Address Low/Informational findings as part of ongoing hardening and code quality efforts.");
260
+ items.push(
261
+ "4. Address Low/Informational findings as part of ongoing hardening and code quality efforts.",
262
+ )
188
263
  }
189
264
 
190
265
  if (items.length === 0) {
191
- items.push("1. Maintain current controls, monitor code changes, and re-audit before major upgrades.");
266
+ items.push(
267
+ "1. Maintain current controls, monitor code changes, and re-audit before major upgrades.",
268
+ )
192
269
  }
193
270
 
194
- return items;
271
+ return items
195
272
  }
196
273
 
197
274
  function buildFindingsSection(findings: Finding[]): string {
198
275
  if (findings.length === 0) {
199
- return "## Findings\nNo findings meet the configured severity threshold.";
276
+ return "## Findings\nNo findings meet the configured severity threshold."
200
277
  }
201
278
 
202
- const lines: string[] = ["## Findings"];
279
+ const lines: string[] = ["## Findings"]
203
280
 
204
281
  for (const severity of SEVERITY_ORDER) {
205
- const severityFindings = findings.filter((finding) => finding.severity === severity);
282
+ const severityFindings = findings.filter((finding) => finding.severity === severity)
206
283
  if (severityFindings.length === 0) {
207
- continue;
284
+ continue
208
285
  }
209
286
 
210
- lines.push(`### ${severity}`);
287
+ lines.push(`### ${severity}`)
211
288
 
212
289
  severityFindings.forEach((finding, index) => {
213
- const prefix = SEVERITY_PREFIX[severity];
214
- const findingId = `[${prefix}-${index + 1}]`;
215
- const title = normalizeTitle(finding.check);
216
- const recommendation = finding.remediation ?? genericRecommendation(severity);
217
-
218
- lines.push(`### ${findingId} ${title}`);
219
- lines.push(`**Severity**: ${finding.severity}`);
220
- lines.push(`**Confidence**: ${finding.confidence}`);
221
- lines.push(`**Location**: ${formatLocation(finding)}`);
222
- lines.push("");
223
- lines.push(`**Description**: ${finding.description}`);
224
- lines.push("");
225
- lines.push(`**Impact**: ${genericImpact(finding.severity)}`);
226
- lines.push("");
227
- lines.push(`**Recommendation**: ${recommendation}`);
228
- lines.push("");
229
- });
290
+ const prefix = SEVERITY_PREFIX[severity]
291
+ const findingId = `[${prefix}-${index + 1}]`
292
+ const title = normalizeTitle(finding.check)
293
+ const recommendation = finding.remediation ?? genericRecommendation(severity)
294
+
295
+ lines.push(`### ${findingId} ${title}`)
296
+ lines.push(`**Severity**: ${finding.severity}`)
297
+ lines.push(`**Confidence**: ${finding.confidence}`)
298
+ lines.push(`**Location**: ${formatLocation(finding)}`)
299
+ lines.push("")
300
+ lines.push(`**Description**: ${finding.description}`)
301
+ lines.push("")
302
+ lines.push(`**Impact**: ${genericImpact(finding.severity)}`)
303
+ lines.push("")
304
+ lines.push(`**Recommendation**: ${recommendation}`)
305
+ lines.push("")
306
+ })
230
307
  }
231
308
 
232
- return lines.join("\n");
309
+ return lines.join("\n")
233
310
  }
234
311
 
235
312
  function formatDuration(ms: number): string {
236
- if (ms < 1000) return `${ms}ms`;
237
- return `${(ms / 1000).toFixed(1)}s`;
313
+ if (ms < 1000) return `${ms}ms`
314
+ return `${(ms / 1000).toFixed(1)}s`
238
315
  }
239
316
 
240
317
  export function buildProvenanceAppendix(
@@ -242,174 +319,165 @@ export function buildProvenanceAppendix(
242
319
  threshold: SeverityThreshold,
243
320
  includedCount: number,
244
321
  ): string {
245
- const lines: string[] = ["## Appendix: Data Provenance"];
322
+ const lines: string[] = ["## Appendix: Data Provenance"]
246
323
 
247
- lines.push("- Data source: `audit_state` payload");
248
- lines.push(`- Severity threshold applied: ${threshold}`);
249
- lines.push(`- Findings included in report: ${includedCount}`);
324
+ lines.push("- Data source: `audit_state` payload")
325
+ lines.push(`- Severity threshold applied: ${threshold}`)
326
+ lines.push(`- Findings included in report: ${includedCount}`)
250
327
 
251
328
  if (state.findings.length > 0) {
252
- const sourceCounts: Record<string, number> = {};
329
+ const sourceCounts: Record<string, number> = {}
253
330
  for (const f of state.findings) {
254
- sourceCounts[f.source] = (sourceCounts[f.source] ?? 0) + 1;
331
+ sourceCounts[f.source] = (sourceCounts[f.source] ?? 0) + 1
255
332
  }
256
- lines.push("");
257
- lines.push("### Source Breakdown");
258
- lines.push("");
259
- lines.push("| Source | Count |");
260
- lines.push("| --- | ---: |");
261
- for (const [source, count] of Object.entries(sourceCounts).sort(
262
- (a, b) => b[1] - a[1],
263
- )) {
264
- lines.push(`| ${source} | ${count} |`);
333
+ lines.push("")
334
+ lines.push("### Source Breakdown")
335
+ lines.push("")
336
+ lines.push("| Source | Count |")
337
+ lines.push("| --- | ---: |")
338
+ for (const [source, count] of Object.entries(sourceCounts).sort((a, b) => b[1] - a[1])) {
339
+ lines.push(`| ${source} | ${count} |`)
265
340
  }
266
341
  }
267
342
 
268
343
  if (state.toolsExecuted.length > 0) {
269
- lines.push("");
270
- lines.push("### Tool Execution Summary");
271
- lines.push("");
272
- lines.push("| Tool | Duration | Status | Findings |");
273
- lines.push("| --- | --- | --- | ---: |");
344
+ lines.push("")
345
+ lines.push("### Tool Execution Summary")
346
+ lines.push("")
347
+ lines.push("| Tool | Duration | Status | Findings |")
348
+ lines.push("| --- | --- | --- | ---: |")
274
349
  for (const exec of state.toolsExecuted) {
275
- const duration =
276
- exec.endTime != null
277
- ? formatDuration(exec.endTime - exec.startTime)
278
- : "—";
279
- const status = exec.success ? "✅ success" : "❌ failure";
280
- lines.push(
281
- `| ${exec.tool} | ${duration} | ${status} | ${exec.findingsCount} |`,
282
- );
350
+ const duration = exec.endTime != null ? formatDuration(exec.endTime - exec.startTime) : "—"
351
+ const status = exec.success ? "✅ success" : "❌ failure"
352
+ lines.push(`| ${exec.tool} | ${duration} | ${status} | ${exec.findingsCount} |`)
283
353
  }
284
354
  }
285
355
 
286
- const syncExec = state.toolsExecuted.find((t) => t.tool === "argus_sync_knowledge");
356
+ const syncExec = state.toolsExecuted.find((t) => t.tool === "argus_sync_knowledge")
287
357
  if (state.patternVersion || syncExec) {
288
- lines.push("");
289
- lines.push("### Data Freshness");
290
- lines.push("");
358
+ lines.push("")
359
+ lines.push("### Data Freshness")
360
+ lines.push("")
291
361
  if (state.patternVersion) {
292
- lines.push(`- Pattern pack version: \`${state.patternVersion}\``);
362
+ lines.push(`- Pattern pack version: \`${state.patternVersion}\``)
293
363
  }
294
364
  if (syncExec) {
295
- lines.push(`- SCVD last synced: ${new Date(syncExec.startTime).toISOString()}`);
365
+ lines.push(`- SCVD last synced: ${new Date(syncExec.startTime).toISOString()}`)
296
366
  }
297
367
  }
298
368
 
299
369
  if (state.soloditResults && state.soloditResults.length > 0) {
300
- lines.push("");
301
- lines.push("### Solodit Cross-References");
302
- lines.push("");
370
+ lines.push("")
371
+ lines.push("### Solodit Cross-References")
372
+ lines.push("")
303
373
  for (const result of state.soloditResults) {
304
- lines.push(`**Query**: "${result.query}" — ${result.resultCount} results`);
374
+ lines.push(`**Query**: "${result.query}" — ${result.resultCount} results`)
305
375
  if (result.topResults.length > 0) {
306
- lines.push("");
307
- lines.push("| Title | Severity | Protocol |");
308
- lines.push("| --- | --- | --- |");
376
+ lines.push("")
377
+ lines.push("| Title | Severity | Protocol |")
378
+ lines.push("| --- | --- | --- |")
309
379
  for (const top of result.topResults) {
310
- lines.push(`| ${top.title} | ${top.severity} | ${top.protocol} |`);
380
+ lines.push(`| ${top.title} | ${top.severity} | ${top.protocol} |`)
311
381
  }
312
382
  }
313
- lines.push("");
383
+ lines.push("")
314
384
  }
315
385
  }
316
386
 
317
387
  if (state.fuzzCounterexamples && state.fuzzCounterexamples.length > 0) {
318
- lines.push("");
319
- lines.push("### Fuzz Evidence");
320
- lines.push("");
321
- lines.push("| Test | Inputs | Runs | Revert Reason |");
322
- lines.push("| --- | --- | ---: | --- |");
388
+ lines.push("")
389
+ lines.push("### Fuzz Evidence")
390
+ lines.push("")
391
+ lines.push("| Test | Inputs | Runs | Revert Reason |")
392
+ lines.push("| --- | --- | ---: | --- |")
323
393
  for (const cx of state.fuzzCounterexamples) {
324
- const inputs = cx.inputs.join(", ");
325
- const reason = cx.revertReason ?? "—";
326
- lines.push(`| ${cx.testName} | ${inputs} | ${cx.runs} | ${reason} |`);
394
+ const inputs = cx.inputs.join(", ")
395
+ const reason = cx.revertReason ?? "—"
396
+ lines.push(`| ${cx.testName} | ${inputs} | ${cx.runs} | ${reason} |`)
327
397
  }
328
398
  }
329
399
 
330
400
  if (state.skillsLoaded && state.skillsLoaded.length > 0) {
331
- lines.push("");
332
- lines.push("### Knowledge Sources");
333
- lines.push("");
334
- lines.push("Skills loaded during this audit:");
335
- lines.push("");
401
+ lines.push("")
402
+ lines.push("### Knowledge Sources")
403
+ lines.push("")
404
+ lines.push("Skills loaded during this audit:")
405
+ lines.push("")
336
406
  for (const skill of state.skillsLoaded) {
337
- lines.push(`- ${skill}`);
407
+ lines.push(`- ${skill}`)
338
408
  }
339
409
  }
340
410
 
341
- return lines.join("\n");
411
+ return lines.join("\n")
342
412
  }
343
413
 
344
414
  export async function executeReportGeneration(
345
415
  args: ReportGeneratorArgs,
346
- context: ToolContext
416
+ context: ToolContext,
347
417
  ): Promise<ReportGenerationResult> {
348
- const includeExecutiveSummary = args.include_executive_summary ?? true;
349
- const threshold = args.severity_threshold ?? "low";
350
- const state = parseAuditState(args.audit_state);
351
- const findings = state.findings.filter((finding) =>
352
- shouldIncludeFinding(finding, threshold)
353
- );
354
- const counts = calculateCounts(findings);
355
- const auditDate = new Date().toISOString().slice(0, 10);
418
+ const includeExecutiveSummary = args.include_executive_summary ?? true
419
+ const threshold = args.severity_threshold ?? "low"
420
+ const state = parseAuditState(args.audit_state)
421
+ const findings = state.findings.filter((finding) => shouldIncludeFinding(finding, threshold))
422
+ const counts = calculateCounts(findings)
423
+ const auditDate = new Date().toISOString().slice(0, 10)
356
424
 
357
- context.metadata({ title: `Generate audit report: ${args.project_name}` });
425
+ context.metadata({ title: `Generate audit report: ${args.project_name}` })
358
426
 
359
- const sections: string[] = [`# Security Audit Report — ${args.project_name}`];
427
+ const sections: string[] = [`# Security Audit Report — ${args.project_name}`]
360
428
 
361
429
  if (includeExecutiveSummary) {
362
- sections.push("## Executive Summary");
430
+ sections.push("## Executive Summary")
363
431
  sections.push(
364
- `This report summarizes security findings identified for ${args.project_name} based on static analysis, testing, and pattern-based review.`
365
- );
366
- sections.push("");
367
- sections.push("| Severity | Count |");
368
- sections.push("| --- | ---: |");
369
- sections.push(`| Critical | ${counts.critical} |`);
370
- sections.push(`| High | ${counts.high} |`);
371
- sections.push(`| Medium | ${counts.medium} |`);
372
- sections.push(`| Low | ${counts.low} |`);
373
- sections.push(`| Informational | ${counts.informational} |`);
374
- sections.push("");
375
- sections.push(`Overall risk assessment: ${overallRiskAssessment(counts)}.`);
432
+ `This report summarizes security findings identified for ${args.project_name} based on static analysis, testing, and pattern-based review.`,
433
+ )
434
+ sections.push("")
435
+ sections.push("| Severity | Count |")
436
+ sections.push("| --- | ---: |")
437
+ sections.push(`| Critical | ${counts.critical} |`)
438
+ sections.push(`| High | ${counts.high} |`)
439
+ sections.push(`| Medium | ${counts.medium} |`)
440
+ sections.push(`| Low | ${counts.low} |`)
441
+ sections.push(`| Informational | ${counts.informational} |`)
442
+ sections.push("")
443
+ sections.push(`Overall risk assessment: ${overallRiskAssessment(counts)}.`)
376
444
  }
377
445
 
378
- sections.push("## Scope");
379
- sections.push("Contracts in scope:");
446
+ sections.push("## Scope")
447
+ sections.push("Contracts in scope:")
380
448
  if (args.scope.length === 0) {
381
- sections.push("- None provided");
449
+ sections.push("- None provided")
382
450
  } else {
383
451
  for (const contract of args.scope) {
384
- sections.push(`- ${contract}`);
452
+ sections.push(`- ${contract}`)
385
453
  }
386
454
  }
387
- sections.push(`Audit date: ${auditDate}`);
388
-
389
- sections.push("## Methodology");
390
- sections.push("Tools and techniques used:");
391
- sections.push("- Slither static analysis");
392
- sections.push("- Foundry tests and fuzzing");
393
- sections.push("- Pattern Analysis");
394
- sections.push("- Solodit research cross-referencing");
455
+ sections.push(`Audit date: ${auditDate}`)
456
+
457
+ sections.push("## Methodology")
458
+ sections.push("Tools and techniques used:")
459
+ sections.push("- Slither static analysis")
460
+ sections.push("- Foundry tests and fuzzing")
461
+ sections.push("- Pattern Analysis")
462
+ sections.push("- Solodit research cross-referencing")
395
463
  sections.push(
396
- "Approach: Findings were normalized, deduplicated by detector signature and location, then prioritized by severity and confidence."
397
- );
464
+ "Approach: Findings were normalized, deduplicated by detector signature and location, then prioritized by severity and confidence.",
465
+ )
398
466
 
399
- sections.push(buildFindingsSection(findings));
467
+ sections.push(buildFindingsSection(findings))
400
468
 
401
- sections.push("## Recommendations");
469
+ sections.push("## Recommendations")
402
470
  for (const item of buildRecommendations(counts)) {
403
- sections.push(`- ${item}`);
471
+ sections.push(`- ${item}`)
404
472
  }
405
473
 
406
- sections.push(buildProvenanceAppendix(state, threshold, findings.length));
474
+ sections.push(buildProvenanceAppendix(state, threshold, findings.length))
407
475
 
408
476
  return {
409
477
  report: sections.join("\n\n"),
410
478
  findingsCount: counts,
411
479
  filename: `${args.project_name}-audit-report-${auditDate}.md`,
412
- };
480
+ }
413
481
  }
414
482
 
415
483
  export const reportGeneratorTool = tool({
@@ -425,7 +493,7 @@ export const reportGeneratorTool = tool({
425
493
  audit_state: tool.schema.string(),
426
494
  },
427
495
  async execute(args, context) {
428
- const result = await executeReportGeneration(args, context);
429
- return JSON.stringify(result);
496
+ const result = await executeReportGeneration(args, context)
497
+ return JSON.stringify(result)
430
498
  },
431
- });
499
+ })