diff --git a/src/audit/audit-session.ts b/src/audit/audit-session.ts index 6e09133..7e8cc6a 100644 --- a/src/audit/audit-session.ts +++ b/src/audit/audit-session.ts @@ -17,21 +17,11 @@ import { MetricsTracker } from './metrics-tracker.js'; import { initializeAuditStructure, type SessionMetadata } from './utils.js'; import { formatTimestamp } from '../utils/formatting.js'; import { SessionMutex } from '../utils/concurrency.js'; +import type { AgentEndResult } from '../types/index.js'; // Global mutex instance const sessionMutex = new SessionMutex(); -interface AgentEndResult { - attemptNumber: number; - duration_ms: number; - cost_usd: number; - success: boolean; - model?: string | undefined; - error?: string | undefined; - checkpoint?: string | undefined; - isFinalAttempt?: boolean | undefined; -} - /** * AuditSession - Main audit system facade */ diff --git a/src/audit/metrics-tracker.ts b/src/audit/metrics-tracker.ts index 4462d90..89cdf2b 100644 --- a/src/audit/metrics-tracker.ts +++ b/src/audit/metrics-tracker.ts @@ -18,7 +18,7 @@ import { import { atomicWrite, readJson, fileExists } from '../utils/file-io.js'; import { formatTimestamp, calculatePercentage } from '../utils/formatting.js'; import { AGENT_PHASE_MAP, type PhaseName } from '../session-manager.js'; -import type { AgentName } from '../types/index.js'; +import type { AgentName, AgentEndResult } from '../types/index.js'; interface AttemptData { attempt_number: number; @@ -30,7 +30,7 @@ interface AttemptData { error?: string | undefined; } -interface AgentMetrics { +interface AgentAuditMetrics { status: 'in-progress' | 'success' | 'failed'; attempts: AttemptData[]; final_duration_ms: number; @@ -68,21 +68,10 @@ interface SessionData { total_duration_ms: number; total_cost_usd: number; phases: Record; - agents: Record; + agents: Record; }; } -interface AgentEndResult { - attemptNumber: number; - duration_ms: number; - cost_usd: number; - success: boolean; - model?: string | undefined; - error?: string | undefined; - checkpoint?: string | undefined; - isFinalAttempt?: boolean | undefined; -} - interface ActiveTimer { startTime: number; attemptNumber: number; @@ -326,9 +315,9 @@ export class MetricsTracker { * Calculate phase-level metrics */ private calculatePhaseMetrics( - successfulAgents: Array<[string, AgentMetrics]> + successfulAgents: Array<[string, AgentAuditMetrics]> ): Record { - const phases: Record = { + const phases: Record = { 'pre-recon': [], 'recon': [], 'vulnerability-analysis': [], diff --git a/src/audit/utils.ts b/src/audit/utils.ts index 4b024d3..6e91964 100644 --- a/src/audit/utils.ts +++ b/src/audit/utils.ts @@ -15,6 +15,10 @@ import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; +// Import and re-export file I/O utilities from canonical location +import { ensureDirectory, atomicWrite, readJson, fileExists } from '../utils/file-io.js'; +export { ensureDirectory, atomicWrite, readJson, fileExists }; + const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -93,65 +97,6 @@ export function generateWorkflowLogPath(sessionMetadata: SessionMetadata): strin return path.join(auditPath, 'workflow.log'); } -/** - * Ensure directory exists (idempotent, race-safe) - */ -export async function ensureDirectory(dirPath: string): Promise { - try { - await fs.mkdir(dirPath, { recursive: true }); - } catch (error) { - // Ignore EEXIST errors (race condition safe) - if ((error as NodeJS.ErrnoException).code !== 'EEXIST') { - throw error; - } - } -} - -/** - * Atomic write using temp file + rename pattern - * Guarantees no partial writes or corruption on crash - */ -export async function atomicWrite(filePath: string, data: object | string): Promise { - const tempPath = `${filePath}.tmp`; - const content = typeof data === 'string' ? data : JSON.stringify(data, null, 2); - - try { - // Write to temp file - await fs.writeFile(tempPath, content, 'utf8'); - - // Atomic rename (POSIX guarantee: atomic on same filesystem) - await fs.rename(tempPath, filePath); - } catch (error) { - // Clean up temp file on failure - try { - await fs.unlink(tempPath); - } catch { - // Ignore cleanup errors - } - throw error; - } -} - -/** - * Read and parse JSON file - */ -export async function readJson(filePath: string): Promise { - const content = await fs.readFile(filePath, 'utf8'); - return JSON.parse(content) as T; -} - -/** - * Check if file exists - */ -export async function fileExists(filePath: string): Promise { - try { - await fs.access(filePath); - return true; - } catch { - return false; - } -} - /** * Initialize audit directory structure for a session * Creates: audit-logs/{sessionId}/, agents/, prompts/, deliverables/ diff --git a/src/session-manager.ts b/src/session-manager.ts index 5733740..4e7fced 100644 --- a/src/session-manager.ts +++ b/src/session-manager.ts @@ -4,14 +4,7 @@ // it under the terms of the GNU Affero General Public License version 3 // as published by the Free Software Foundation. -import type { AgentName } from './types/index.js'; - -// Agent definition interface -interface AgentDefinition { - name: AgentName; - displayName: string; - prerequisites: AgentName[]; -} +import type { AgentName, AgentDefinition } from './types/index.js'; // Agent definitions according to PRD export const AGENTS: Readonly> = Object.freeze({ diff --git a/src/types/audit.ts b/src/types/audit.ts new file mode 100644 index 0000000..efa9326 --- /dev/null +++ b/src/types/audit.ts @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Keygraph, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 +// as published by the Free Software Foundation. + +/** + * Audit system type definitions + */ + +/** + * Result data passed to audit system when an agent execution ends. + * Used by both AuditSession and MetricsTracker. + */ +export interface AgentEndResult { + attemptNumber: number; + duration_ms: number; + cost_usd: number; + success: boolean; + model?: string | undefined; + error?: string | undefined; + checkpoint?: string | undefined; + isFinalAttempt?: boolean | undefined; +} diff --git a/src/types/index.ts b/src/types/index.ts index 9d7088d..8968dd3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -11,3 +11,4 @@ export * from './errors.js'; export * from './config.js'; export * from './agents.js'; +export * from './audit.js';