solidity-argus 0.1.8 → 0.2.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.
- package/README.md +161 -1
- package/package.json +5 -2
- package/skills/README.md +63 -0
- package/skills/checklists/cyfrin-defi-core/SKILL.md +3 -0
- package/skills/manifests/cyfrin.json +16 -0
- package/skills/manifests/defifofum.json +25 -0
- package/skills/manifests/kadenzipfel.json +48 -0
- package/skills/manifests/scvd.json +9 -0
- package/skills/manifests/smartbugs.json +11 -0
- package/skills/manifests/solodit.json +9 -0
- package/skills/manifests/sunweb3sec.json +11 -0
- package/skills/manifests/trailofbits.json +9 -0
- package/skills/methodology/audit-workflow/SKILL.md +3 -0
- package/skills/patterns/access-control.yaml +31 -0
- package/skills/patterns/erc4626.yaml +29 -0
- package/skills/patterns/flash-loan.yaml +20 -0
- package/skills/patterns/oracle.yaml +30 -0
- package/skills/patterns/proxy.yaml +30 -0
- package/skills/patterns/reentrancy.yaml +30 -0
- package/skills/patterns/signature.yaml +31 -0
- package/skills/protocol-patterns/amm-dex/SKILL.md +3 -0
- package/skills/references/exploit-reference/SKILL.md +3 -0
- package/skills/vulnerability-patterns/access-control/SKILL.md +13 -0
- package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +6 -0
- package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +6 -0
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +13 -1
- package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +12 -0
- package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +13 -0
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +10 -1
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +13 -0
- package/skills/vulnerability-patterns/signature-malleability/SKILL.md +9 -0
- package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +11 -0
- package/src/agents/argus-prompt.ts +4 -4
- package/src/agents/pythia-prompt.ts +4 -4
- package/src/agents/scribe-prompt.ts +3 -3
- package/src/agents/sentinel-prompt.ts +4 -4
- package/src/cli/cli-output.ts +16 -0
- package/src/cli/cli-program.ts +9 -5
- package/src/cli/commands/doctor.ts +274 -16
- package/src/cli/commands/init.ts +5 -5
- package/src/cli/commands/install.ts +5 -5
- package/src/cli/commands/lint-skills.ts +114 -0
- package/src/cli/tui-prompts.ts +4 -2
- package/src/config/schema.ts +2 -0
- package/src/create-hooks.ts +99 -14
- package/src/create-tools.ts +2 -0
- package/src/features/error-recovery/tool-error-recovery.ts +74 -19
- package/src/features/persistent-state/audit-state-manager.ts +36 -13
- package/src/hooks/agent-tracker.ts +53 -0
- package/src/hooks/compaction-hook.ts +46 -37
- package/src/hooks/config-handler.ts +3 -0
- package/src/hooks/context-budget.ts +45 -0
- package/src/hooks/event-hook.ts +5 -4
- package/src/hooks/knowledge-sync-hook.ts +2 -1
- package/src/hooks/recon-context-builder.ts +66 -0
- package/src/hooks/safe-create-hook.ts +4 -5
- package/src/hooks/system-prompt-hook.ts +128 -0
- package/src/hooks/tool-tracking-hook.ts +86 -7
- package/src/index.ts +24 -1
- package/src/knowledge/retry.ts +53 -0
- package/src/knowledge/scvd-client.ts +37 -10
- package/src/knowledge/scvd-errors.ts +89 -0
- package/src/knowledge/scvd-index.ts +53 -3
- package/src/knowledge/scvd-sync.ts +205 -34
- package/src/knowledge/source-manifest.ts +102 -0
- package/src/plugin-interface.ts +14 -1
- package/src/shared/binary-utils.ts +1 -0
- package/src/shared/logger.ts +78 -17
- package/src/skills/argus-skill-resolver.ts +226 -0
- package/src/skills/skill-schema.ts +98 -0
- package/src/state/audit-state.ts +2 -0
- package/src/state/types.ts +32 -1
- package/src/tools/argus-skill-load-tool.ts +73 -0
- package/src/tools/pattern-checker-tool.ts +56 -12
- package/src/tools/pattern-loader.ts +183 -0
- package/src/tools/pattern-schema.ts +51 -0
- package/src/tools/report-generator-tool.ts +134 -11
- package/src/tools/slither-tool.ts +61 -19
- package/src/tools/solodit-search-tool.ts +92 -14
- package/src/utils/audit-artifact-detector.ts +119 -0
- package/src/utils/dependency-scanner.ts +93 -0
- package/src/utils/project-detector.ts +128 -26
- package/src/utils/solidity-parser.ts +20 -4
- package/src/utils/solodit-health.ts +29 -0
|
@@ -1,39 +1,140 @@
|
|
|
1
1
|
import type { ScvdClient } from "./scvd-client";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
import { ScvdApiError, ScvdNetworkError } from "./scvd-client";
|
|
3
|
+
import { createLogger } from "../shared/logger";
|
|
4
|
+
import {
|
|
5
|
+
createApiError,
|
|
6
|
+
createNetworkError,
|
|
7
|
+
createParseError,
|
|
8
|
+
createSyncSuccess,
|
|
9
|
+
isRetryableError,
|
|
10
|
+
type SyncError,
|
|
11
|
+
type SyncOutcome,
|
|
12
|
+
} from "./scvd-errors";
|
|
13
|
+
import {
|
|
14
|
+
acquireSyncLock,
|
|
15
|
+
buildIndex,
|
|
16
|
+
loadIndex,
|
|
17
|
+
releaseSyncLock,
|
|
18
|
+
saveIndex,
|
|
19
|
+
type ScvdIndex,
|
|
20
|
+
type ScvdIndexMetadata,
|
|
21
|
+
} from "./scvd-index";
|
|
22
|
+
import { withRetry } from "./retry";
|
|
23
|
+
|
|
24
|
+
export type SyncResult = SyncOutcome;
|
|
11
25
|
|
|
12
|
-
|
|
26
|
+
const RETRY_MAX_ATTEMPTS = 3;
|
|
27
|
+
const RETRY_BASE_DELAY_MS = 1000;
|
|
28
|
+
|
|
29
|
+
function buildErrorResult(error: unknown): SyncError {
|
|
13
30
|
const message = error instanceof Error ? error.message : "Unknown sync error";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
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);
|
|
39
|
+
}
|
|
40
|
+
|
|
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(
|
|
54
|
+
indexPath: string,
|
|
55
|
+
errorResult: SyncError
|
|
56
|
+
): Promise<void> {
|
|
57
|
+
const existing = await loadIndex(indexPath);
|
|
58
|
+
if (!existing) return;
|
|
59
|
+
|
|
60
|
+
const now = new Date().toISOString();
|
|
61
|
+
const prevMetadata = existing.metadata;
|
|
62
|
+
existing.metadata = {
|
|
63
|
+
lastSuccess: prevMetadata?.lastSuccess ?? null,
|
|
64
|
+
lastAttempt: now,
|
|
65
|
+
errorCount: (prevMetadata?.errorCount ?? 0) + 1,
|
|
66
|
+
lastError: errorResult.message,
|
|
67
|
+
lastErrorReason: errorReasonFromResult(errorResult),
|
|
20
68
|
};
|
|
69
|
+
await saveIndex(existing, indexPath);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function syncAllUnlocked(client: ScvdClient, indexPath: string): Promise<SyncResult> {
|
|
73
|
+
const fetchResult = await withRetry(() => client.fetchAllFindings(), {
|
|
74
|
+
maxAttempts: RETRY_MAX_ATTEMPTS,
|
|
75
|
+
baseDelayMs: RETRY_BASE_DELAY_MS,
|
|
76
|
+
shouldRetry: shouldRetrySyncError,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!fetchResult.success) {
|
|
80
|
+
const errorResult = buildErrorResult(fetchResult.error);
|
|
81
|
+
errorResult.attempts = fetchResult.attempts;
|
|
82
|
+
await persistErrorMetadata(indexPath, errorResult);
|
|
83
|
+
return errorResult;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
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;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const findings = fetchResult.value;
|
|
94
|
+
const index = buildIndex(findings);
|
|
95
|
+
const now = new Date().toISOString();
|
|
96
|
+
index.metadata = {
|
|
97
|
+
lastSuccess: now,
|
|
98
|
+
lastAttempt: now,
|
|
99
|
+
errorCount: 0,
|
|
100
|
+
lastError: null,
|
|
101
|
+
lastErrorReason: null,
|
|
102
|
+
};
|
|
103
|
+
await saveIndex(index, indexPath);
|
|
104
|
+
|
|
105
|
+
return createSyncSuccess({
|
|
106
|
+
newFindings: findings.length,
|
|
107
|
+
totalIndexed: index.totalFindings,
|
|
108
|
+
lastSync: index.lastSync,
|
|
109
|
+
attempts: fetchResult.attempts,
|
|
110
|
+
});
|
|
21
111
|
}
|
|
22
112
|
|
|
23
113
|
export async function syncAll(client: ScvdClient, indexPath: string): Promise<SyncResult> {
|
|
24
|
-
|
|
25
|
-
const findings = await client.fetchAllFindings();
|
|
26
|
-
const index = buildIndex(findings);
|
|
27
|
-
await saveIndex(index, indexPath);
|
|
114
|
+
const logger = createLogger();
|
|
28
115
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
116
|
+
if (!acquireSyncLock()) {
|
|
117
|
+
return createParseError("Sync already in progress");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
logger.debug("[sync] starting", "source=scvd mode=full");
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const result = await syncAllUnlocked(client, indexPath);
|
|
124
|
+
if (result.success) {
|
|
125
|
+
logger.debug("[sync] complete", `source=scvd newFindings=${result.newFindings} totalIndexed=${result.totalIndexed}`);
|
|
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
|
-
|
|
132
|
+
const errorResult = buildErrorResult(error);
|
|
133
|
+
logger.debug("[sync] failed", `source=scvd reason=${errorResult.reason}`);
|
|
134
|
+
await persistErrorMetadata(indexPath, errorResult).catch(() => {});
|
|
135
|
+
return errorResult;
|
|
136
|
+
} finally {
|
|
137
|
+
releaseSyncLock();
|
|
37
138
|
}
|
|
38
139
|
}
|
|
39
140
|
|
|
@@ -41,32 +142,80 @@ export async function syncIncremental(
|
|
|
41
142
|
client: ScvdClient,
|
|
42
143
|
indexPath: string
|
|
43
144
|
): Promise<SyncResult> {
|
|
145
|
+
const logger = createLogger();
|
|
146
|
+
|
|
147
|
+
if (!acquireSyncLock()) {
|
|
148
|
+
return createParseError("Sync already in progress");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
logger.debug("[sync] starting", "source=scvd mode=incremental");
|
|
152
|
+
|
|
44
153
|
try {
|
|
45
|
-
const [
|
|
46
|
-
client.fetchStats(),
|
|
154
|
+
const [statsResult, existingIndex] = await Promise.all([
|
|
155
|
+
withRetry(() => client.fetchStats(), {
|
|
156
|
+
maxAttempts: RETRY_MAX_ATTEMPTS,
|
|
157
|
+
baseDelayMs: RETRY_BASE_DELAY_MS,
|
|
158
|
+
shouldRetry: shouldRetrySyncError,
|
|
159
|
+
}),
|
|
47
160
|
loadIndex(indexPath),
|
|
48
161
|
]);
|
|
49
162
|
|
|
163
|
+
if (!statsResult.success) {
|
|
164
|
+
const errorResult = buildErrorResult(statsResult.error);
|
|
165
|
+
errorResult.attempts = statsResult.attempts;
|
|
166
|
+
await persistErrorMetadata(indexPath, errorResult).catch(() => {});
|
|
167
|
+
return errorResult;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
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;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const stats = statsResult.value;
|
|
178
|
+
|
|
50
179
|
if (existingIndex && existingIndex.totalFindings === stats.total) {
|
|
51
|
-
return {
|
|
52
|
-
success: true,
|
|
180
|
+
return createSyncSuccess({
|
|
53
181
|
newFindings: 0,
|
|
54
182
|
totalIndexed: existingIndex.totalFindings,
|
|
55
183
|
lastSync: existingIndex.lastSync,
|
|
56
|
-
};
|
|
184
|
+
});
|
|
57
185
|
}
|
|
58
186
|
|
|
59
|
-
return await
|
|
187
|
+
return await syncAllUnlocked(client, indexPath);
|
|
60
188
|
} catch (error) {
|
|
61
|
-
|
|
189
|
+
const errorResult = buildErrorResult(error);
|
|
190
|
+
await persistErrorMetadata(indexPath, errorResult).catch(() => {});
|
|
191
|
+
return errorResult;
|
|
192
|
+
} finally {
|
|
193
|
+
releaseSyncLock();
|
|
62
194
|
}
|
|
63
195
|
}
|
|
196
|
+
const STALE_THRESHOLD_DAYS = 7;
|
|
197
|
+
|
|
198
|
+
export function isSyncStale(
|
|
199
|
+
index: ScvdIndex | null,
|
|
200
|
+
thresholdDays: number = STALE_THRESHOLD_DAYS
|
|
201
|
+
): 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;
|
|
208
|
+
}
|
|
64
209
|
|
|
65
210
|
export async function getSyncStatus(indexPath: string): Promise<{
|
|
66
211
|
lastSync: string | null;
|
|
67
212
|
totalFindings: number;
|
|
68
213
|
healthy: boolean;
|
|
214
|
+
stale: boolean;
|
|
215
|
+
metadata: ScvdIndexMetadata | null;
|
|
216
|
+
hint?: string;
|
|
69
217
|
}> {
|
|
218
|
+
const logger = createLogger();
|
|
70
219
|
const index = await loadIndex(indexPath);
|
|
71
220
|
|
|
72
221
|
if (!index) {
|
|
@@ -74,6 +223,26 @@ export async function getSyncStatus(indexPath: string): Promise<{
|
|
|
74
223
|
lastSync: null,
|
|
75
224
|
totalFindings: 0,
|
|
76
225
|
healthy: false,
|
|
226
|
+
stale: true,
|
|
227
|
+
metadata: null,
|
|
228
|
+
hint: "SCVD data is missing. Run argus_sync_knowledge to populate.",
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const stale = isSyncStale(index);
|
|
233
|
+
|
|
234
|
+
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}`);
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
lastSync: index.lastSync,
|
|
241
|
+
totalFindings: index.totalFindings,
|
|
242
|
+
healthy: true,
|
|
243
|
+
stale: true,
|
|
244
|
+
metadata: index.metadata ?? null,
|
|
245
|
+
hint: "SCVD data is stale. Run argus_sync_knowledge to update.",
|
|
77
246
|
};
|
|
78
247
|
}
|
|
79
248
|
|
|
@@ -81,5 +250,7 @@ export async function getSyncStatus(indexPath: string): Promise<{
|
|
|
81
250
|
lastSync: index.lastSync,
|
|
82
251
|
totalFindings: index.totalFindings,
|
|
83
252
|
healthy: true,
|
|
253
|
+
stale: false,
|
|
254
|
+
metadata: index.metadata ?? null,
|
|
84
255
|
};
|
|
85
256
|
}
|
|
@@ -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
|
+
}
|
package/src/plugin-interface.ts
CHANGED
|
@@ -16,7 +16,20 @@ export function createPluginInterface(args: {
|
|
|
16
16
|
config: hooks.config,
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
if (hooks["
|
|
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"] =
|
|
29
|
+
hooks["experimental.chat.system.transform"]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (hooks["experimental.session.compacting"]) {
|
|
20
33
|
result["experimental.session.compacting"] =
|
|
21
34
|
hooks["experimental.session.compacting"]
|
|
22
35
|
}
|
package/src/shared/logger.ts
CHANGED
|
@@ -1,36 +1,97 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync, existsSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
import { homedir } from "node:os"
|
|
4
|
+
|
|
1
5
|
export interface LoggerConfig {
|
|
2
|
-
debug?: boolean
|
|
6
|
+
debug?: boolean
|
|
3
7
|
}
|
|
4
8
|
|
|
5
9
|
export interface Logger {
|
|
6
|
-
info(...args:
|
|
7
|
-
debug(...args:
|
|
8
|
-
error(...args:
|
|
9
|
-
warn(...args:
|
|
10
|
+
info(...args: unknown[]): void
|
|
11
|
+
debug(...args: unknown[]): void
|
|
12
|
+
error(...args: unknown[]): void
|
|
13
|
+
warn(...args: unknown[]): void
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
type LogSink = (line: string) => void
|
|
17
|
+
|
|
18
|
+
const LOG_DIR = join(homedir(), ".cache", "solidity-argus")
|
|
19
|
+
const LOG_FILE = join(LOG_DIR, "argus.log")
|
|
20
|
+
|
|
21
|
+
function ensureLogDir(): void {
|
|
22
|
+
if (!existsSync(LOG_DIR)) {
|
|
23
|
+
mkdirSync(LOG_DIR, { recursive: true })
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatLine(level: string, args: unknown[]): string {
|
|
28
|
+
const ts = new Date().toISOString()
|
|
29
|
+
const msg = args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" ")
|
|
30
|
+
return `${ts} [${level}] ${msg}\n`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createFileSink(): LogSink {
|
|
34
|
+
let dirReady = false
|
|
35
|
+
return (line: string) => {
|
|
36
|
+
if (!dirReady) {
|
|
37
|
+
ensureLogDir()
|
|
38
|
+
dirReady = true
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
appendFileSync(LOG_FILE, line)
|
|
42
|
+
} catch {
|
|
43
|
+
// if we can't write logs, we don't crash the plugin
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createStderrSink(): LogSink {
|
|
49
|
+
return (line: string) => {
|
|
50
|
+
process.stderr.write(line)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolveSink(): LogSink {
|
|
55
|
+
const mode = process.env.ARGUS_LOG
|
|
56
|
+
if (mode === "stderr") return createStderrSink()
|
|
57
|
+
return createFileSink()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let sharedSink: LogSink | null = null
|
|
61
|
+
|
|
62
|
+
function getSink(): LogSink {
|
|
63
|
+
if (!sharedSink) {
|
|
64
|
+
sharedSink = resolveSink()
|
|
65
|
+
}
|
|
66
|
+
return sharedSink
|
|
67
|
+
}
|
|
14
68
|
|
|
15
|
-
|
|
69
|
+
export function createLogger(config: LoggerConfig = {}): Logger {
|
|
70
|
+
const { debug = false } = config
|
|
16
71
|
|
|
17
72
|
return {
|
|
18
|
-
info(...args:
|
|
19
|
-
|
|
73
|
+
info(...args: unknown[]): void {
|
|
74
|
+
getSink()(formatLine("INFO", args))
|
|
20
75
|
},
|
|
21
76
|
|
|
22
|
-
debug(...args:
|
|
77
|
+
debug(...args: unknown[]): void {
|
|
23
78
|
if (debug) {
|
|
24
|
-
|
|
79
|
+
getSink()(formatLine("DEBUG", args))
|
|
25
80
|
}
|
|
26
81
|
},
|
|
27
82
|
|
|
28
|
-
error(...args:
|
|
29
|
-
|
|
83
|
+
error(...args: unknown[]): void {
|
|
84
|
+
getSink()(formatLine("ERROR", args))
|
|
30
85
|
},
|
|
31
86
|
|
|
32
|
-
warn(...args:
|
|
33
|
-
|
|
87
|
+
warn(...args: unknown[]): void {
|
|
88
|
+
getSink()(formatLine("WARN", args))
|
|
34
89
|
},
|
|
35
|
-
}
|
|
90
|
+
}
|
|
36
91
|
}
|
|
92
|
+
|
|
93
|
+
export function resetLoggerSink(): void {
|
|
94
|
+
sharedSink = null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { LOG_FILE, LOG_DIR }
|