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,85 +1,261 @@
1
- import type { ScvdClient } from "./scvd-client";
2
- import { buildIndex, loadIndex, saveIndex } from "./scvd-index";
3
-
4
- export interface SyncResult {
5
- success: boolean;
6
- newFindings: number;
7
- totalIndexed: number;
8
- lastSync: string;
9
- error?: string;
1
+ import { createLogger } from "../shared/logger"
2
+ import { withRetry } from "./retry"
3
+ import type { ScvdClient } from "./scvd-client"
4
+ import { ScvdApiError, ScvdNetworkError } from "./scvd-client"
5
+ import {
6
+ createApiError,
7
+ createNetworkError,
8
+ createParseError,
9
+ createSyncSuccess,
10
+ isRetryableError,
11
+ type SyncError,
12
+ type SyncOutcome,
13
+ } from "./scvd-errors"
14
+ import {
15
+ acquireSyncLock,
16
+ buildIndex,
17
+ loadIndex,
18
+ releaseSyncLock,
19
+ type ScvdIndex,
20
+ type ScvdIndexMetadata,
21
+ saveIndex,
22
+ } from "./scvd-index"
23
+
24
+ export type SyncResult = SyncOutcome
25
+
26
+ const RETRY_MAX_ATTEMPTS = 3
27
+ const RETRY_BASE_DELAY_MS = 1000
28
+
29
+ function buildErrorResult(error: unknown): SyncError {
30
+ const message = error instanceof Error ? error.message : "Unknown sync error"
31
+
32
+ if (error instanceof ScvdNetworkError) {
33
+ return createNetworkError(message)
34
+ }
35
+ if (error instanceof ScvdApiError) {
36
+ return createApiError(error.httpStatus, message)
37
+ }
38
+ return createParseError(message)
10
39
  }
11
40
 
12
- function buildErrorResult(error: unknown): SyncResult {
13
- const message = error instanceof Error ? error.message : "Unknown sync error";
14
- return {
15
- success: false,
16
- newFindings: 0,
17
- totalIndexed: 0,
18
- lastSync: new Date().toISOString(),
19
- error: message,
20
- };
41
+ function shouldRetrySyncError(error: unknown): boolean {
42
+ if (!(error instanceof ScvdNetworkError)) {
43
+ return false
44
+ }
45
+
46
+ return isRetryableError(buildErrorResult(error))
47
+ }
48
+
49
+ function errorReasonFromResult(result: SyncError): string {
50
+ return result.reason
51
+ }
52
+
53
+ async function persistErrorMetadata(indexPath: string, errorResult: SyncError): Promise<void> {
54
+ const existing = await loadIndex(indexPath)
55
+ if (!existing) return
56
+
57
+ const now = new Date().toISOString()
58
+ const prevMetadata = existing.metadata
59
+ existing.metadata = {
60
+ lastSuccess: prevMetadata?.lastSuccess ?? null,
61
+ lastAttempt: now,
62
+ errorCount: (prevMetadata?.errorCount ?? 0) + 1,
63
+ lastError: errorResult.message,
64
+ lastErrorReason: errorReasonFromResult(errorResult),
65
+ }
66
+ await saveIndex(existing, indexPath)
67
+ }
68
+
69
+ async function syncAllUnlocked(client: ScvdClient, indexPath: string): Promise<SyncResult> {
70
+ const fetchResult = await withRetry(() => client.fetchAllFindings(), {
71
+ maxAttempts: RETRY_MAX_ATTEMPTS,
72
+ baseDelayMs: RETRY_BASE_DELAY_MS,
73
+ shouldRetry: shouldRetrySyncError,
74
+ })
75
+
76
+ if (!fetchResult.success) {
77
+ const errorResult = buildErrorResult(fetchResult.error)
78
+ errorResult.attempts = fetchResult.attempts
79
+ await persistErrorMetadata(indexPath, errorResult)
80
+ return errorResult
81
+ }
82
+
83
+ if (fetchResult.value === undefined) {
84
+ const errorResult = createParseError("SCVD sync returned no findings payload")
85
+ errorResult.attempts = fetchResult.attempts
86
+ await persistErrorMetadata(indexPath, errorResult)
87
+ return errorResult
88
+ }
89
+
90
+ const findings = fetchResult.value
91
+ const index = buildIndex(findings)
92
+ const now = new Date().toISOString()
93
+ index.metadata = {
94
+ lastSuccess: now,
95
+ lastAttempt: now,
96
+ errorCount: 0,
97
+ lastError: null,
98
+ lastErrorReason: null,
99
+ }
100
+ await saveIndex(index, indexPath)
101
+
102
+ return createSyncSuccess({
103
+ newFindings: findings.length,
104
+ totalIndexed: index.totalFindings,
105
+ lastSync: index.lastSync,
106
+ attempts: fetchResult.attempts,
107
+ })
21
108
  }
22
109
 
23
110
  export async function syncAll(client: ScvdClient, indexPath: string): Promise<SyncResult> {
24
- try {
25
- const findings = await client.fetchAllFindings();
26
- const index = buildIndex(findings);
27
- await saveIndex(index, indexPath);
111
+ const logger = createLogger()
28
112
 
29
- return {
30
- success: true,
31
- newFindings: findings.length,
32
- totalIndexed: index.totalFindings,
33
- lastSync: index.lastSync,
34
- };
113
+ if (!acquireSyncLock()) {
114
+ return createParseError("Sync already in progress")
115
+ }
116
+
117
+ logger.debug("[sync] starting", "source=scvd mode=full")
118
+
119
+ try {
120
+ const result = await syncAllUnlocked(client, indexPath)
121
+ if (result.success) {
122
+ logger.debug(
123
+ "[sync] complete",
124
+ `source=scvd newFindings=${result.newFindings} totalIndexed=${result.totalIndexed}`,
125
+ )
126
+ } else {
127
+ const reason = result.status === "error" ? result.reason : result.status
128
+ logger.debug("[sync] failed", `source=scvd reason=${reason}`)
129
+ }
130
+ return result
35
131
  } catch (error) {
36
- return buildErrorResult(error);
132
+ const errorResult = buildErrorResult(error)
133
+ logger.debug("[sync] failed", `source=scvd reason=${errorResult.reason}`)
134
+ await persistErrorMetadata(indexPath, errorResult).catch(() => {
135
+ logger.debug("Failed to persist sync error metadata")
136
+ })
137
+ return errorResult
138
+ } finally {
139
+ releaseSyncLock()
37
140
  }
38
141
  }
39
142
 
40
- export async function syncIncremental(
41
- client: ScvdClient,
42
- indexPath: string
43
- ): Promise<SyncResult> {
143
+ export async function syncIncremental(client: ScvdClient, indexPath: string): Promise<SyncResult> {
144
+ const logger = createLogger()
145
+
146
+ if (!acquireSyncLock()) {
147
+ return createParseError("Sync already in progress")
148
+ }
149
+
150
+ logger.debug("[sync] starting", "source=scvd mode=incremental")
151
+
44
152
  try {
45
- const [stats, existingIndex] = await Promise.all([
46
- client.fetchStats(),
153
+ const [statsResult, existingIndex] = await Promise.all([
154
+ withRetry(() => client.fetchStats(), {
155
+ maxAttempts: RETRY_MAX_ATTEMPTS,
156
+ baseDelayMs: RETRY_BASE_DELAY_MS,
157
+ shouldRetry: shouldRetrySyncError,
158
+ }),
47
159
  loadIndex(indexPath),
48
- ]);
160
+ ])
161
+
162
+ if (!statsResult.success) {
163
+ const errorResult = buildErrorResult(statsResult.error)
164
+ errorResult.attempts = statsResult.attempts
165
+ await persistErrorMetadata(indexPath, errorResult).catch(() => {
166
+ logger.debug("Failed to persist sync error metadata")
167
+ })
168
+ return errorResult
169
+ }
170
+
171
+ if (statsResult.value === undefined) {
172
+ const errorResult = createParseError("SCVD sync returned no stats payload")
173
+ errorResult.attempts = statsResult.attempts
174
+ await persistErrorMetadata(indexPath, errorResult).catch(() => {
175
+ logger.debug("Failed to persist sync error metadata")
176
+ })
177
+ return errorResult
178
+ }
179
+
180
+ const stats = statsResult.value
49
181
 
50
182
  if (existingIndex && existingIndex.totalFindings === stats.total) {
51
- return {
52
- success: true,
183
+ return createSyncSuccess({
53
184
  newFindings: 0,
54
185
  totalIndexed: existingIndex.totalFindings,
55
186
  lastSync: existingIndex.lastSync,
56
- };
187
+ })
57
188
  }
58
189
 
59
- return await syncAll(client, indexPath);
190
+ return await syncAllUnlocked(client, indexPath)
60
191
  } catch (error) {
61
- return buildErrorResult(error);
192
+ const errorResult = buildErrorResult(error)
193
+ await persistErrorMetadata(indexPath, errorResult).catch(() => {
194
+ logger.debug("Failed to persist sync error metadata")
195
+ })
196
+ return errorResult
197
+ } finally {
198
+ releaseSyncLock()
62
199
  }
63
200
  }
201
+ const STALE_THRESHOLD_DAYS = 7
202
+
203
+ export function isSyncStale(
204
+ index: ScvdIndex | null,
205
+ thresholdDays: number = STALE_THRESHOLD_DAYS,
206
+ ): boolean {
207
+ if (!index || !index.lastSync) return true
208
+ const lastSyncDate = new Date(index.lastSync)
209
+ const now = new Date()
210
+ const diffMs = now.getTime() - lastSyncDate.getTime()
211
+ const diffDays = diffMs / (1000 * 60 * 60 * 24)
212
+ return diffDays > thresholdDays
213
+ }
64
214
 
65
215
  export async function getSyncStatus(indexPath: string): Promise<{
66
- lastSync: string | null;
67
- totalFindings: number;
68
- healthy: boolean;
216
+ lastSync: string | null
217
+ totalFindings: number
218
+ healthy: boolean
219
+ stale: boolean
220
+ metadata: ScvdIndexMetadata | null
221
+ hint?: string
69
222
  }> {
70
- const index = await loadIndex(indexPath);
223
+ const logger = createLogger()
224
+ const index = await loadIndex(indexPath)
71
225
 
72
226
  if (!index) {
73
227
  return {
74
228
  lastSync: null,
75
229
  totalFindings: 0,
76
230
  healthy: false,
77
- };
231
+ stale: true,
232
+ metadata: null,
233
+ hint: "SCVD data is missing. Run argus_sync_knowledge to populate.",
234
+ }
235
+ }
236
+
237
+ const stale = isSyncStale(index)
238
+
239
+ if (stale) {
240
+ const lastSyncDate = new Date(index.lastSync)
241
+ const daysSince = Math.floor((Date.now() - lastSyncDate.getTime()) / (1000 * 60 * 60 * 24))
242
+ logger.debug("[sync] stale", `source=scvd daysSince=${daysSince}`)
243
+
244
+ return {
245
+ lastSync: index.lastSync,
246
+ totalFindings: index.totalFindings,
247
+ healthy: true,
248
+ stale: true,
249
+ metadata: index.metadata ?? null,
250
+ hint: "SCVD data is stale. Run argus_sync_knowledge to update.",
251
+ }
78
252
  }
79
253
 
80
254
  return {
81
255
  lastSync: index.lastSync,
82
256
  totalFindings: index.totalFindings,
83
257
  healthy: true,
84
- };
258
+ stale: false,
259
+ metadata: index.metadata ?? null,
260
+ }
85
261
  }
@@ -0,0 +1,102 @@
1
+ export type SourceMode = "baked-in" | "on-demand" | "hybrid"
2
+
3
+ export interface SourceManifest {
4
+ name: string
5
+ mode: SourceMode
6
+ url: string
7
+ license: string
8
+ updateCadence: string
9
+ lastUpdated?: string
10
+ hash?: string
11
+ version?: string
12
+ }
13
+
14
+ export class IngestionRegistry {
15
+ private sources = new Map<string, SourceManifest>()
16
+
17
+ register(manifest: SourceManifest): void {
18
+ this.sources.set(manifest.name, manifest)
19
+ }
20
+
21
+ get(name: string): SourceManifest | null {
22
+ return this.sources.get(name) ?? null
23
+ }
24
+
25
+ list(): SourceManifest[] {
26
+ return Array.from(this.sources.values())
27
+ }
28
+
29
+ getByMode(mode: SourceMode): SourceManifest[] {
30
+ return Array.from(this.sources.values()).filter((m) => m.mode === mode)
31
+ }
32
+ }
33
+
34
+ export function createDefaultRegistry(): IngestionRegistry {
35
+ const registry = new IngestionRegistry()
36
+
37
+ registry.register({
38
+ name: "cyfrin",
39
+ mode: "baked-in",
40
+ url: "https://github.com/Cyfrin/audit-checklist",
41
+ license: "unspecified",
42
+ updateCadence: "per-release",
43
+ })
44
+
45
+ registry.register({
46
+ name: "kadenzipfel",
47
+ mode: "baked-in",
48
+ url: "https://github.com/kadenzipfel/smart-contract-vulnerabilities",
49
+ license: "MIT",
50
+ updateCadence: "per-release",
51
+ })
52
+
53
+ registry.register({
54
+ name: "defifofum",
55
+ mode: "baked-in",
56
+ url: "https://github.com/DeFiFoFum/fofum-solidity-skills",
57
+ license: "MIT",
58
+ updateCadence: "per-release",
59
+ })
60
+
61
+ registry.register({
62
+ name: "smartbugs",
63
+ mode: "baked-in",
64
+ url: "https://github.com/smartbugs/smartbugs-curated",
65
+ license: "Apache-2.0",
66
+ updateCadence: "per-release",
67
+ })
68
+
69
+ registry.register({
70
+ name: "sunweb3sec",
71
+ mode: "baked-in",
72
+ url: "https://github.com/SunWeb3Sec/DeFiHackLabs",
73
+ license: "reference-only",
74
+ updateCadence: "per-release",
75
+ })
76
+
77
+ registry.register({
78
+ name: "scvd",
79
+ mode: "hybrid",
80
+ url: "https://api.scvd.dev",
81
+ license: "CC0",
82
+ updateCadence: "on-sync",
83
+ })
84
+
85
+ registry.register({
86
+ name: "trailofbits",
87
+ mode: "hybrid",
88
+ url: "https://github.com/trailofbits/solidity-security-research",
89
+ license: "varies",
90
+ updateCadence: "on-install",
91
+ })
92
+
93
+ registry.register({
94
+ name: "solodit",
95
+ mode: "on-demand",
96
+ url: "https://solodit.xyz",
97
+ license: "varies",
98
+ updateCadence: "per-request",
99
+ })
100
+
101
+ return registry
102
+ }
@@ -1 +1 @@
1
- export type { BackgroundManager, AuditStateManager, Managers } from "./types";
1
+ export type { AuditStateManager, BackgroundManager, Managers } from "./types"
@@ -1,4 +1,4 @@
1
- import type { AuditState } from "../state/types";
1
+ import type { AuditState } from "../state/types"
2
2
 
3
3
  /**
4
4
  * BackgroundManager interface
@@ -12,32 +12,32 @@ export interface BackgroundManager {
12
12
  * @param options - Optional configuration (priority, timeout, etc.)
13
13
  * @returns taskId - Unique identifier for tracking this task
14
14
  */
15
- dispatch(agentName: string, prompt: string, options?: { priority?: number }): string;
15
+ dispatch(agentName: string, prompt: string, options?: { priority?: number }): string
16
16
 
17
17
  /**
18
18
  * Cancel a running background task
19
19
  * @param taskId - The task ID to cancel
20
20
  */
21
- cancel(taskId: string): void;
21
+ cancel(taskId: string): void
22
22
 
23
23
  /**
24
24
  * Get the result of a completed background task
25
25
  * @param taskId - The task ID to retrieve results for
26
26
  * @returns Promise resolving to the task result
27
27
  */
28
- getResult(taskId: string): Promise<unknown>;
28
+ getResult(taskId: string): Promise<unknown>
29
29
 
30
30
  /**
31
31
  * Register a callback to be invoked when a task completes
32
32
  * @param callback - Function called with (taskId, result) when task finishes
33
33
  */
34
- onComplete(callback: (taskId: string, result: unknown) => void): void;
34
+ onComplete(callback: (taskId: string, result: unknown) => void): void
35
35
 
36
36
  /**
37
37
  * Get the number of currently active/running tasks
38
38
  * @returns Number of active tasks
39
39
  */
40
- getActiveCount(): number;
40
+ getActiveCount(): number
41
41
  }
42
42
 
43
43
  /**
@@ -49,30 +49,35 @@ export interface AuditStateManager {
49
49
  * Load audit state from persistent storage
50
50
  * @returns Promise resolving to AuditState or null if not found
51
51
  */
52
- load(): Promise<AuditState | null>;
52
+ load(): Promise<AuditState | null>
53
53
 
54
54
  /**
55
55
  * Save audit state to persistent storage
56
56
  * @param state - The AuditState to persist
57
57
  */
58
- save(state: AuditState): Promise<void>;
58
+ save(state: AuditState): Promise<void>
59
59
 
60
60
  /**
61
61
  * Get the current in-memory audit state
62
62
  * @returns The current AuditState or null if not loaded
63
63
  */
64
- get(): AuditState | null;
64
+ get(): AuditState | null
65
65
 
66
66
  /**
67
67
  * Update the audit state with a partial patch
68
68
  * @param patch - Partial AuditState object with fields to update
69
69
  */
70
- update(patch: Partial<AuditState>): Promise<void>;
70
+ update(patch: Partial<AuditState>): Promise<void>
71
71
 
72
72
  /**
73
73
  * Reset the audit state (clear all data)
74
74
  */
75
- reset(): Promise<void>;
75
+ reset(): Promise<void>
76
+
77
+ /**
78
+ * Archive current state (if meaningful) then reset
79
+ */
80
+ archive(): Promise<void>
76
81
  }
77
82
 
78
83
  /**
@@ -80,6 +85,6 @@ export interface AuditStateManager {
80
85
  * Container for all manager instances
81
86
  */
82
87
  export type Managers = {
83
- backgroundManager: BackgroundManager;
84
- auditStateManager: AuditStateManager;
85
- };
88
+ backgroundManager: BackgroundManager
89
+ auditStateManager: AuditStateManager
90
+ }
@@ -11,14 +11,25 @@ export function createPluginInterface(args: {
11
11
  }): PluginReturn {
12
12
  const { tools, hooks } = args
13
13
 
14
- const result: PluginReturn = {
15
- tool: tools,
16
- config: hooks.config,
17
- }
18
-
19
- if (hooks["experimental.session.compacting"]) {
20
- result["experimental.session.compacting"] =
21
- hooks["experimental.session.compacting"]
14
+ const result: PluginReturn = {
15
+ tool: tools,
16
+ config: hooks.config,
17
+ }
18
+
19
+ if (hooks["chat.params"]) {
20
+ result["chat.params"] = hooks["chat.params"]
21
+ }
22
+
23
+ if (hooks["chat.message"]) {
24
+ result["chat.message"] = hooks["chat.message"]
25
+ }
26
+
27
+ if (hooks["experimental.chat.system.transform"]) {
28
+ result["experimental.chat.system.transform"] = hooks["experimental.chat.system.transform"]
29
+ }
30
+
31
+ if (hooks["experimental.session.compacting"]) {
32
+ result["experimental.session.compacting"] = hooks["experimental.session.compacting"]
22
33
  }
23
34
 
24
35
  if (hooks["tool.execute.after"]) {
@@ -1,63 +1,73 @@
1
- import { execSync } from "child_process";
2
- import { existsSync, readFileSync } from "fs";
3
- import { join } from "path";
1
+ import { existsSync } from "node:fs"
2
+ import { join } from "node:path"
3
+ import { createLogger } from "./logger"
4
+
5
+ const logger = createLogger()
4
6
 
5
7
  export function hasBinary(name: string): boolean {
6
8
  try {
7
- execSync(`which ${name}`, { stdio: "ignore", timeout: 3_000 });
8
- return true;
9
+ const result = Bun.spawnSync(["which", name], {
10
+ stdout: "ignore",
11
+ stderr: "ignore",
12
+ timeout: 5_000,
13
+ })
14
+ return result.exitCode === 0
9
15
  } catch (_e) {
10
- return false;
16
+ return false
11
17
  }
12
18
  }
13
19
 
14
- export function parseSolcVersion(target: string): string | undefined {
15
- const foundryToml = join(target, "foundry.toml");
16
- if (existsSync(foundryToml)) {
17
- const content = readFileSync(foundryToml, "utf-8");
18
- const match = content.match(/solc\s*=\s*["']([^"']+)["']/);
19
- if (match?.[1]) return match[1];
20
+ export async function parseSolcVersion(target: string): Promise<string | undefined> {
21
+ const foundryToml = join(target, "foundry.toml")
22
+ if (await Bun.file(foundryToml).exists()) {
23
+ const content = await Bun.file(foundryToml).text()
24
+ const match = content.match(/solc\s*=\s*["']([^"']+)["']/)
25
+ if (match?.[1]) return match[1]
20
26
  }
21
27
 
22
- const solFiles = [target];
23
- if (existsSync(target) && target.endsWith(".sol")) {
24
- solFiles.push(target);
28
+ const solFiles: string[] = []
29
+ if (target.endsWith(".sol") && (await Bun.file(target).exists())) {
30
+ solFiles.push(target)
25
31
  } else {
26
- const srcDir = join(target, "src");
32
+ const srcDir = join(target, "src")
27
33
  if (existsSync(srcDir)) {
28
34
  try {
29
- const files = execSync(`find "${srcDir}" -name "*.sol" -maxdepth 3`, {
30
- encoding: "utf-8",
31
- timeout: 5_000,
35
+ const proc = Bun.spawn(["find", srcDir, "-maxdepth", "3", "-name", "*.sol"], {
36
+ stdout: "pipe",
37
+ stderr: "pipe",
38
+ signal: AbortSignal.timeout(10_000),
32
39
  })
33
- .trim()
34
- .split("\n")
35
- .filter(Boolean);
36
- solFiles.push(...files);
40
+ const exitCode = await proc.exited
41
+ if (exitCode === 0) {
42
+ const output = await new Response(proc.stdout).text()
43
+ solFiles.push(...output.trim().split("\n").filter(Boolean))
44
+ }
37
45
  } catch (_findErr) {
46
+ logger.debug("find command failed for .sol files")
38
47
  }
39
48
  }
40
49
  }
41
50
 
42
51
  for (const file of solFiles) {
43
- if (!existsSync(file) || !file.endsWith(".sol")) continue;
52
+ if (!file.endsWith(".sol") || !(await Bun.file(file).exists())) continue
44
53
  try {
45
- const content = readFileSync(file, "utf-8");
46
- const pragma = content.match(/pragma\s+solidity\s+[\^~>=<]*\s*([\d.]+)/);
47
- if (pragma?.[1]) return pragma[1];
54
+ const content = await Bun.file(file).text()
55
+ const pragma = content.match(/pragma\s+solidity\s+[\^~>=<]*\s*([\d.]+)/)
56
+ if (pragma?.[1]) return pragma[1]
48
57
  } catch (_readErr) {
58
+ logger.debug("Failed to read .sol file for pragma detection")
49
59
  }
50
60
  }
51
- return undefined;
61
+ return undefined
52
62
  }
53
63
 
54
- export function extractContractNames(filePath: string): string[] {
55
- if (!existsSync(filePath)) return [];
64
+ export async function extractContractNames(filePath: string): Promise<string[]> {
65
+ if (!(await Bun.file(filePath).exists())) return []
56
66
  try {
57
- const content = readFileSync(filePath, "utf-8");
58
- const matches = content.matchAll(/\b(?:contract|library|interface)\s+(\w+)/g);
59
- return Array.from(matches, (m) => m[1]).filter(Boolean) as string[];
67
+ const content = await Bun.file(filePath).text()
68
+ const matches = content.matchAll(/\b(?:contract|library|interface)\s+(\w+)/g)
69
+ return Array.from(matches, (m) => m[1]).filter(Boolean) as string[]
60
70
  } catch (_e) {
61
- return [];
71
+ return []
62
72
  }
63
73
  }