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,50 +1,50 @@
1
- import type { ScvdFinding } from "./scvd-client";
1
+ import type { ScvdFinding } from "./scvd-client"
2
2
 
3
3
  export interface ScvdIndexEntry {
4
- id: string;
5
- title: string;
6
- severity: string;
7
- swc: string[];
8
- cwe: string[];
9
- keywords: string[];
10
- repoUrl: string;
4
+ id: string
5
+ title: string
6
+ severity: string
7
+ swc: string[]
8
+ cwe: string[]
9
+ keywords: string[]
10
+ repoUrl: string
11
11
  }
12
12
 
13
13
  export interface ScvdIndexMetadata {
14
- lastSuccess: string | null;
15
- lastAttempt: string | null;
16
- errorCount: number;
17
- lastError: string | null;
18
- lastErrorReason: string | null;
14
+ lastSuccess: string | null
15
+ lastAttempt: string | null
16
+ errorCount: number
17
+ lastError: string | null
18
+ lastErrorReason: string | null
19
19
  }
20
20
 
21
21
  export interface ScvdIndex {
22
- version: number;
23
- lastSync: string;
24
- totalFindings: number;
25
- entries: ScvdIndexEntry[];
26
- metadata?: ScvdIndexMetadata;
22
+ version: number
23
+ lastSync: string
24
+ totalFindings: number
25
+ entries: ScvdIndexEntry[]
26
+ metadata?: ScvdIndexMetadata
27
27
  }
28
28
 
29
- const INDEX_VERSION = 1;
30
- const DEFAULT_LIMIT = 10;
31
- let syncInProgress = false;
29
+ const INDEX_VERSION = 1
30
+ const DEFAULT_LIMIT = 10
31
+ let syncInProgress = false
32
32
 
33
33
  export function acquireSyncLock(): boolean {
34
34
  if (syncInProgress) {
35
- return false;
35
+ return false
36
36
  }
37
37
 
38
- syncInProgress = true;
39
- return true;
38
+ syncInProgress = true
39
+ return true
40
40
  }
41
41
 
42
42
  export function releaseSyncLock(): void {
43
- syncInProgress = false;
43
+ syncInProgress = false
44
44
  }
45
45
 
46
46
  export function isSyncLocked(): boolean {
47
- return syncInProgress;
47
+ return syncInProgress
48
48
  }
49
49
 
50
50
  function normalizeKeywordInput(value: string): string[] {
@@ -52,15 +52,15 @@ function normalizeKeywordInput(value: string): string[] {
52
52
  .toLowerCase()
53
53
  .split(/[^a-z0-9]+/g)
54
54
  .map((word) => word.trim())
55
- .filter((word) => word.length > 1);
55
+ .filter((word) => word.length > 1)
56
56
  }
57
57
 
58
58
  function uniqueWords(words: string[]): string[] {
59
- return Array.from(new Set(words));
59
+ return Array.from(new Set(words))
60
60
  }
61
61
 
62
62
  function findingToEntry(finding: ScvdFinding): ScvdIndexEntry {
63
- const keywordSource = `${finding.title} ${finding.description_md}`;
63
+ const keywordSource = `${finding.title} ${finding.description_md}`
64
64
 
65
65
  return {
66
66
  id: finding.scvd_id,
@@ -70,86 +70,84 @@ function findingToEntry(finding: ScvdFinding): ScvdIndexEntry {
70
70
  cwe: finding.taxonomy.cwe,
71
71
  keywords: uniqueWords(normalizeKeywordInput(keywordSource)),
72
72
  repoUrl: finding.repo.url,
73
- };
73
+ }
74
74
  }
75
75
 
76
76
  export function buildIndex(findings: ScvdFinding[]): ScvdIndex {
77
- const now = new Date().toISOString();
78
- const entries = findings.map(findingToEntry);
77
+ const now = new Date().toISOString()
78
+ const entries = findings.map(findingToEntry)
79
79
 
80
80
  return {
81
81
  version: INDEX_VERSION,
82
82
  lastSync: now,
83
83
  totalFindings: entries.length,
84
84
  entries,
85
- };
85
+ }
86
86
  }
87
87
 
88
88
  export function searchIndex(
89
89
  index: ScvdIndex,
90
90
  query: {
91
- swc?: string;
92
- severity?: string;
93
- keyword?: string;
94
- limit?: number;
95
- }
91
+ swc?: string
92
+ severity?: string
93
+ keyword?: string
94
+ limit?: number
95
+ },
96
96
  ): ScvdIndexEntry[] {
97
- const normalizedKeyword = query.keyword?.toLowerCase().trim();
98
- const limit = query.limit ?? DEFAULT_LIMIT;
97
+ const normalizedKeyword = query.keyword?.toLowerCase().trim()
98
+ const limit = query.limit ?? DEFAULT_LIMIT
99
99
 
100
100
  const filtered = index.entries.filter((entry) => {
101
101
  if (query.swc && !entry.swc.includes(query.swc)) {
102
- return false;
102
+ return false
103
103
  }
104
104
 
105
105
  if (query.severity && entry.severity !== query.severity) {
106
- return false;
106
+ return false
107
107
  }
108
108
 
109
109
  if (normalizedKeyword && normalizedKeyword.length > 0) {
110
- const matchesKeyword = entry.keywords.some((keyword) =>
111
- keyword.includes(normalizedKeyword)
112
- );
110
+ const matchesKeyword = entry.keywords.some((keyword) => keyword.includes(normalizedKeyword))
113
111
 
114
112
  if (!matchesKeyword) {
115
- return false;
113
+ return false
116
114
  }
117
115
  }
118
116
 
119
- return true;
120
- });
117
+ return true
118
+ })
121
119
 
122
- return filtered.slice(0, limit);
120
+ return filtered.slice(0, limit)
123
121
  }
124
122
 
125
123
  export async function saveIndex(index: ScvdIndex, filePath: string): Promise<void> {
126
- const tmpPath = `${filePath}.tmp.${Date.now()}`;
127
- await Bun.write(tmpPath, JSON.stringify(index, null, 2));
128
- const { renameSync } = await import("node:fs");
129
- renameSync(tmpPath, filePath);
124
+ const tmpPath = `${filePath}.tmp.${Date.now()}`
125
+ const { renameSync } = await import("node:fs")
126
+ await Bun.write(tmpPath, JSON.stringify(index, null, 2))
127
+ renameSync(tmpPath, filePath)
130
128
  }
131
129
 
132
130
  function isRecord(value: unknown): value is Record<string, unknown> {
133
- return typeof value === "object" && value !== null;
131
+ return typeof value === "object" && value !== null
134
132
  }
135
133
 
136
134
  function parseStringArray(value: unknown): string[] {
137
135
  if (!Array.isArray(value)) {
138
- return [];
136
+ return []
139
137
  }
140
138
 
141
- return value.filter((item): item is string => typeof item === "string");
139
+ return value.filter((item): item is string => typeof item === "string")
142
140
  }
143
141
 
144
142
  function parseEntry(value: unknown): ScvdIndexEntry | null {
145
143
  if (!isRecord(value)) {
146
- return null;
144
+ return null
147
145
  }
148
146
 
149
- const id = value.id;
150
- const title = value.title;
151
- const severity = value.severity;
152
- const repoUrl = value.repoUrl;
147
+ const id = value.id
148
+ const title = value.title
149
+ const severity = value.severity
150
+ const repoUrl = value.repoUrl
153
151
 
154
152
  if (
155
153
  typeof id !== "string" ||
@@ -157,7 +155,7 @@ function parseEntry(value: unknown): ScvdIndexEntry | null {
157
155
  typeof severity !== "string" ||
158
156
  typeof repoUrl !== "string"
159
157
  ) {
160
- return null;
158
+ return null
161
159
  }
162
160
 
163
161
  return {
@@ -168,11 +166,11 @@ function parseEntry(value: unknown): ScvdIndexEntry | null {
168
166
  cwe: parseStringArray(value.cwe),
169
167
  keywords: parseStringArray(value.keywords),
170
168
  repoUrl,
171
- };
169
+ }
172
170
  }
173
171
 
174
172
  function parseNullableString(value: unknown): string | null {
175
- return typeof value === "string" ? value : null;
173
+ return typeof value === "string" ? value : null
176
174
  }
177
175
 
178
176
  function parseMetadata(raw: Record<string, unknown>): ScvdIndexMetadata {
@@ -182,27 +180,27 @@ function parseMetadata(raw: Record<string, unknown>): ScvdIndexMetadata {
182
180
  errorCount: typeof raw.errorCount === "number" ? raw.errorCount : 0,
183
181
  lastError: parseNullableString(raw.lastError),
184
182
  lastErrorReason: parseNullableString(raw.lastErrorReason),
185
- };
183
+ }
186
184
  }
187
185
 
188
186
  export async function loadIndex(filePath: string): Promise<ScvdIndex | null> {
189
- const file = Bun.file(filePath);
190
- const exists = await file.exists();
187
+ const file = Bun.file(filePath)
188
+ const exists = await file.exists()
191
189
 
192
190
  if (!exists) {
193
- return null;
191
+ return null
194
192
  }
195
193
 
196
- const raw = (await file.json()) as unknown;
194
+ const raw = (await file.json()) as unknown
197
195
 
198
196
  if (!isRecord(raw)) {
199
- return null;
197
+ return null
200
198
  }
201
199
 
202
- const version = raw.version;
203
- const lastSync = raw.lastSync;
204
- const totalFindings = raw.totalFindings;
205
- const rawEntries = raw.entries;
200
+ const version = raw.version
201
+ const lastSync = raw.lastSync
202
+ const totalFindings = raw.totalFindings
203
+ const rawEntries = raw.entries
206
204
 
207
205
  if (
208
206
  typeof version !== "number" ||
@@ -210,24 +208,24 @@ export async function loadIndex(filePath: string): Promise<ScvdIndex | null> {
210
208
  typeof totalFindings !== "number" ||
211
209
  !Array.isArray(rawEntries)
212
210
  ) {
213
- return null;
211
+ return null
214
212
  }
215
213
 
216
214
  const entries = rawEntries
217
215
  .map(parseEntry)
218
- .filter((entry): entry is ScvdIndexEntry => entry !== null);
216
+ .filter((entry): entry is ScvdIndexEntry => entry !== null)
219
217
 
220
218
  const index: ScvdIndex = {
221
219
  version,
222
220
  lastSync,
223
221
  totalFindings,
224
222
  entries,
225
- };
223
+ }
226
224
 
227
- const rawMetadata = raw.metadata;
225
+ const rawMetadata = raw.metadata
228
226
  if (isRecord(rawMetadata)) {
229
- index.metadata = parseMetadata(rawMetadata);
227
+ index.metadata = parseMetadata(rawMetadata)
230
228
  }
231
229
 
232
- return index;
230
+ return index
233
231
  }
@@ -1,6 +1,7 @@
1
- import type { ScvdClient } from "./scvd-client";
2
- import { ScvdApiError, ScvdNetworkError } from "./scvd-client";
3
- import { createLogger } from "../shared/logger";
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"
4
5
  import {
5
6
  createApiError,
6
7
  createNetworkError,
@@ -9,64 +10,60 @@ import {
9
10
  isRetryableError,
10
11
  type SyncError,
11
12
  type SyncOutcome,
12
- } from "./scvd-errors";
13
+ } from "./scvd-errors"
13
14
  import {
14
15
  acquireSyncLock,
15
16
  buildIndex,
16
17
  loadIndex,
17
18
  releaseSyncLock,
18
- saveIndex,
19
19
  type ScvdIndex,
20
20
  type ScvdIndexMetadata,
21
- } from "./scvd-index";
22
- import { withRetry } from "./retry";
21
+ saveIndex,
22
+ } from "./scvd-index"
23
23
 
24
- export type SyncResult = SyncOutcome;
24
+ export type SyncResult = SyncOutcome
25
25
 
26
- const RETRY_MAX_ATTEMPTS = 3;
27
- const RETRY_BASE_DELAY_MS = 1000;
26
+ const RETRY_MAX_ATTEMPTS = 3
27
+ const RETRY_BASE_DELAY_MS = 1000
28
28
 
29
29
  function buildErrorResult(error: unknown): SyncError {
30
- const message = error instanceof Error ? error.message : "Unknown sync error";
30
+ const message = error instanceof Error ? error.message : "Unknown sync error"
31
31
 
32
32
  if (error instanceof ScvdNetworkError) {
33
- return createNetworkError(message);
33
+ return createNetworkError(message)
34
34
  }
35
35
  if (error instanceof ScvdApiError) {
36
- return createApiError(error.httpStatus, message);
36
+ return createApiError(error.httpStatus, message)
37
37
  }
38
- return createParseError(message);
38
+ return createParseError(message)
39
39
  }
40
40
 
41
41
  function shouldRetrySyncError(error: unknown): boolean {
42
42
  if (!(error instanceof ScvdNetworkError)) {
43
- return false;
43
+ return false
44
44
  }
45
45
 
46
- return isRetryableError(buildErrorResult(error));
46
+ return isRetryableError(buildErrorResult(error))
47
47
  }
48
48
 
49
49
  function errorReasonFromResult(result: SyncError): string {
50
- return result.reason;
50
+ return result.reason
51
51
  }
52
52
 
53
- async function persistErrorMetadata(
54
- indexPath: string,
55
- errorResult: SyncError
56
- ): Promise<void> {
57
- const existing = await loadIndex(indexPath);
58
- if (!existing) return;
53
+ async function persistErrorMetadata(indexPath: string, errorResult: SyncError): Promise<void> {
54
+ const existing = await loadIndex(indexPath)
55
+ if (!existing) return
59
56
 
60
- const now = new Date().toISOString();
61
- const prevMetadata = existing.metadata;
57
+ const now = new Date().toISOString()
58
+ const prevMetadata = existing.metadata
62
59
  existing.metadata = {
63
60
  lastSuccess: prevMetadata?.lastSuccess ?? null,
64
61
  lastAttempt: now,
65
62
  errorCount: (prevMetadata?.errorCount ?? 0) + 1,
66
63
  lastError: errorResult.message,
67
64
  lastErrorReason: errorReasonFromResult(errorResult),
68
- };
69
- await saveIndex(existing, indexPath);
65
+ }
66
+ await saveIndex(existing, indexPath)
70
67
  }
71
68
 
72
69
  async function syncAllUnlocked(client: ScvdClient, indexPath: string): Promise<SyncResult> {
@@ -74,81 +71,83 @@ async function syncAllUnlocked(client: ScvdClient, indexPath: string): Promise<S
74
71
  maxAttempts: RETRY_MAX_ATTEMPTS,
75
72
  baseDelayMs: RETRY_BASE_DELAY_MS,
76
73
  shouldRetry: shouldRetrySyncError,
77
- });
74
+ })
78
75
 
79
76
  if (!fetchResult.success) {
80
- const errorResult = buildErrorResult(fetchResult.error);
81
- errorResult.attempts = fetchResult.attempts;
82
- await persistErrorMetadata(indexPath, errorResult);
83
- return errorResult;
77
+ const errorResult = buildErrorResult(fetchResult.error)
78
+ errorResult.attempts = fetchResult.attempts
79
+ await persistErrorMetadata(indexPath, errorResult)
80
+ return errorResult
84
81
  }
85
82
 
86
83
  if (fetchResult.value === undefined) {
87
- const errorResult = createParseError("SCVD sync returned no findings payload");
88
- errorResult.attempts = fetchResult.attempts;
89
- await persistErrorMetadata(indexPath, errorResult);
90
- return errorResult;
84
+ const errorResult = createParseError("SCVD sync returned no findings payload")
85
+ errorResult.attempts = fetchResult.attempts
86
+ await persistErrorMetadata(indexPath, errorResult)
87
+ return errorResult
91
88
  }
92
89
 
93
- const findings = fetchResult.value;
94
- const index = buildIndex(findings);
95
- const now = new Date().toISOString();
90
+ const findings = fetchResult.value
91
+ const index = buildIndex(findings)
92
+ const now = new Date().toISOString()
96
93
  index.metadata = {
97
94
  lastSuccess: now,
98
95
  lastAttempt: now,
99
96
  errorCount: 0,
100
97
  lastError: null,
101
98
  lastErrorReason: null,
102
- };
103
- await saveIndex(index, indexPath);
99
+ }
100
+ await saveIndex(index, indexPath)
104
101
 
105
102
  return createSyncSuccess({
106
103
  newFindings: findings.length,
107
104
  totalIndexed: index.totalFindings,
108
105
  lastSync: index.lastSync,
109
106
  attempts: fetchResult.attempts,
110
- });
107
+ })
111
108
  }
112
109
 
113
110
  export async function syncAll(client: ScvdClient, indexPath: string): Promise<SyncResult> {
114
- const logger = createLogger();
111
+ const logger = createLogger()
115
112
 
116
113
  if (!acquireSyncLock()) {
117
- return createParseError("Sync already in progress");
114
+ return createParseError("Sync already in progress")
118
115
  }
119
116
 
120
- logger.debug("[sync] starting", "source=scvd mode=full");
117
+ logger.debug("[sync] starting", "source=scvd mode=full")
121
118
 
122
119
  try {
123
- const result = await syncAllUnlocked(client, indexPath);
120
+ const result = await syncAllUnlocked(client, indexPath)
124
121
  if (result.success) {
125
- logger.debug("[sync] complete", `source=scvd newFindings=${result.newFindings} totalIndexed=${result.totalIndexed}`);
122
+ logger.debug(
123
+ "[sync] complete",
124
+ `source=scvd newFindings=${result.newFindings} totalIndexed=${result.totalIndexed}`,
125
+ )
126
126
  } else {
127
- const reason = result.status === "error" ? result.reason : result.status;
128
- logger.debug("[sync] failed", `source=scvd reason=${reason}`);
127
+ const reason = result.status === "error" ? result.reason : result.status
128
+ logger.debug("[sync] failed", `source=scvd reason=${reason}`)
129
129
  }
130
- return result;
130
+ return result
131
131
  } catch (error) {
132
- const errorResult = buildErrorResult(error);
133
- logger.debug("[sync] failed", `source=scvd reason=${errorResult.reason}`);
134
- await persistErrorMetadata(indexPath, errorResult).catch(() => {});
135
- return errorResult;
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
136
138
  } finally {
137
- releaseSyncLock();
139
+ releaseSyncLock()
138
140
  }
139
141
  }
140
142
 
141
- export async function syncIncremental(
142
- client: ScvdClient,
143
- indexPath: string
144
- ): Promise<SyncResult> {
145
- const logger = createLogger();
143
+ export async function syncIncremental(client: ScvdClient, indexPath: string): Promise<SyncResult> {
144
+ const logger = createLogger()
146
145
 
147
146
  if (!acquireSyncLock()) {
148
- return createParseError("Sync already in progress");
147
+ return createParseError("Sync already in progress")
149
148
  }
150
149
 
151
- logger.debug("[sync] starting", "source=scvd mode=incremental");
150
+ logger.debug("[sync] starting", "source=scvd mode=incremental")
152
151
 
153
152
  try {
154
153
  const [statsResult, existingIndex] = await Promise.all([
@@ -158,65 +157,71 @@ export async function syncIncremental(
158
157
  shouldRetry: shouldRetrySyncError,
159
158
  }),
160
159
  loadIndex(indexPath),
161
- ]);
160
+ ])
162
161
 
163
162
  if (!statsResult.success) {
164
- const errorResult = buildErrorResult(statsResult.error);
165
- errorResult.attempts = statsResult.attempts;
166
- await persistErrorMetadata(indexPath, errorResult).catch(() => {});
167
- return errorResult;
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
168
169
  }
169
170
 
170
171
  if (statsResult.value === undefined) {
171
- const errorResult = createParseError("SCVD sync returned no stats payload");
172
- errorResult.attempts = statsResult.attempts;
173
- await persistErrorMetadata(indexPath, errorResult).catch(() => {});
174
- return errorResult;
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
175
178
  }
176
179
 
177
- const stats = statsResult.value;
180
+ const stats = statsResult.value
178
181
 
179
182
  if (existingIndex && existingIndex.totalFindings === stats.total) {
180
183
  return createSyncSuccess({
181
184
  newFindings: 0,
182
185
  totalIndexed: existingIndex.totalFindings,
183
186
  lastSync: existingIndex.lastSync,
184
- });
187
+ })
185
188
  }
186
189
 
187
- return await syncAllUnlocked(client, indexPath);
190
+ return await syncAllUnlocked(client, indexPath)
188
191
  } catch (error) {
189
- const errorResult = buildErrorResult(error);
190
- await persistErrorMetadata(indexPath, errorResult).catch(() => {});
191
- return errorResult;
192
+ const errorResult = buildErrorResult(error)
193
+ await persistErrorMetadata(indexPath, errorResult).catch(() => {
194
+ logger.debug("Failed to persist sync error metadata")
195
+ })
196
+ return errorResult
192
197
  } finally {
193
- releaseSyncLock();
198
+ releaseSyncLock()
194
199
  }
195
200
  }
196
- const STALE_THRESHOLD_DAYS = 7;
201
+ const STALE_THRESHOLD_DAYS = 7
197
202
 
198
203
  export function isSyncStale(
199
204
  index: ScvdIndex | null,
200
- thresholdDays: number = STALE_THRESHOLD_DAYS
205
+ thresholdDays: number = STALE_THRESHOLD_DAYS,
201
206
  ): boolean {
202
- if (!index || !index.lastSync) return true;
203
- const lastSyncDate = new Date(index.lastSync);
204
- const now = new Date();
205
- const diffMs = now.getTime() - lastSyncDate.getTime();
206
- const diffDays = diffMs / (1000 * 60 * 60 * 24);
207
- return diffDays > thresholdDays;
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
208
213
  }
209
214
 
210
215
  export async function getSyncStatus(indexPath: string): Promise<{
211
- lastSync: string | null;
212
- totalFindings: number;
213
- healthy: boolean;
214
- stale: boolean;
215
- metadata: ScvdIndexMetadata | null;
216
- hint?: string;
216
+ lastSync: string | null
217
+ totalFindings: number
218
+ healthy: boolean
219
+ stale: boolean
220
+ metadata: ScvdIndexMetadata | null
221
+ hint?: string
217
222
  }> {
218
- const logger = createLogger();
219
- const index = await loadIndex(indexPath);
223
+ const logger = createLogger()
224
+ const index = await loadIndex(indexPath)
220
225
 
221
226
  if (!index) {
222
227
  return {
@@ -226,15 +231,15 @@ export async function getSyncStatus(indexPath: string): Promise<{
226
231
  stale: true,
227
232
  metadata: null,
228
233
  hint: "SCVD data is missing. Run argus_sync_knowledge to populate.",
229
- };
234
+ }
230
235
  }
231
236
 
232
- const stale = isSyncStale(index);
237
+ const stale = isSyncStale(index)
233
238
 
234
239
  if (stale) {
235
- const lastSyncDate = new Date(index.lastSync);
236
- const daysSince = Math.floor((Date.now() - lastSyncDate.getTime()) / (1000 * 60 * 60 * 24));
237
- logger.debug("[sync] stale", `source=scvd daysSince=${daysSince}`);
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}`)
238
243
 
239
244
  return {
240
245
  lastSync: index.lastSync,
@@ -243,7 +248,7 @@ export async function getSyncStatus(indexPath: string): Promise<{
243
248
  stale: true,
244
249
  metadata: index.metadata ?? null,
245
250
  hint: "SCVD data is stale. Run argus_sync_knowledge to update.",
246
- };
251
+ }
247
252
  }
248
253
 
249
254
  return {
@@ -252,5 +257,5 @@ export async function getSyncStatus(indexPath: string): Promise<{
252
257
  healthy: true,
253
258
  stale: false,
254
259
  metadata: index.metadata ?? null,
255
- };
260
+ }
256
261
  }
@@ -1 +1 @@
1
- export type { BackgroundManager, AuditStateManager, Managers } from "./types";
1
+ export type { AuditStateManager, BackgroundManager, Managers } from "./types"