/** * Validation Storage - CRUD operations for GitHub issue validation results * * Stores validation results in .automaker/validations/{issueNumber}/validation.json * Results include the validation verdict, metadata, and timestamp for cache invalidation. */ import * as secureFs from './secure-fs.js'; import { getValidationsDir, getValidationDir, getValidationPath } from '@automaker/platform'; import type { StoredValidation } from '@automaker/types'; // Re-export StoredValidation for convenience export type { StoredValidation }; /** Number of hours before a validation is considered stale */ const VALIDATION_CACHE_TTL_HOURS = 24; /** * Write validation result to storage * * Creates the validation directory if needed and stores the result as JSON. * * @param projectPath - Absolute path to project directory * @param issueNumber - GitHub issue number * @param data - Validation data to store */ export async function writeValidation( projectPath: string, issueNumber: number, data: StoredValidation ): Promise { const validationDir = getValidationDir(projectPath, issueNumber); const validationPath = getValidationPath(projectPath, issueNumber); // Ensure directory exists await secureFs.mkdir(validationDir, { recursive: true }); // Write validation result await secureFs.writeFile(validationPath, JSON.stringify(data, null, 2), 'utf-8'); } /** * Read validation result from storage * * @param projectPath - Absolute path to project directory * @param issueNumber - GitHub issue number * @returns Stored validation or null if not found */ export async function readValidation( projectPath: string, issueNumber: number ): Promise { try { const validationPath = getValidationPath(projectPath, issueNumber); const content = (await secureFs.readFile(validationPath, 'utf-8')) as string; return JSON.parse(content) as StoredValidation; } catch { // File doesn't exist or can't be read return null; } } /** * Get all stored validations for a project * * @param projectPath - Absolute path to project directory * @returns Array of stored validations */ export async function getAllValidations(projectPath: string): Promise { const validationsDir = getValidationsDir(projectPath); try { const dirs = await secureFs.readdir(validationsDir, { withFileTypes: true }); // Read all validation files in parallel for better performance const promises = dirs .filter((dir) => dir.isDirectory()) .map((dir) => { const issueNumber = parseInt(dir.name, 10); if (!isNaN(issueNumber)) { return readValidation(projectPath, issueNumber); } return Promise.resolve(null); }); const results = await Promise.all(promises); const validations = results.filter((v): v is StoredValidation => v !== null); // Sort by issue number validations.sort((a, b) => a.issueNumber - b.issueNumber); return validations; } catch { // Directory doesn't exist return []; } } /** * Delete a validation from storage * * @param projectPath - Absolute path to project directory * @param issueNumber - GitHub issue number * @returns true if validation was deleted, false if not found */ export async function deleteValidation(projectPath: string, issueNumber: number): Promise { try { const validationDir = getValidationDir(projectPath, issueNumber); await secureFs.rm(validationDir, { recursive: true, force: true }); return true; } catch { return false; } } /** * Check if a validation is stale (older than TTL) * * @param validation - Stored validation to check * @returns true if validation is older than 24 hours */ export function isValidationStale(validation: StoredValidation): boolean { const validatedAt = new Date(validation.validatedAt); const now = new Date(); const hoursDiff = (now.getTime() - validatedAt.getTime()) / (1000 * 60 * 60); return hoursDiff > VALIDATION_CACHE_TTL_HOURS; } /** * Get validation with freshness info * * @param projectPath - Absolute path to project directory * @param issueNumber - GitHub issue number * @returns Object with validation and isStale flag, or null if not found */ export async function getValidationWithFreshness( projectPath: string, issueNumber: number ): Promise<{ validation: StoredValidation; isStale: boolean } | null> { const validation = await readValidation(projectPath, issueNumber); if (!validation) { return null; } return { validation, isStale: isValidationStale(validation), }; } /** * Mark a validation as viewed by the user * * @param projectPath - Absolute path to project directory * @param issueNumber - GitHub issue number * @returns true if validation was marked as viewed, false if not found */ export async function markValidationViewed( projectPath: string, issueNumber: number ): Promise { const validation = await readValidation(projectPath, issueNumber); if (!validation) { return false; } validation.viewedAt = new Date().toISOString(); await writeValidation(projectPath, issueNumber, validation); return true; } /** * Get count of unviewed, non-stale validations for a project * * @param projectPath - Absolute path to project directory * @returns Number of unviewed validations */ export async function getUnviewedValidationsCount(projectPath: string): Promise { const validations = await getAllValidations(projectPath); return validations.filter((v) => !v.viewedAt && !isValidationStale(v)).length; }