| |
| |
| |
| |
| |
| |
|
|
| import * as secureFs from './secure-fs.js'; |
| import { getValidationsDir, getValidationDir, getValidationPath } from '@automaker/platform'; |
| import type { StoredValidation } from '@automaker/types'; |
|
|
| |
| export type { StoredValidation }; |
|
|
| |
| const VALIDATION_CACHE_TTL_HOURS = 24; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function writeValidation( |
| projectPath: string, |
| issueNumber: number, |
| data: StoredValidation |
| ): Promise<void> { |
| const validationDir = getValidationDir(projectPath, issueNumber); |
| const validationPath = getValidationPath(projectPath, issueNumber); |
|
|
| |
| await secureFs.mkdir(validationDir, { recursive: true }); |
|
|
| |
| await secureFs.writeFile(validationPath, JSON.stringify(data, null, 2), 'utf-8'); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export async function readValidation( |
| projectPath: string, |
| issueNumber: number |
| ): Promise<StoredValidation | null> { |
| try { |
| const validationPath = getValidationPath(projectPath, issueNumber); |
| const content = (await secureFs.readFile(validationPath, 'utf-8')) as string; |
| return JSON.parse(content) as StoredValidation; |
| } catch { |
| |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export async function getAllValidations(projectPath: string): Promise<StoredValidation[]> { |
| const validationsDir = getValidationsDir(projectPath); |
|
|
| try { |
| const dirs = await secureFs.readdir(validationsDir, { withFileTypes: true }); |
|
|
| |
| 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); |
|
|
| |
| validations.sort((a, b) => a.issueNumber - b.issueNumber); |
|
|
| return validations; |
| } catch { |
| |
| return []; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export async function deleteValidation(projectPath: string, issueNumber: number): Promise<boolean> { |
| try { |
| const validationDir = getValidationDir(projectPath, issueNumber); |
| await secureFs.rm(validationDir, { recursive: true, force: true }); |
| return true; |
| } catch { |
| return false; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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), |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export async function markValidationViewed( |
| projectPath: string, |
| issueNumber: number |
| ): Promise<boolean> { |
| const validation = await readValidation(projectPath, issueNumber); |
| if (!validation) { |
| return false; |
| } |
|
|
| validation.viewedAt = new Date().toISOString(); |
| await writeValidation(projectPath, issueNumber, validation); |
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export async function getUnviewedValidationsCount(projectPath: string): Promise<number> { |
| const validations = await getAllValidations(projectPath); |
| return validations.filter((v) => !v.viewedAt && !isValidationStale(v)).length; |
| } |
|
|