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.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/src/agents/argus-prompt.ts +67 -8
  3. package/src/agents/scribe-prompt.ts +13 -5
  4. package/src/cli/commands/init.ts +1 -1
  5. package/src/cli/index.ts +0 -0
  6. package/src/config/schema.ts +7 -2
  7. package/src/create-hooks.ts +116 -27
  8. package/src/features/audit-enforcer/audit-enforcer.ts +31 -2
  9. package/src/features/migration/index.ts +14 -0
  10. package/src/features/migration/migration-adapter.ts +151 -0
  11. package/src/features/migration/parity-telemetry.ts +133 -0
  12. package/src/features/persistent-state/audit-state-manager.ts +28 -6
  13. package/src/features/persistent-state/event-sink.ts +175 -0
  14. package/src/features/persistent-state/findings-materializer.ts +51 -0
  15. package/src/features/persistent-state/index.ts +2 -0
  16. package/src/features/persistent-state/run-finalizer.ts +192 -0
  17. package/src/features/persistent-state/run-journal.ts +15 -4
  18. package/src/hooks/agent-tracker.ts +15 -0
  19. package/src/hooks/event-hook.ts +93 -1
  20. package/src/hooks/system-prompt-hook.ts +20 -0
  21. package/src/hooks/tool-tracking-hook.ts +263 -33
  22. package/src/shared/audit-artifact-resolver.ts +75 -0
  23. package/src/shared/drop-diagnostics.ts +108 -0
  24. package/src/shared/file-utils.ts +7 -2
  25. package/src/shared/index.ts +14 -0
  26. package/src/shared/path-root-resolver.ts +34 -0
  27. package/src/shared/report-path-resolver.ts +70 -0
  28. package/src/solodit-lifecycle.ts +86 -7
  29. package/src/state/adapters.ts +262 -0
  30. package/src/state/index.ts +15 -0
  31. package/src/state/projectors.ts +437 -0
  32. package/src/state/schemas.ts +453 -0
  33. package/src/state/types.ts +6 -0
  34. package/src/tools/report-generator-tool.ts +647 -36
  35. package/src/tools/report-preflight.ts +79 -0
  36. package/src/tools/solodit-search-tool.ts +15 -24
  37. 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
- const fallback = await callSoloditHttp(query, limit, args.severity, port)
302
- if (fallback.error || hadMcpError) {
303
- return fallback
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 {