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