mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
- Added a scheduled task to clean up stale validation entries every hour, preventing memory leaks. - Enhanced the `getAllValidations` function to read validation files in parallel for improved performance. - Updated the `fetchLinkedPRs` function to use `spawn` for safer execution of GitHub CLI commands, mitigating shell injection risks. - Modified event handling in the GitHub issues view to utilize the model for validation, ensuring consistency and reducing stale closure issues. - Introduced a new property in the issue validation event to track the model used for validation.
182 lines
5.5 KiB
TypeScript
182 lines
5.5 KiB
TypeScript
/**
|
|
* 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<void> {
|
|
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<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 {
|
|
// 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<StoredValidation[]> {
|
|
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<boolean> {
|
|
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<boolean> {
|
|
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<number> {
|
|
const validations = await getAllValidations(projectPath);
|
|
return validations.filter((v) => !v.viewedAt && !isValidationStale(v)).length;
|
|
}
|