// engine/validator/gates/test-gates.ts — test file existence and runner gates
import { readFile, access, readdir } from 'node:fs/promises';
import { join } from 'node:path';
/**
 * Derive expected test file path from a source file path.
 * src/foo/bar.ts -> tests/foo/bar.test.ts
 */
function sourceToTestPath(projectPath, sourceFile) {
    // Normalize: strip leading project path if absolute
    const relative = sourceFile.startsWith(projectPath)
        ? sourceFile.slice(projectPath.length).replace(/^\//, '')
        : sourceFile;
    const withoutSrc = relative.replace(/^src\//, '');
    const withoutExt = withoutSrc.replace(/\.[^.]+$/, '');
    return join(projectPath, 'tests', `${withoutExt}.test.ts`);
}
/**
 * Extract source files referenced in spec acceptance criteria text.
 * Looks for `src/...` paths in the content.
 */
function extractSourceFilesFromText(content) {
    const pattern = /src\/[\w/.%-]+\.(?:ts|js|tsx|jsx)/g;
    const matches = content.match(pattern) ?? [];
    return [...new Set(matches)];
}
/**
 * Gate: test-files-gate
 * Checks if test counterparts exist for source files mentioned in spec criteria/progress.
 */
export async function runTestFilesGate(projectPath, specCriteria, progressContent) {
    const combinedContent = [specCriteria, progressContent ?? ''].join('\n');
    const sourceFiles = extractSourceFilesFromText(combinedContent);
    if (sourceFiles.length === 0) {
        return {
            gate: 'test-files-gate',
            status: 'skipped',
            reason: 'no source files detected in spec',
        };
    }
    const missingTests = [];
    for (const srcFile of sourceFiles) {
        const testPath = sourceToTestPath(projectPath, srcFile);
        try {
            await access(testPath);
        }
        catch {
            missingTests.push(testPath.replace(projectPath + '/', ''));
        }
    }
    if (missingTests.length === sourceFiles.length) {
        // None found at all — probably wrong heuristic, skip instead of fail
        return {
            gate: 'test-files-gate',
            status: 'skipped',
            reason: 'no matching test files found (heuristic may not apply)',
        };
    }
    if (missingTests.length > 0) {
        return {
            gate: 'test-files-gate',
            status: 'failed',
            detail: `Missing test files: ${missingTests.join(', ')}`,
        };
    }
    return { gate: 'test-files-gate', status: 'passed' };
}
/**
 * Gate: test-runner-gate
 * Checks if package.json has a test script AND tests/ directory exists.
 */
export async function runTestRunnerGate(projectPath) {
    // Check tests/ directory exists
    try {
        const testsDir = join(projectPath, 'tests');
        const entries = await readdir(testsDir);
        if (entries.length === 0) {
            return { gate: 'test-runner-gate', status: 'skipped', reason: 'tests directory is empty' };
        }
    }
    catch {
        return { gate: 'test-runner-gate', status: 'skipped', reason: 'tests directory not found' };
    }
    // Check package.json has test script
    try {
        const pkgPath = join(projectPath, 'package.json');
        const raw = await readFile(pkgPath, 'utf-8');
        const pkg = JSON.parse(raw);
        if (pkg.scripts?.test) {
            return { gate: 'test-runner-gate', status: 'passed' };
        }
        return {
            gate: 'test-runner-gate',
            status: 'failed',
            detail: 'No "test" script in package.json',
        };
    }
    catch {
        return {
            gate: 'test-runner-gate',
            status: 'skipped',
            reason: 'package.json not found or unreadable',
        };
    }
}
//# sourceMappingURL=test-gates.js.map