solidity-argus 0.3.3 → 0.3.5
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/package.json +1 -1
- package/src/agents/argus-prompt.ts +67 -8
- package/src/agents/scribe-prompt.ts +13 -5
- package/src/cli/commands/init.ts +1 -1
- package/src/cli/index.ts +0 -0
- package/src/config/schema.ts +7 -2
- package/src/create-hooks.ts +116 -27
- package/src/features/audit-enforcer/audit-enforcer.ts +31 -2
- package/src/features/migration/index.ts +14 -0
- package/src/features/migration/migration-adapter.ts +151 -0
- package/src/features/migration/parity-telemetry.ts +133 -0
- package/src/features/persistent-state/audit-state-manager.ts +28 -6
- package/src/features/persistent-state/event-sink.ts +175 -0
- package/src/features/persistent-state/findings-materializer.ts +51 -0
- package/src/features/persistent-state/index.ts +2 -0
- package/src/features/persistent-state/run-finalizer.ts +192 -0
- package/src/features/persistent-state/run-journal.ts +15 -4
- package/src/hooks/agent-tracker.ts +15 -0
- package/src/hooks/event-hook.ts +93 -1
- package/src/hooks/system-prompt-hook.ts +20 -0
- package/src/hooks/tool-tracking-hook.ts +263 -33
- package/src/shared/audit-artifact-resolver.ts +75 -0
- package/src/shared/drop-diagnostics.ts +108 -0
- package/src/shared/file-utils.ts +7 -2
- package/src/shared/index.ts +14 -0
- package/src/shared/path-root-resolver.ts +34 -0
- package/src/shared/report-path-resolver.ts +70 -0
- package/src/solodit-lifecycle.ts +86 -7
- package/src/state/adapters.ts +262 -0
- package/src/state/index.ts +15 -0
- package/src/state/projectors.ts +437 -0
- package/src/state/schemas.ts +453 -0
- package/src/state/types.ts +6 -0
- package/src/tools/report-generator-tool.ts +647 -36
- package/src/tools/report-preflight.ts +79 -0
- package/src/tools/solodit-search-tool.ts +15 -24
- package/src/utils/solodit-health.ts +18 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectToolLifecycleIssues,
|
|
3
|
+
hasSessionCreated,
|
|
4
|
+
hasSessionDeleted,
|
|
5
|
+
} from "../features/persistent-state/run-finalizer"
|
|
6
|
+
import type { AuditEvent } from "../state/schemas"
|
|
7
|
+
|
|
8
|
+
export interface PreflightResult {
|
|
9
|
+
passed: boolean
|
|
10
|
+
orphanedTools: string[]
|
|
11
|
+
missingLifecycle: string[]
|
|
12
|
+
missingRequiredTools: string[]
|
|
13
|
+
warnings: string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PreflightOptions {
|
|
17
|
+
requiredTools?: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
21
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
22
|
+
return value as Record<string, unknown>
|
|
23
|
+
}
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hasCompletedTool(events: AuditEvent[], toolName: string): boolean {
|
|
28
|
+
for (const event of events) {
|
|
29
|
+
if (event.type !== "tool.completed") {
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const payload = asRecord(event.payload)
|
|
34
|
+
if (!payload) {
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const name = payload.name
|
|
39
|
+
const tool = payload.tool
|
|
40
|
+
if (name === toolName || tool === toolName) {
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function checkReportPreflight(
|
|
49
|
+
events: AuditEvent[],
|
|
50
|
+
options: PreflightOptions = {},
|
|
51
|
+
): PreflightResult {
|
|
52
|
+
const missingLifecycle: string[] = []
|
|
53
|
+
if (!hasSessionCreated(events)) {
|
|
54
|
+
missingLifecycle.push("session.created")
|
|
55
|
+
}
|
|
56
|
+
if (!hasSessionDeleted(events)) {
|
|
57
|
+
missingLifecycle.push("session.deleted")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { orphanedToolCallIds, malformedEvents } = collectToolLifecycleIssues(events)
|
|
61
|
+
|
|
62
|
+
const missingRequiredTools: string[] = []
|
|
63
|
+
for (const requiredTool of options.requiredTools ?? []) {
|
|
64
|
+
if (!hasCompletedTool(events, requiredTool)) {
|
|
65
|
+
missingRequiredTools.push(requiredTool)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
passed:
|
|
71
|
+
orphanedToolCallIds.length === 0 &&
|
|
72
|
+
missingLifecycle.length === 0 &&
|
|
73
|
+
missingRequiredTools.length === 0,
|
|
74
|
+
orphanedTools: orphanedToolCallIds,
|
|
75
|
+
missingLifecycle,
|
|
76
|
+
missingRequiredTools,
|
|
77
|
+
warnings: malformedEvents,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -245,33 +245,22 @@ export async function executeSoloditSearch(
|
|
|
245
245
|
|
|
246
246
|
context.metadata({ title: `Solodit search: ${query}` })
|
|
247
247
|
|
|
248
|
-
// Belt-and-suspenders: check if Solodit MCP is available, with 3s retry
|
|
249
|
-
// Skip check in test environment
|
|
250
|
-
if (!soloditAvailable && process.env.NODE_ENV !== "test") {
|
|
251
|
-
// Wait up to 3s for monitoring to flip the flag
|
|
252
|
-
for (let i = 0; i < 3 && !soloditAvailable; i++) {
|
|
253
|
-
await Bun.sleep(1000)
|
|
254
|
-
}
|
|
255
|
-
if (!soloditAvailable) {
|
|
256
|
-
return {
|
|
257
|
-
results: [],
|
|
258
|
-
totalFound: 0,
|
|
259
|
-
query,
|
|
260
|
-
error:
|
|
261
|
-
"Solodit MCP not available — server did not start. Results limited to local patterns.",
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
248
|
const mcpCaller = callMcpTool ?? (hasMcpCapability(context) ? context.callMcpTool : undefined)
|
|
267
249
|
|
|
250
|
+
// When MCP is unavailable or no caller exists, go straight to HTTP fallback
|
|
268
251
|
if (!mcpCaller) {
|
|
252
|
+
const reason = !soloditAvailable ? "MCP unavailable" : "no callMcpTool"
|
|
253
|
+
logger.debug(`[solodit] ${reason} — using HTTP fallback for query: ${query}`)
|
|
269
254
|
return callSoloditHttp(query, limit, args.severity, port)
|
|
270
255
|
}
|
|
271
256
|
|
|
257
|
+
// MCP path: try each tool name in order, fall back to HTTP on any failure
|
|
272
258
|
let hadMcpError = false
|
|
273
259
|
for (const toolName of SOLODIT_MCP_TOOLS) {
|
|
274
260
|
try {
|
|
261
|
+
logger.debug(
|
|
262
|
+
`[solodit] Trying MCP tool '${toolName}' on server '${SOLODIT_MCP_SERVER}' for query: ${query}`,
|
|
263
|
+
)
|
|
275
264
|
const response = await mcpCaller(
|
|
276
265
|
SOLODIT_MCP_SERVER,
|
|
277
266
|
toolName,
|
|
@@ -279,6 +268,7 @@ export async function executeSoloditSearch(
|
|
|
279
268
|
)
|
|
280
269
|
|
|
281
270
|
if (hasMcpError(response)) {
|
|
271
|
+
logger.debug(`[solodit] MCP tool '${toolName}' returned error envelope — trying next tool`)
|
|
282
272
|
hadMcpError = true
|
|
283
273
|
continue
|
|
284
274
|
}
|
|
@@ -288,22 +278,23 @@ export async function executeSoloditSearch(
|
|
|
288
278
|
args.severity,
|
|
289
279
|
)
|
|
290
280
|
|
|
281
|
+
logger.debug(`[solodit] MCP tool '${toolName}' succeeded — found ${findings.length} findings`)
|
|
291
282
|
return {
|
|
292
283
|
results: findings.slice(0, limit),
|
|
293
284
|
totalFound: findings.length,
|
|
294
285
|
query,
|
|
295
286
|
}
|
|
296
287
|
} catch {
|
|
288
|
+
logger.debug(`[solodit] MCP tool '${toolName}' threw — trying next tool`)
|
|
297
289
|
hadMcpError = true
|
|
298
290
|
}
|
|
299
291
|
}
|
|
300
292
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return fallback
|
|
293
|
+
// All MCP tools failed — fall back to HTTP
|
|
294
|
+
logger.debug(
|
|
295
|
+
`[solodit] All MCP tools failed (hadMcpError=${hadMcpError}) — falling back to HTTP for query: ${query}`,
|
|
296
|
+
)
|
|
297
|
+
return callSoloditHttp(query, limit, args.severity, port)
|
|
307
298
|
}
|
|
308
299
|
|
|
309
300
|
export function createSoloditSearchTool(port: number = DEFAULT_SOLODIT_PORT): ToolDefinition {
|
|
@@ -5,6 +5,17 @@ export interface SoloditHealthStatus {
|
|
|
5
5
|
error?: string
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
const MCP_INITIALIZE_BODY = JSON.stringify({
|
|
9
|
+
jsonrpc: "2.0",
|
|
10
|
+
method: "initialize",
|
|
11
|
+
params: {
|
|
12
|
+
protocolVersion: "2024-11-05",
|
|
13
|
+
capabilities: {},
|
|
14
|
+
clientInfo: { name: "argus-health-probe", version: "1.0.0" },
|
|
15
|
+
},
|
|
16
|
+
id: 1,
|
|
17
|
+
})
|
|
18
|
+
|
|
8
19
|
export async function checkSoloditHealth(
|
|
9
20
|
port: number,
|
|
10
21
|
enabled: boolean,
|
|
@@ -15,8 +26,15 @@ export async function checkSoloditHealth(
|
|
|
15
26
|
|
|
16
27
|
try {
|
|
17
28
|
const response = await fetch(`http://localhost:${port}/mcp`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
Accept: "application/json, text/event-stream",
|
|
33
|
+
},
|
|
34
|
+
body: MCP_INITIALIZE_BODY,
|
|
18
35
|
signal: AbortSignal.timeout(2000),
|
|
19
36
|
})
|
|
37
|
+
// Any 2xx response means the MCP server is reachable (even if body contains JSON-RPC error)
|
|
20
38
|
return { reachable: response.ok, enabled: true, port }
|
|
21
39
|
} catch (error) {
|
|
22
40
|
return {
|