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.
- package/AGENTS.md +3 -3
- package/README.md +229 -13
- package/package.json +37 -8
- package/skills/INVENTORY.md +88 -57
- package/skills/README.md +72 -6
- package/skills/case-studies/beanstalk-governance/SKILL.md +52 -0
- package/skills/case-studies/bzx-flash-loan/SKILL.md +53 -0
- package/skills/case-studies/cream-finance/SKILL.md +52 -0
- package/skills/case-studies/curve-reentrancy/SKILL.md +52 -0
- package/skills/case-studies/dao-hack/SKILL.md +51 -0
- package/skills/case-studies/euler-finance/SKILL.md +52 -0
- package/skills/case-studies/harvest-finance/SKILL.md +52 -0
- package/skills/case-studies/level-finance/SKILL.md +51 -0
- package/skills/case-studies/mango-markets/SKILL.md +53 -0
- package/skills/case-studies/nomad-bridge/SKILL.md +51 -0
- package/skills/case-studies/parity-multisig/SKILL.md +55 -0
- package/skills/case-studies/poly-network/SKILL.md +51 -0
- package/skills/case-studies/rari-fuse/SKILL.md +51 -0
- package/skills/case-studies/ronin-bridge/SKILL.md +52 -0
- package/skills/case-studies/wormhole-bridge/SKILL.md +51 -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 +9 -0
- package/skills/manifests/solodit.json +9 -0
- package/skills/manifests/sunweb3sec.json +9 -0
- package/skills/manifests/trailofbits.json +9 -0
- package/skills/methodology/audit-workflow/SKILL.md +3 -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 +27 -0
- package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +13 -1
- package/skills/vulnerability-patterns/assert-violation/SKILL.md +8 -1
- package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +12 -1
- package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +8 -1
- package/skills/vulnerability-patterns/cross-chain-bridge-vulnerabilities/SKILL.md +217 -0
- package/skills/vulnerability-patterns/default-visibility/SKILL.md +13 -1
- package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +8 -1
- package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +14 -1
- package/skills/vulnerability-patterns/erc4626-exchange-rate-manipulation/SKILL.md +64 -0
- package/skills/vulnerability-patterns/fee-on-transfer-tokens/SKILL.md +93 -0
- package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +13 -0
- package/skills/vulnerability-patterns/floating-pragma/SKILL.md +8 -1
- package/skills/vulnerability-patterns/front-running-attacks/SKILL.md +209 -0
- package/skills/vulnerability-patterns/gas-optimization-patterns/SKILL.md +203 -0
- package/skills/vulnerability-patterns/governance-attacks/SKILL.md +208 -0
- package/skills/vulnerability-patterns/hash-collision/SKILL.md +8 -1
- package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +12 -1
- package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +8 -1
- package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +8 -1
- package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +12 -1
- package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +7 -1
- package/skills/vulnerability-patterns/logic-errors/SKILL.md +10 -0
- package/skills/vulnerability-patterns/missing-parameter-bounds/SKILL.md +44 -0
- package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +17 -1
- package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +12 -1
- package/skills/vulnerability-patterns/off-by-one/SKILL.md +7 -1
- package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +22 -0
- package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +11 -1
- package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +22 -0
- package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +8 -1
- package/skills/vulnerability-patterns/share-accounting-desynchronization/SKILL.md +44 -0
- package/skills/vulnerability-patterns/signature-malleability/SKILL.md +11 -1
- package/skills/vulnerability-patterns/stateful-parameter-update-drift/SKILL.md +44 -0
- package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +13 -1
- package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +8 -1
- package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +8 -1
- package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +8 -1
- package/skills/vulnerability-patterns/unsafe-erc20-transfers/SKILL.md +132 -0
- package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +11 -1
- package/skills/vulnerability-patterns/unused-variables/SKILL.md +8 -1
- package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +8 -1
- package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +8 -1
- package/skills/vulnerability-patterns/weird-tokens/SKILL.md +10 -0
- package/skills/vulnerability-patterns/zero-address-misconfiguration/SKILL.md +48 -0
- package/src/agents/argus-prompt.ts +27 -10
- package/src/agents/pythia-prompt.ts +7 -8
- package/src/agents/scribe-prompt.ts +10 -5
- package/src/agents/sentinel-prompt.ts +36 -7
- package/src/cli/cli-output.ts +16 -0
- package/src/cli/cli-program.ts +29 -22
- package/src/cli/commands/check-skills.ts +135 -0
- package/src/cli/commands/doctor.ts +303 -23
- package/src/cli/commands/init.ts +8 -6
- package/src/cli/commands/install.ts +10 -8
- package/src/cli/commands/lint-skills.ts +118 -0
- package/src/cli/index.ts +5 -5
- package/src/cli/tui-prompts.ts +4 -2
- package/src/cli/types.ts +3 -3
- package/src/config/index.ts +1 -1
- package/src/config/loader.ts +4 -6
- package/src/config/schema.ts +6 -5
- package/src/config/types.ts +2 -2
- package/src/constants/defaults.ts +2 -0
- package/src/create-hooks.ts +225 -29
- package/src/create-managers.ts +10 -8
- package/src/create-tools.ts +14 -8
- package/src/features/background-agent/background-manager.ts +93 -87
- package/src/features/background-agent/index.ts +1 -1
- package/src/features/context-monitor/context-monitor.ts +3 -3
- package/src/features/context-monitor/index.ts +2 -2
- package/src/features/error-recovery/session-recovery.ts +2 -4
- package/src/features/error-recovery/tool-error-recovery.ts +79 -19
- package/src/features/index.ts +5 -5
- package/src/features/persistent-state/audit-state-manager.ts +158 -52
- package/src/features/persistent-state/global-run-index.ts +38 -0
- package/src/features/persistent-state/index.ts +1 -1
- package/src/features/persistent-state/run-journal.ts +86 -0
- package/src/hooks/agent-tracker.ts +53 -0
- package/src/hooks/compaction-hook.ts +46 -37
- package/src/hooks/config-handler.ts +31 -11
- package/src/hooks/context-budget.ts +42 -0
- package/src/hooks/event-hook.ts +48 -23
- package/src/hooks/hook-system.ts +4 -4
- package/src/hooks/index.ts +5 -5
- package/src/hooks/knowledge-sync-hook.ts +19 -21
- package/src/hooks/recon-context-builder.ts +66 -0
- package/src/hooks/safe-create-hook.ts +9 -11
- package/src/hooks/system-prompt-hook.ts +128 -0
- package/src/hooks/tool-tracking-hook.ts +162 -29
- package/src/hooks/types.ts +2 -1
- package/src/index.ts +23 -13
- package/src/knowledge/retry.ts +53 -0
- package/src/knowledge/scvd-client.ts +103 -83
- package/src/knowledge/scvd-errors.ts +89 -0
- package/src/knowledge/scvd-index.ts +110 -62
- package/src/knowledge/scvd-sync.ts +223 -47
- package/src/knowledge/source-manifest.ts +102 -0
- package/src/managers/index.ts +1 -1
- package/src/managers/types.ts +19 -14
- package/src/plugin-interface.ts +19 -8
- package/src/shared/binary-utils.ts +44 -34
- package/src/shared/deep-merge.ts +55 -36
- package/src/shared/file-utils.ts +21 -19
- package/src/shared/index.ts +11 -5
- package/src/shared/jsonc-parser.ts +123 -28
- package/src/shared/logger.ts +91 -17
- package/src/shared/project-utils.ts +30 -0
- package/src/skills/analysis/cluster.ts +414 -0
- package/src/skills/analysis/gates.ts +227 -0
- package/src/skills/analysis/index.ts +33 -0
- package/src/skills/analysis/normalize.ts +217 -0
- package/src/skills/analysis/similarity.ts +224 -0
- package/src/skills/argus-skill-resolver.ts +237 -0
- package/src/skills/skill-schema.ts +99 -0
- package/src/solodit-lifecycle.ts +202 -0
- package/src/state/audit-state.ts +10 -8
- package/src/state/finding-store.ts +68 -55
- package/src/state/types.ts +96 -44
- package/src/tools/argus-skill-load-tool.ts +78 -0
- package/src/tools/contract-analyzer-tool.ts +60 -77
- package/src/tools/forge-coverage-tool.ts +226 -0
- package/src/tools/forge-fuzz-tool.ts +127 -127
- package/src/tools/forge-test-tool.ts +153 -157
- package/src/tools/gas-analysis-tool.ts +264 -0
- package/src/tools/pattern-checker-tool.ts +206 -167
- package/src/tools/pattern-loader.ts +77 -0
- package/src/tools/pattern-schema.ts +51 -0
- package/src/tools/proxy-detection-tool.ts +224 -0
- package/src/tools/report-generator-tool.ts +333 -142
- package/src/tools/slither-tool.ts +300 -210
- package/src/tools/solodit-search-tool.ts +255 -80
- package/src/tools/sync-knowledge-tool.ts +7 -11
- package/src/utils/audit-artifact-detector.ts +118 -0
- package/src/utils/dependency-scanner.ts +93 -0
- package/src/utils/project-detector.ts +175 -86
- package/src/utils/solidity-parser.ts +112 -67
- package/src/utils/solodit-health.ts +29 -0
- package/src/hooks/event-hook-v2.ts +0 -99
- package/src/state/plugin-state.ts +0 -14
package/src/shared/deep-merge.ts
CHANGED
|
@@ -1,49 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const deduplicateObjectIds = new WeakMap<object, number>()
|
|
2
|
+
let nextDeduplicateObjectId = 1
|
|
3
|
+
|
|
4
|
+
function getDeduplicateObjectKey(obj: object): string {
|
|
5
|
+
let id = deduplicateObjectIds.get(obj)
|
|
6
|
+
if (id === undefined) {
|
|
7
|
+
id = nextDeduplicateObjectId++
|
|
8
|
+
deduplicateObjectIds.set(obj, id)
|
|
9
|
+
}
|
|
10
|
+
return `object:${id}`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function deduplicateArray(arr: unknown[]): unknown[] {
|
|
14
|
+
const seen = new Set<string>()
|
|
15
|
+
const result: unknown[] = []
|
|
16
|
+
|
|
17
|
+
for (const item of arr) {
|
|
18
|
+
let key: string
|
|
19
|
+
if (typeof item === "object" && item !== null) {
|
|
20
|
+
try {
|
|
21
|
+
key = `object:${JSON.stringify(item)}`
|
|
22
|
+
} catch {
|
|
23
|
+
key = getDeduplicateObjectKey(item)
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
key = `${typeof item}:${String(item)}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!seen.has(key)) {
|
|
30
|
+
seen.add(key)
|
|
31
|
+
result.push(item)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function deepMerge(target: unknown, source: unknown): unknown {
|
|
3
39
|
if (source === undefined) {
|
|
4
|
-
return target
|
|
40
|
+
return target
|
|
5
41
|
}
|
|
6
42
|
|
|
7
|
-
// If either is not an object, return source (override)
|
|
8
43
|
if (
|
|
9
44
|
typeof target !== "object" ||
|
|
10
45
|
target === null ||
|
|
11
46
|
typeof source !== "object" ||
|
|
12
47
|
source === null
|
|
13
48
|
) {
|
|
14
|
-
return source
|
|
49
|
+
return source
|
|
15
50
|
}
|
|
16
51
|
|
|
17
|
-
// If both are arrays, concatenate and deduplicate
|
|
18
52
|
if (Array.isArray(target) && Array.isArray(source)) {
|
|
19
|
-
|
|
20
|
-
// Deduplicate by filtering unique values
|
|
21
|
-
return Array.from(new Set(merged));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// If target is array but source is not, return source
|
|
25
|
-
if (Array.isArray(target) && !Array.isArray(source)) {
|
|
26
|
-
return source;
|
|
53
|
+
return deduplicateArray([...target, ...source])
|
|
27
54
|
}
|
|
28
55
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return source;
|
|
56
|
+
if (Array.isArray(target) || Array.isArray(source)) {
|
|
57
|
+
return source
|
|
32
58
|
}
|
|
33
59
|
|
|
34
|
-
|
|
35
|
-
const
|
|
60
|
+
const tgt = target as Record<string, unknown>
|
|
61
|
+
const src = source as Record<string, unknown>
|
|
62
|
+
const result: Record<string, unknown> = { ...tgt }
|
|
36
63
|
|
|
37
|
-
for (const key in
|
|
38
|
-
if (Object.
|
|
39
|
-
const sourceValue =
|
|
64
|
+
for (const key in src) {
|
|
65
|
+
if (Object.hasOwn(src, key)) {
|
|
66
|
+
const sourceValue = src[key]
|
|
40
67
|
|
|
41
|
-
// Skip undefined values from source
|
|
42
68
|
if (sourceValue === undefined) {
|
|
43
|
-
continue
|
|
69
|
+
continue
|
|
44
70
|
}
|
|
45
71
|
|
|
46
|
-
// If both are objects (and not arrays), recurse
|
|
47
72
|
if (
|
|
48
73
|
typeof result[key] === "object" &&
|
|
49
74
|
result[key] !== null &&
|
|
@@ -52,20 +77,14 @@ export function deepMerge(target: any, source: any): any {
|
|
|
52
77
|
sourceValue !== null &&
|
|
53
78
|
!Array.isArray(sourceValue)
|
|
54
79
|
) {
|
|
55
|
-
result[key] = deepMerge(result[key], sourceValue)
|
|
56
|
-
} else if (
|
|
57
|
-
|
|
58
|
-
Array.isArray(sourceValue)
|
|
59
|
-
) {
|
|
60
|
-
// Both are arrays, concatenate and deduplicate
|
|
61
|
-
const merged = [...result[key], ...sourceValue];
|
|
62
|
-
result[key] = Array.from(new Set(merged));
|
|
80
|
+
result[key] = deepMerge(result[key], sourceValue)
|
|
81
|
+
} else if (Array.isArray(result[key]) && Array.isArray(sourceValue)) {
|
|
82
|
+
result[key] = deduplicateArray([...(result[key] as unknown[]), ...sourceValue])
|
|
63
83
|
} else {
|
|
64
|
-
|
|
65
|
-
result[key] = sourceValue;
|
|
84
|
+
result[key] = sourceValue
|
|
66
85
|
}
|
|
67
86
|
}
|
|
68
87
|
}
|
|
69
88
|
|
|
70
|
-
return result
|
|
89
|
+
return result
|
|
71
90
|
}
|
package/src/shared/file-utils.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "fs"
|
|
2
|
-
import { join } from "path"
|
|
3
|
-
import { stripJsoncComments } from "./jsonc-parser"
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
import { stripJsoncComments } from "./jsonc-parser"
|
|
4
4
|
|
|
5
|
-
export type ConfigFormat = "json" | "jsonc" | "none"
|
|
5
|
+
export type ConfigFormat = "json" | "jsonc" | "none"
|
|
6
6
|
|
|
7
7
|
export interface ConfigFileInfo {
|
|
8
|
-
path: string | null
|
|
9
|
-
format: ConfigFormat
|
|
8
|
+
path: string | null
|
|
9
|
+
format: ConfigFormat
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function detectConfigFile(basePath: string): ConfigFileInfo {
|
|
@@ -15,42 +15,44 @@ export function detectConfigFile(basePath: string): ConfigFileInfo {
|
|
|
15
15
|
{ path: join(basePath, ".opencode", "solidity-argus.json"), format: "json" as const },
|
|
16
16
|
{ path: join(basePath, "solidity-argus.jsonc"), format: "jsonc" as const },
|
|
17
17
|
{ path: join(basePath, "solidity-argus.json"), format: "json" as const },
|
|
18
|
-
|
|
19
|
-
{ path: join(basePath, "config.json"), format: "json" as const },
|
|
20
|
-
];
|
|
18
|
+
]
|
|
21
19
|
|
|
22
20
|
for (const candidate of candidates) {
|
|
23
21
|
if (existsSync(candidate.path)) {
|
|
24
22
|
return {
|
|
25
23
|
path: candidate.path,
|
|
26
24
|
format: candidate.format,
|
|
27
|
-
}
|
|
25
|
+
}
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
return {
|
|
32
30
|
path: null,
|
|
33
31
|
format: "none",
|
|
34
|
-
}
|
|
32
|
+
}
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
export function readJsoncFile(filePath: string): Record<string,
|
|
35
|
+
export function readJsoncFile(filePath: string): Record<string, unknown> | null {
|
|
38
36
|
try {
|
|
39
37
|
if (!existsSync(filePath)) {
|
|
40
|
-
return null
|
|
38
|
+
return null
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
const content = readFileSync(filePath, "utf-8")
|
|
41
|
+
const content = readFileSync(filePath, "utf-8")
|
|
44
42
|
|
|
45
43
|
if (!content.trim()) {
|
|
46
|
-
return null
|
|
44
|
+
return null
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
const stripped = stripJsoncComments(content)
|
|
50
|
-
const parsed = JSON.parse(stripped)
|
|
47
|
+
const stripped = stripJsoncComments(content)
|
|
48
|
+
const parsed: unknown = JSON.parse(stripped)
|
|
49
|
+
|
|
50
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
51
53
|
|
|
52
|
-
return parsed
|
|
54
|
+
return parsed as Record<string, unknown>
|
|
53
55
|
} catch (_error) {
|
|
54
|
-
return null
|
|
56
|
+
return null
|
|
55
57
|
}
|
|
56
58
|
}
|
package/src/shared/index.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { deepMerge } from "./deep-merge"
|
|
3
|
-
export {
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export { extractContractNames, hasBinary, parseSolcVersion } from "./binary-utils"
|
|
2
|
+
export { deepMerge } from "./deep-merge"
|
|
3
|
+
export {
|
|
4
|
+
type ConfigFileInfo,
|
|
5
|
+
type ConfigFormat,
|
|
6
|
+
detectConfigFile,
|
|
7
|
+
readJsoncFile,
|
|
8
|
+
} from "./file-utils"
|
|
9
|
+
export { stripJsoncComments } from "./jsonc-parser"
|
|
10
|
+
export { createLogger, type Logger, type LoggerConfig } from "./logger"
|
|
11
|
+
export { findFoundryProjectDir, resolveProjectDir } from "./project-utils"
|
|
@@ -1,39 +1,134 @@
|
|
|
1
1
|
export function stripJsoncComments(jsonc: string): string {
|
|
2
|
-
let
|
|
2
|
+
let inString = false
|
|
3
|
+
let escaped = false
|
|
4
|
+
let inLineComment = false
|
|
5
|
+
let blockCommentDepth = 0
|
|
6
|
+
const chars: string[] = []
|
|
3
7
|
|
|
4
|
-
|
|
8
|
+
for (let i = 0; i < jsonc.length; i++) {
|
|
9
|
+
const ch = jsonc.charAt(i)
|
|
10
|
+
const next = jsonc.charAt(i + 1)
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
if (inLineComment) {
|
|
13
|
+
if (ch === "\n" || ch === "\r") {
|
|
14
|
+
inLineComment = false
|
|
15
|
+
chars.push(ch)
|
|
16
|
+
}
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
if (blockCommentDepth > 0) {
|
|
21
|
+
if (ch === "/" && next === "*") {
|
|
22
|
+
blockCommentDepth++
|
|
23
|
+
i++
|
|
24
|
+
continue
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (ch === "*" && next === "/") {
|
|
28
|
+
blockCommentDepth--
|
|
29
|
+
i++
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (ch === "\n" || ch === "\r") {
|
|
34
|
+
chars.push(ch)
|
|
35
|
+
}
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (escaped) {
|
|
40
|
+
escaped = false
|
|
41
|
+
chars.push(ch)
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (inString) {
|
|
46
|
+
if (ch === "\\") {
|
|
47
|
+
escaped = true
|
|
48
|
+
} else if (ch === '"') {
|
|
49
|
+
inString = false
|
|
50
|
+
}
|
|
51
|
+
chars.push(ch)
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (ch === '"') {
|
|
56
|
+
inString = true
|
|
57
|
+
chars.push(ch)
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (ch === "/" && next === "/") {
|
|
62
|
+
inLineComment = true
|
|
63
|
+
i++
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (ch === "/" && next === "*") {
|
|
68
|
+
blockCommentDepth = 1
|
|
69
|
+
i++
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
chars.push(ch)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = chars.join("")
|
|
77
|
+
const out: string[] = []
|
|
78
|
+
let inString2 = false
|
|
79
|
+
let escaped2 = false
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < result.length; i++) {
|
|
82
|
+
const ch = result.charAt(i)
|
|
83
|
+
|
|
84
|
+
if (escaped2) {
|
|
85
|
+
escaped2 = false
|
|
86
|
+
out.push(ch)
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (inString2) {
|
|
91
|
+
if (ch === "\\") {
|
|
92
|
+
escaped2 = true
|
|
93
|
+
} else if (ch === '"') {
|
|
94
|
+
inString2 = false
|
|
95
|
+
}
|
|
96
|
+
out.push(ch)
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (ch === '"') {
|
|
101
|
+
inString2 = true
|
|
102
|
+
out.push(ch)
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (ch === ",") {
|
|
107
|
+
let j = i + 1
|
|
108
|
+
while (j < result.length) {
|
|
109
|
+
const lookahead = result.charAt(j)
|
|
110
|
+
if (lookahead === " " || lookahead === "\t" || lookahead === "\n" || lookahead === "\r") {
|
|
111
|
+
j++
|
|
112
|
+
continue
|
|
24
113
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
break
|
|
114
|
+
|
|
115
|
+
if (lookahead === "}" || lookahead === "]") {
|
|
116
|
+
break
|
|
28
117
|
}
|
|
118
|
+
|
|
119
|
+
out.push(ch)
|
|
120
|
+
break
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (j >= result.length) {
|
|
124
|
+
out.push(ch)
|
|
29
125
|
}
|
|
30
126
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
})
|
|
34
|
-
.join("\n");
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
35
129
|
|
|
36
|
-
|
|
130
|
+
out.push(ch)
|
|
131
|
+
}
|
|
37
132
|
|
|
38
|
-
return
|
|
133
|
+
return out.join("")
|
|
39
134
|
}
|
package/src/shared/logger.ts
CHANGED
|
@@ -1,36 +1,110 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync } from "node:fs"
|
|
2
|
+
import { homedir } from "node:os"
|
|
3
|
+
import { join } from "node:path"
|
|
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 safeStringify(a: unknown): string {
|
|
28
|
+
if (typeof a === "string") return a
|
|
29
|
+
try {
|
|
30
|
+
return JSON.stringify(a)
|
|
31
|
+
} catch {
|
|
32
|
+
try {
|
|
33
|
+
return String(a)
|
|
34
|
+
} catch {
|
|
35
|
+
return "[Unserializable value]"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatLine(level: string, args: unknown[]): string {
|
|
41
|
+
const ts = new Date().toISOString()
|
|
42
|
+
const msg = args.map(safeStringify).join(" ")
|
|
43
|
+
return `${ts} [${level}] ${msg}\n`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createFileSink(): LogSink {
|
|
47
|
+
let dirReady = false
|
|
48
|
+
return (line: string) => {
|
|
49
|
+
if (!dirReady) {
|
|
50
|
+
ensureLogDir()
|
|
51
|
+
dirReady = true
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
appendFileSync(LOG_FILE, line)
|
|
55
|
+
} catch {
|
|
56
|
+
// if we can't write logs, we don't crash the plugin
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
14
60
|
|
|
15
|
-
|
|
61
|
+
function createStderrSink(): LogSink {
|
|
62
|
+
return (line: string) => {
|
|
63
|
+
process.stderr.write(line)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveSink(): LogSink {
|
|
68
|
+
const mode = process.env.ARGUS_LOG
|
|
69
|
+
if (mode === "stderr") return createStderrSink()
|
|
70
|
+
return createFileSink()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let sharedSink: LogSink | null = null
|
|
74
|
+
|
|
75
|
+
function getSink(): LogSink {
|
|
76
|
+
if (!sharedSink) {
|
|
77
|
+
sharedSink = resolveSink()
|
|
78
|
+
}
|
|
79
|
+
return sharedSink
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function createLogger(config: LoggerConfig = {}): Logger {
|
|
83
|
+
const { debug = false } = config
|
|
16
84
|
|
|
17
85
|
return {
|
|
18
|
-
info(...args:
|
|
19
|
-
|
|
86
|
+
info(...args: unknown[]): void {
|
|
87
|
+
getSink()(formatLine("INFO", args))
|
|
20
88
|
},
|
|
21
89
|
|
|
22
|
-
debug(...args:
|
|
90
|
+
debug(...args: unknown[]): void {
|
|
23
91
|
if (debug) {
|
|
24
|
-
|
|
92
|
+
getSink()(formatLine("DEBUG", args))
|
|
25
93
|
}
|
|
26
94
|
},
|
|
27
95
|
|
|
28
|
-
error(...args:
|
|
29
|
-
|
|
96
|
+
error(...args: unknown[]): void {
|
|
97
|
+
getSink()(formatLine("ERROR", args))
|
|
30
98
|
},
|
|
31
99
|
|
|
32
|
-
warn(...args:
|
|
33
|
-
|
|
100
|
+
warn(...args: unknown[]): void {
|
|
101
|
+
getSink()(formatLine("WARN", args))
|
|
34
102
|
},
|
|
35
|
-
}
|
|
103
|
+
}
|
|
36
104
|
}
|
|
105
|
+
|
|
106
|
+
export function resetLoggerSink(): void {
|
|
107
|
+
sharedSink = null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export { LOG_FILE, LOG_DIR }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import { dirname, join } from "node:path"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the project directory from tool execution context.
|
|
6
|
+
* Provides a consistent fallback chain: directory → worktree → cwd.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveProjectDir(context: { directory?: string; worktree?: string }): string {
|
|
9
|
+
return context.directory ?? context.worktree ?? process.cwd()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Walk up from a file path to find the nearest directory containing foundry.toml.
|
|
14
|
+
* Returns the file's parent directory if no foundry.toml is found.
|
|
15
|
+
*/
|
|
16
|
+
export function findFoundryProjectDir(fromPath: string): string {
|
|
17
|
+
let current = dirname(fromPath)
|
|
18
|
+
|
|
19
|
+
while (true) {
|
|
20
|
+
if (existsSync(join(current, "foundry.toml"))) {
|
|
21
|
+
return current
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const parent = dirname(current)
|
|
25
|
+
if (parent === current) {
|
|
26
|
+
return dirname(fromPath)
|
|
27
|
+
}
|
|
28
|
+
current = parent
|
|
29
|
+
}
|
|
30
|
+
}
|