refactor: consolidate duplicate types and file I/O utilities
- Remove 4 duplicate file I/O functions from audit/utils.ts, re-export from utils/file-io.ts - Consolidate AgentEndResult interface into new types/audit.ts - Use exported AgentDefinition from types/agents.ts in session-manager.ts - Rename AgentMetrics to AgentAuditMetrics to disambiguate from temporal/shared.ts
This commit is contained in:
@@ -17,21 +17,11 @@ import { MetricsTracker } from './metrics-tracker.js';
|
|||||||
import { initializeAuditStructure, type SessionMetadata } from './utils.js';
|
import { initializeAuditStructure, type SessionMetadata } from './utils.js';
|
||||||
import { formatTimestamp } from '../utils/formatting.js';
|
import { formatTimestamp } from '../utils/formatting.js';
|
||||||
import { SessionMutex } from '../utils/concurrency.js';
|
import { SessionMutex } from '../utils/concurrency.js';
|
||||||
|
import type { AgentEndResult } from '../types/index.js';
|
||||||
|
|
||||||
// Global mutex instance
|
// Global mutex instance
|
||||||
const sessionMutex = new SessionMutex();
|
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
|
* AuditSession - Main audit system facade
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
import { atomicWrite, readJson, fileExists } from '../utils/file-io.js';
|
import { atomicWrite, readJson, fileExists } from '../utils/file-io.js';
|
||||||
import { formatTimestamp, calculatePercentage } from '../utils/formatting.js';
|
import { formatTimestamp, calculatePercentage } from '../utils/formatting.js';
|
||||||
import { AGENT_PHASE_MAP, type PhaseName } from '../session-manager.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 {
|
interface AttemptData {
|
||||||
attempt_number: number;
|
attempt_number: number;
|
||||||
@@ -30,7 +30,7 @@ interface AttemptData {
|
|||||||
error?: string | undefined;
|
error?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AgentMetrics {
|
interface AgentAuditMetrics {
|
||||||
status: 'in-progress' | 'success' | 'failed';
|
status: 'in-progress' | 'success' | 'failed';
|
||||||
attempts: AttemptData[];
|
attempts: AttemptData[];
|
||||||
final_duration_ms: number;
|
final_duration_ms: number;
|
||||||
@@ -68,21 +68,10 @@ interface SessionData {
|
|||||||
total_duration_ms: number;
|
total_duration_ms: number;
|
||||||
total_cost_usd: number;
|
total_cost_usd: number;
|
||||||
phases: Record<string, PhaseMetrics>;
|
phases: Record<string, PhaseMetrics>;
|
||||||
agents: Record<string, AgentMetrics>;
|
agents: Record<string, AgentAuditMetrics>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
interface ActiveTimer {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
attemptNumber: number;
|
attemptNumber: number;
|
||||||
@@ -326,9 +315,9 @@ export class MetricsTracker {
|
|||||||
* Calculate phase-level metrics
|
* Calculate phase-level metrics
|
||||||
*/
|
*/
|
||||||
private calculatePhaseMetrics(
|
private calculatePhaseMetrics(
|
||||||
successfulAgents: Array<[string, AgentMetrics]>
|
successfulAgents: Array<[string, AgentAuditMetrics]>
|
||||||
): Record<string, PhaseMetrics> {
|
): Record<string, PhaseMetrics> {
|
||||||
const phases: Record<PhaseName, AgentMetrics[]> = {
|
const phases: Record<PhaseName, AgentAuditMetrics[]> = {
|
||||||
'pre-recon': [],
|
'pre-recon': [],
|
||||||
'recon': [],
|
'recon': [],
|
||||||
'vulnerability-analysis': [],
|
'vulnerability-analysis': [],
|
||||||
|
|||||||
+4
-59
@@ -15,6 +15,10 @@ import fs from 'fs/promises';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
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 __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
@@ -93,65 +97,6 @@ export function generateWorkflowLogPath(sessionMetadata: SessionMetadata): strin
|
|||||||
return path.join(auditPath, 'workflow.log');
|
return path.join(auditPath, 'workflow.log');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure directory exists (idempotent, race-safe)
|
|
||||||
*/
|
|
||||||
export async function ensureDirectory(dirPath: string): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
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<T = unknown>(filePath: string): Promise<T> {
|
|
||||||
const content = await fs.readFile(filePath, 'utf8');
|
|
||||||
return JSON.parse(content) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if file exists
|
|
||||||
*/
|
|
||||||
export async function fileExists(filePath: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
await fs.access(filePath);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize audit directory structure for a session
|
* Initialize audit directory structure for a session
|
||||||
* Creates: audit-logs/{sessionId}/, agents/, prompts/, deliverables/
|
* Creates: audit-logs/{sessionId}/, agents/, prompts/, deliverables/
|
||||||
|
|||||||
@@ -4,14 +4,7 @@
|
|||||||
// it under the terms of the GNU Affero General Public License version 3
|
// it under the terms of the GNU Affero General Public License version 3
|
||||||
// as published by the Free Software Foundation.
|
// as published by the Free Software Foundation.
|
||||||
|
|
||||||
import type { AgentName } from './types/index.js';
|
import type { AgentName, AgentDefinition } from './types/index.js';
|
||||||
|
|
||||||
// Agent definition interface
|
|
||||||
interface AgentDefinition {
|
|
||||||
name: AgentName;
|
|
||||||
displayName: string;
|
|
||||||
prerequisites: AgentName[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Agent definitions according to PRD
|
// Agent definitions according to PRD
|
||||||
export const AGENTS: Readonly<Record<AgentName, AgentDefinition>> = Object.freeze({
|
export const AGENTS: Readonly<Record<AgentName, AgentDefinition>> = Object.freeze({
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -11,3 +11,4 @@
|
|||||||
export * from './errors.js';
|
export * from './errors.js';
|
||||||
export * from './config.js';
|
export * from './config.js';
|
||||||
export * from './agents.js';
|
export * from './agents.js';
|
||||||
|
export * from './audit.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user