solidity-argus 0.2.0 → 0.3.2

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 (169) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +93 -37
  3. package/package.json +34 -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 +34 -7
  75. package/src/agents/pythia-prompt.ts +13 -4
  76. package/src/agents/scribe-prompt.ts +20 -2
  77. package/src/agents/sentinel-prompt.ts +45 -5
  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 +6 -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/system-prompt-hook.ts +18 -1
  114. package/src/hooks/tool-tracking-hook.ts +110 -51
  115. package/src/hooks/types.ts +2 -1
  116. package/src/index.ts +24 -37
  117. package/src/knowledge/retry.ts +22 -22
  118. package/src/knowledge/scvd-client.ts +88 -95
  119. package/src/knowledge/scvd-errors.ts +35 -35
  120. package/src/knowledge/scvd-index.ts +78 -80
  121. package/src/knowledge/scvd-sync.ts +106 -101
  122. package/src/managers/index.ts +1 -1
  123. package/src/managers/types.ts +19 -14
  124. package/src/plugin-interface.ts +7 -9
  125. package/src/shared/binary-utils.ts +44 -35
  126. package/src/shared/deep-merge.ts +55 -36
  127. package/src/shared/file-utils.ts +21 -19
  128. package/src/shared/index.ts +11 -5
  129. package/src/shared/jsonc-parser.ts +123 -28
  130. package/src/shared/logger.ts +16 -3
  131. package/src/shared/project-utils.ts +30 -0
  132. package/src/skills/analysis/cluster.ts +414 -0
  133. package/src/skills/analysis/gates.ts +227 -0
  134. package/src/skills/analysis/index.ts +33 -0
  135. package/src/skills/analysis/normalize.ts +217 -0
  136. package/src/skills/analysis/similarity.ts +224 -0
  137. package/src/skills/argus-skill-resolver.ts +17 -6
  138. package/src/skills/skill-schema.ts +11 -10
  139. package/src/solodit-lifecycle.ts +203 -0
  140. package/src/state/audit-state.ts +8 -8
  141. package/src/state/finding-store.ts +68 -55
  142. package/src/state/types.ts +88 -67
  143. package/src/tools/argus-skill-load-tool.ts +12 -7
  144. package/src/tools/contract-analyzer-tool.ts +142 -77
  145. package/src/tools/forge-coverage-tool.ts +226 -0
  146. package/src/tools/forge-fuzz-tool.ts +127 -127
  147. package/src/tools/forge-test-tool.ts +201 -158
  148. package/src/tools/gas-analysis-tool.ts +264 -0
  149. package/src/tools/pattern-checker-tool.ts +203 -191
  150. package/src/tools/pattern-loader.ts +5 -111
  151. package/src/tools/pattern-schema.ts +3 -0
  152. package/src/tools/proxy-detection-tool.ts +224 -0
  153. package/src/tools/report-generator-tool.ts +305 -206
  154. package/src/tools/slither-tool.ts +266 -218
  155. package/src/tools/solodit-search-tool.ts +235 -119
  156. package/src/tools/sync-knowledge-tool.ts +7 -11
  157. package/src/utils/audit-artifact-detector.ts +28 -29
  158. package/src/utils/dependency-scanner.ts +37 -37
  159. package/src/utils/project-detector.ts +111 -124
  160. package/src/utils/solidity-parser.ts +175 -75
  161. package/skills/patterns/access-control.yaml +0 -31
  162. package/skills/patterns/erc4626.yaml +0 -29
  163. package/skills/patterns/flash-loan.yaml +0 -20
  164. package/skills/patterns/oracle.yaml +0 -30
  165. package/skills/patterns/proxy.yaml +0 -30
  166. package/skills/patterns/reentrancy.yaml +0 -30
  167. package/skills/patterns/signature.yaml +0 -31
  168. package/src/hooks/event-hook-v2.ts +0 -99
  169. package/src/state/plugin-state.ts +0 -14
@@ -1,47 +1,47 @@
1
1
  export interface RetryOptions<T> {
2
- maxAttempts: number;
3
- baseDelayMs: number;
4
- shouldRetry: (error: unknown) => boolean;
5
- onRetry?: (attempt: number, error: unknown) => void;
6
- _valueType?: T;
2
+ maxAttempts: number
3
+ baseDelayMs: number
4
+ shouldRetry: (error: unknown) => boolean
5
+ onRetry?: (attempt: number, error: unknown) => void
6
+ _valueType?: T
7
7
  }
8
8
 
9
9
  export interface RetryResult<T> {
10
- success: boolean;
11
- value?: T;
12
- error?: unknown;
13
- attempts: number;
10
+ success: boolean
11
+ value?: T
12
+ error?: unknown
13
+ attempts: number
14
14
  }
15
15
 
16
16
  function sleep(delayMs: number): Promise<void> {
17
- return new Promise((resolve) => setTimeout(resolve, delayMs));
17
+ return new Promise((resolve) => setTimeout(resolve, delayMs))
18
18
  }
19
19
 
20
20
  export async function withRetry<T>(
21
21
  fn: () => Promise<T>,
22
- options: RetryOptions<T>
22
+ options: RetryOptions<T>,
23
23
  ): Promise<RetryResult<T>> {
24
- const maxAttempts = options.maxAttempts > 0 ? options.maxAttempts : 1;
25
- let lastError: unknown;
24
+ const maxAttempts = options.maxAttempts > 0 ? options.maxAttempts : 1
25
+ let lastError: unknown
26
26
 
27
27
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
28
28
  try {
29
- const value = await fn();
30
- return { success: true, value, attempts: attempt };
29
+ const value = await fn()
30
+ return { success: true, value, attempts: attempt }
31
31
  } catch (error) {
32
- lastError = error;
33
- const canRetry = attempt < maxAttempts && options.shouldRetry(error);
32
+ lastError = error
33
+ const canRetry = attempt < maxAttempts && options.shouldRetry(error)
34
34
 
35
35
  if (!canRetry) {
36
- return { success: false, error, attempts: attempt };
36
+ return { success: false, error, attempts: attempt }
37
37
  }
38
38
 
39
39
  if (options.onRetry) {
40
- options.onRetry(attempt, error);
40
+ options.onRetry(attempt, error)
41
41
  }
42
42
 
43
- const delay = options.baseDelayMs * 2 ** (attempt - 1);
44
- await sleep(delay);
43
+ const delay = options.baseDelayMs * 2 ** (attempt - 1)
44
+ await sleep(delay)
45
45
  }
46
46
  }
47
47
 
@@ -49,5 +49,5 @@ export async function withRetry<T>(
49
49
  success: false,
50
50
  error: lastError,
51
51
  attempts: maxAttempts,
52
- };
52
+ }
53
53
  }
@@ -1,79 +1,79 @@
1
1
  export interface ScvdFinding {
2
- scvd_id: string;
3
- doc_id: string;
4
- title: string;
5
- description_md: string;
6
- severity: "Critical" | "High" | "Medium" | "Low" | "Informational";
7
- taxonomy: { swc: string[]; cwe: string[] };
8
- repo: { url: string; commit?: string; lines?: [number, number] };
9
- sections: { recommendation_md?: string; poc_md?: string };
2
+ scvd_id: string
3
+ doc_id: string
4
+ title: string
5
+ description_md: string
6
+ severity: "Critical" | "High" | "Medium" | "Low" | "Informational"
7
+ taxonomy: { swc: string[]; cwe: string[] }
8
+ repo: { url: string; commit?: string; lines?: [number, number] }
9
+ sections: { recommendation_md?: string; poc_md?: string }
10
10
  }
11
11
 
12
12
  export interface ScvdStats {
13
- total: number;
14
- by_severity: Record<string, number>;
15
- last_updated: string;
13
+ total: number
14
+ by_severity: Record<string, number>
15
+ last_updated: string
16
16
  }
17
17
 
18
- const DEFAULT_PAGE_SIZE = 100;
18
+ const DEFAULT_PAGE_SIZE = 100
19
19
 
20
20
  function isRecord(value: unknown): value is Record<string, unknown> {
21
- return typeof value === "object" && value !== null;
21
+ return typeof value === "object" && value !== null
22
22
  }
23
23
 
24
24
  function toStringArray(value: unknown): string[] {
25
25
  if (!Array.isArray(value)) {
26
- return [];
26
+ return []
27
27
  }
28
28
 
29
- return value.filter((item): item is string => typeof item === "string");
29
+ return value.filter((item): item is string => typeof item === "string")
30
30
  }
31
31
 
32
32
  function toNumberRecord(value: unknown): Record<string, number> {
33
33
  if (!isRecord(value)) {
34
- return {};
34
+ return {}
35
35
  }
36
36
 
37
- const output: Record<string, number> = {};
37
+ const output: Record<string, number> = {}
38
38
  for (const [key, rawValue] of Object.entries(value)) {
39
39
  if (typeof rawValue === "number" && Number.isFinite(rawValue)) {
40
- output[key] = rawValue;
40
+ output[key] = rawValue
41
41
  }
42
42
  }
43
43
 
44
- return output;
44
+ return output
45
45
  }
46
46
 
47
47
  function parseLines(value: unknown): [number, number] | undefined {
48
48
  if (!Array.isArray(value) || value.length !== 2) {
49
- return undefined;
49
+ return undefined
50
50
  }
51
51
 
52
- const start = value[0];
53
- const end = value[1];
52
+ const start = value[0]
53
+ const end = value[1]
54
54
 
55
55
  if (typeof start !== "number" || typeof end !== "number") {
56
- return undefined;
56
+ return undefined
57
57
  }
58
58
 
59
- return [start, end];
59
+ return [start, end]
60
60
  }
61
61
 
62
62
  function parseFinding(raw: unknown): ScvdFinding | null {
63
63
  if (!isRecord(raw)) {
64
- return null;
64
+ return null
65
65
  }
66
66
 
67
- const taxonomyRaw = isRecord(raw.taxonomy) ? raw.taxonomy : {};
68
- const repoRaw = isRecord(raw.repo) ? raw.repo : {};
69
- const sectionsRaw = isRecord(raw.sections) ? raw.sections : {};
67
+ const taxonomyRaw = isRecord(raw.taxonomy) ? raw.taxonomy : {}
68
+ const repoRaw = isRecord(raw.repo) ? raw.repo : {}
69
+ const sectionsRaw = isRecord(raw.sections) ? raw.sections : {}
70
70
 
71
- const scvdId = raw.scvd_id;
72
- const docId = raw.doc_id;
73
- const title = raw.title;
74
- const description = raw.description_md;
75
- const severity = raw.severity;
76
- const repoUrl = repoRaw.url;
71
+ const scvdId = raw.scvd_id
72
+ const docId = raw.doc_id
73
+ const title = raw.title
74
+ const description = raw.description_md
75
+ const severity = raw.severity
76
+ const repoUrl = repoRaw.url
77
77
 
78
78
  if (
79
79
  typeof scvdId !== "string" ||
@@ -82,7 +82,7 @@ function parseFinding(raw: unknown): ScvdFinding | null {
82
82
  typeof description !== "string" ||
83
83
  typeof repoUrl !== "string"
84
84
  ) {
85
- return null;
85
+ return null
86
86
  }
87
87
 
88
88
  if (
@@ -92,7 +92,7 @@ function parseFinding(raw: unknown): ScvdFinding | null {
92
92
  severity !== "Low" &&
93
93
  severity !== "Informational"
94
94
  ) {
95
- return null;
95
+ return null
96
96
  }
97
97
 
98
98
  return {
@@ -117,153 +117,146 @@ function parseFinding(raw: unknown): ScvdFinding | null {
117
117
  : undefined,
118
118
  poc_md: typeof sectionsRaw.poc_md === "string" ? sectionsRaw.poc_md : undefined,
119
119
  },
120
- };
120
+ }
121
121
  }
122
122
 
123
123
  function parseFindings(raw: unknown): ScvdFinding[] {
124
124
  if (!Array.isArray(raw)) {
125
125
  if (isRecord(raw) && Array.isArray(raw.data)) {
126
- return raw.data.map(parseFinding).filter((value): value is ScvdFinding => value !== null);
126
+ return raw.data.map(parseFinding).filter((value): value is ScvdFinding => value !== null)
127
127
  }
128
- return [];
128
+ return []
129
129
  }
130
130
 
131
- return raw.map(parseFinding).filter((value): value is ScvdFinding => value !== null);
131
+ return raw.map(parseFinding).filter((value): value is ScvdFinding => value !== null)
132
132
  }
133
133
 
134
134
  function parseStats(raw: unknown): ScvdStats {
135
135
  if (!isRecord(raw)) {
136
- throw new Error("Invalid SCVD stats response payload");
136
+ throw new Error("Invalid SCVD stats response payload")
137
137
  }
138
138
 
139
- const total = raw.total;
140
- const lastUpdated = raw.last_updated;
139
+ const total = raw.total
140
+ const lastUpdated = raw.last_updated
141
141
 
142
142
  if (typeof total !== "number" || typeof lastUpdated !== "string") {
143
- throw new Error("Invalid SCVD stats fields in response");
143
+ throw new Error("Invalid SCVD stats fields in response")
144
144
  }
145
145
 
146
146
  return {
147
147
  total,
148
148
  by_severity: toNumberRecord(raw.by_severity),
149
149
  last_updated: lastUpdated,
150
- };
150
+ }
151
151
  }
152
152
 
153
153
  export class ScvdNetworkError extends Error {
154
- override readonly name = "ScvdNetworkError" as const;
155
-
156
- constructor(message: string) {
157
- super(message);
158
- }
154
+ override readonly name = "ScvdNetworkError" as const
159
155
  }
160
156
 
161
157
  export class ScvdApiError extends Error {
162
- override readonly name = "ScvdApiError" as const;
163
- readonly httpStatus: number;
158
+ override readonly name = "ScvdApiError" as const
159
+ readonly httpStatus: number
164
160
 
165
161
  constructor(httpStatus: number, message?: string) {
166
- super(message ?? `SCVD API error: HTTP ${httpStatus}`);
167
- this.httpStatus = httpStatus;
162
+ super(message ?? `SCVD API error: HTTP ${httpStatus}`)
163
+ this.httpStatus = httpStatus
168
164
  }
169
165
  }
170
166
 
171
167
  export class ScvdClient {
172
- private readonly baseUrl: string;
173
- private readonly signal?: AbortSignal;
168
+ private readonly baseUrl: string
169
+ private readonly signal?: AbortSignal
174
170
 
175
171
  constructor(apiUrl: string, signal?: AbortSignal) {
176
- this.baseUrl = apiUrl.replace(/\/$/, "");
177
- this.signal = signal;
172
+ this.baseUrl = apiUrl.replace(/\/$/, "")
173
+ this.signal = signal
178
174
  }
179
175
 
180
176
  async fetchStats(): Promise<ScvdStats> {
181
- const url = `${this.baseUrl}/stats`;
177
+ const url = `${this.baseUrl}/stats`
182
178
 
183
- let response: Response;
179
+ let response: Response
184
180
  try {
185
- response = await fetch(url, { signal: this.signal });
181
+ response = await fetch(url, { signal: this.signal })
186
182
  } catch (error) {
187
- const message = error instanceof Error ? error.message : "unknown network error";
188
- throw new ScvdNetworkError(`Failed to fetch SCVD stats from ${url}: ${message}`);
183
+ const message = error instanceof Error ? error.message : "unknown network error"
184
+ throw new ScvdNetworkError(`Failed to fetch SCVD stats from ${url}: ${message}`)
189
185
  }
190
186
 
191
187
  if (!response.ok) {
192
188
  throw new ScvdApiError(
193
189
  response.status,
194
- `Failed to fetch SCVD stats from ${url}: HTTP ${response.status}`
195
- );
190
+ `Failed to fetch SCVD stats from ${url}: HTTP ${response.status}`,
191
+ )
196
192
  }
197
193
 
198
- const body = (await response.json()) as unknown;
199
- return parseStats(body);
194
+ const body = (await response.json()) as unknown
195
+ return parseStats(body)
200
196
  }
201
197
 
202
198
  async fetchFindings(params: {
203
- severity?: string;
204
- limit?: number;
205
- offset?: number;
199
+ severity?: string
200
+ limit?: number
201
+ offset?: number
206
202
  }): Promise<ScvdFinding[]> {
207
- const searchParams = new URLSearchParams();
203
+ const searchParams = new URLSearchParams()
208
204
 
209
205
  if (params.severity) {
210
- searchParams.set("severity", params.severity);
206
+ searchParams.set("severity", params.severity)
211
207
  }
212
208
  if (typeof params.limit === "number") {
213
- searchParams.set("limit", String(params.limit));
209
+ searchParams.set("limit", String(params.limit))
214
210
  }
215
211
  if (typeof params.offset === "number") {
216
- searchParams.set("offset", String(params.offset));
212
+ searchParams.set("offset", String(params.offset))
217
213
  }
218
214
 
219
- const query = searchParams.toString();
220
- const url = `${this.baseUrl}/findings${query.length > 0 ? `?${query}` : ""}`;
215
+ const query = searchParams.toString()
216
+ const url = `${this.baseUrl}/findings${query.length > 0 ? `?${query}` : ""}`
221
217
 
222
- let response: Response;
218
+ let response: Response
223
219
  try {
224
- response = await fetch(url, { signal: this.signal });
220
+ response = await fetch(url, { signal: this.signal })
225
221
  } catch (error) {
226
- const message = error instanceof Error ? error.message : "unknown network error";
227
- throw new ScvdNetworkError(`Failed to fetch SCVD findings from ${url}: ${message}`);
222
+ const message = error instanceof Error ? error.message : "unknown network error"
223
+ throw new ScvdNetworkError(`Failed to fetch SCVD findings from ${url}: ${message}`)
228
224
  }
229
225
 
230
226
  if (!response.ok) {
231
- throw new ScvdApiError(
232
- response.status,
233
- `SCVD API error: HTTP ${response.status} for ${url}`
234
- );
227
+ throw new ScvdApiError(response.status, `SCVD API error: HTTP ${response.status} for ${url}`)
235
228
  }
236
229
 
237
- const body = (await response.json()) as unknown;
238
- return parseFindings(body);
230
+ const body = (await response.json()) as unknown
231
+ return parseFindings(body)
239
232
  }
240
233
 
241
234
  async fetchAllFindings(onProgress?: (count: number) => void): Promise<ScvdFinding[]> {
242
- const results: ScvdFinding[] = [];
243
- let offset = 0;
235
+ const results: ScvdFinding[] = []
236
+ let offset = 0
244
237
 
245
238
  while (true) {
246
239
  const page = await this.fetchFindings({
247
240
  limit: DEFAULT_PAGE_SIZE,
248
241
  offset,
249
- });
242
+ })
250
243
 
251
244
  if (page.length === 0) {
252
- break;
245
+ break
253
246
  }
254
247
 
255
- results.push(...page);
256
- offset += page.length;
248
+ results.push(...page)
249
+ offset += page.length
257
250
 
258
251
  if (onProgress) {
259
- onProgress(results.length);
252
+ onProgress(results.length)
260
253
  }
261
254
 
262
255
  if (page.length < DEFAULT_PAGE_SIZE) {
263
- break;
256
+ break
264
257
  }
265
258
  }
266
259
 
267
- return results;
260
+ return results
268
261
  }
269
262
  }
@@ -1,38 +1,38 @@
1
1
  export type SyncError = {
2
- status: "error";
3
- success: false;
4
- reason: "network" | "api" | "parse";
5
- message: string;
6
- error: string;
7
- httpStatus?: number;
8
- newFindings: 0;
9
- totalIndexed: 0;
10
- lastSync: string;
11
- attempts?: number;
12
- };
2
+ status: "error"
3
+ success: false
4
+ reason: "network" | "api" | "parse"
5
+ message: string
6
+ error: string
7
+ httpStatus?: number
8
+ newFindings: 0
9
+ totalIndexed: 0
10
+ lastSync: string
11
+ attempts?: number
12
+ }
13
13
 
14
14
  export type SyncSuccess = {
15
- status: "success";
16
- success: true;
17
- newFindings: number;
18
- totalIndexed: number;
19
- lastSync: string;
20
- error?: undefined;
21
- attempts?: number;
22
- };
15
+ status: "success"
16
+ success: true
17
+ newFindings: number
18
+ totalIndexed: number
19
+ lastSync: string
20
+ error?: undefined
21
+ attempts?: number
22
+ }
23
23
 
24
24
  export type SyncStale = {
25
- status: "stale";
26
- success: false;
27
- newFindings: 0;
28
- totalIndexed: 0;
29
- lastSync: string;
30
- error?: undefined;
31
- daysSinceSync: number;
32
- attempts?: number;
33
- };
25
+ status: "stale"
26
+ success: false
27
+ newFindings: 0
28
+ totalIndexed: 0
29
+ lastSync: string
30
+ error?: undefined
31
+ daysSinceSync: number
32
+ attempts?: number
33
+ }
34
34
 
35
- export type SyncOutcome = SyncSuccess | SyncError | SyncStale;
35
+ export type SyncOutcome = SyncSuccess | SyncError | SyncStale
36
36
 
37
37
  export function createNetworkError(message: string): SyncError {
38
38
  return {
@@ -44,7 +44,7 @@ export function createNetworkError(message: string): SyncError {
44
44
  newFindings: 0,
45
45
  totalIndexed: 0,
46
46
  lastSync: new Date().toISOString(),
47
- };
47
+ }
48
48
  }
49
49
 
50
50
  export function createApiError(httpStatus: number, message: string): SyncError {
@@ -58,7 +58,7 @@ export function createApiError(httpStatus: number, message: string): SyncError {
58
58
  newFindings: 0,
59
59
  totalIndexed: 0,
60
60
  lastSync: new Date().toISOString(),
61
- };
61
+ }
62
62
  }
63
63
 
64
64
  export function createParseError(message: string): SyncError {
@@ -71,19 +71,19 @@ export function createParseError(message: string): SyncError {
71
71
  newFindings: 0,
72
72
  totalIndexed: 0,
73
73
  lastSync: new Date().toISOString(),
74
- };
74
+ }
75
75
  }
76
76
 
77
77
  export function createSyncSuccess(
78
- data: Omit<SyncSuccess, "status" | "success" | "error"> & { attempts?: number }
78
+ data: Omit<SyncSuccess, "status" | "success" | "error"> & { attempts?: number },
79
79
  ): SyncSuccess {
80
80
  return {
81
81
  status: "success",
82
82
  success: true,
83
83
  ...data,
84
- };
84
+ }
85
85
  }
86
86
 
87
87
  export function isRetryableError(outcome: SyncOutcome): boolean {
88
- return outcome.status === "error" && outcome.reason === "network";
88
+ return outcome.status === "error" && outcome.reason === "network"
89
89
  }