/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { execFile as execFileCb } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { promisify } from 'node:util';
import { addEvidenceRecord } from './orchestration-records.js';
import { readJsonlFile } from './file-writer.js';
const execFile = promisify(execFileCb);
export function parseNpmAudit(stdout) {
    let parsed;
    try {
        parsed = JSON.parse(stdout);
    }
    catch {
        return { blocking: false, detail: 'npm audit output could not be parsed' };
    }
    const vulns = parsed.metadata?.vulnerabilities ?? {};
    const high = vulns.high ?? 0;
    const critical = vulns.critical ?? 0;
    const blocking = high > 0 || critical > 0;
    const detail = blocking ? `Found ${critical} critical and ${high} high CVEs` : buildVulnSummary(vulns);
    return { blocking, detail };
}
function buildVulnSummary(vulns) {
    const parts = Object.entries(vulns)
        .filter(([, count]) => (count ?? 0) > 0)
        .map(([sev, count]) => `${count ?? 0} ${sev}`)
        .join(', ');
    return parts ? `Vulnerabilities: ${parts}` : 'No vulnerabilities found';
}
export function parseKnip(stdout) {
    let parsed;
    try {
        parsed = JSON.parse(stdout);
    }
    catch {
        return { hasIssues: false, detail: 'knip output could not be parsed' };
    }
    const unusedFiles = parsed.files?.length ?? 0;
    const issueCount = parsed.issues?.length ?? 0;
    const total = unusedFiles + issueCount;
    return {
        hasIssues: total > 0,
        detail: total > 0 ? `${unusedFiles} unused files, ${issueCount} unused exports/deps` : 'No dead code found',
    };
}
export function parseSecretScan(stdout) {
    let parsed;
    try {
        parsed = JSON.parse(stdout);
    }
    catch {
        return { blocking: false, detail: 'secret-scan output could not be parsed' };
    }
    const count = parsed.hits?.length ?? 0;
    return {
        blocking: count > 0,
        detail: count > 0
            ? `Found ${count ?? 0} potential secret(s): ${parsed.hits
                .slice(0, 3)
                .map((h) => h.pattern)
                .join(', ')}`
            : 'No secrets found',
    };
}
export function parseCoverageOutput(stdout) {
    // Node built-in test coverage outputs a summary line like:
    // all files | 80% | 60% | 90% | 90% |
    const match = /all files\s*\|\s*([\d.]+)%/i.exec(stdout);
    if (match)
        return { detail: `Coverage: ${match[1]}%` };
    // Fallback: look for "Coverage: X%" in output
    const fallback = /coverage[^\d]{0,20}([\d.]+)%/i.exec(stdout);
    if (fallback)
        return { detail: `Coverage: ${fallback[1]}%` };
    return { detail: 'Coverage report generated (see output for details)' };
}
// ─── individual checks ─────────────────────────────────────────────────────
async function runNpmAudit(cwd, taskId) {
    let stdout = '';
    let exitCode = 0;
    try {
        const result = await execFile('npm', ['audit', '--json'], { cwd });
        stdout = result.stdout;
    }
    catch (err) {
        const e = err;
        stdout = e.stdout ?? '';
        exitCode = e.code ?? 1;
    }
    const { blocking, detail } = parseNpmAudit(stdout);
    let status;
    if (blocking) {
        status = 'fail';
    }
    else {
        status = exitCode === 0 ? 'pass' : 'warn';
    }
    const evidence = addEvidenceRecord(cwd, {
        summary: `npm audit: ${detail}`,
        profile: 'devops',
        evidenceKind: 'command',
        command: 'npm audit --json',
        exitCode,
        details: detail,
        taskIds: [taskId],
    });
    return { name: 'npm-audit', status, blocking, detail, evidenceId: evidence.id };
}
async function runKnip(cwd, taskId) {
    let stdout = '';
    let exitCode = 0;
    try {
        const result = await execFile('npx', ['knip', '--reporter', 'json'], { cwd });
        stdout = result.stdout;
    }
    catch (err) {
        const e = err;
        stdout = e.stdout ?? '';
        exitCode = e.code ?? 1;
    }
    const { hasIssues, detail } = parseKnip(stdout);
    const status = hasIssues ? 'warn' : 'pass';
    const evidence = addEvidenceRecord(cwd, {
        summary: `knip: ${detail}`,
        profile: 'devops',
        evidenceKind: 'report',
        command: 'npx knip --reporter json',
        exitCode,
        details: detail,
        taskIds: [taskId],
    });
    return { name: 'knip', status, blocking: false, detail, evidenceId: evidence.id };
}
async function runCoverage(cwd, taskId) {
    let stdout = '';
    let exitCode = 0;
    try {
        const result = await execFile('node', ['--test', '--experimental-test-coverage'], { cwd });
        stdout = result.stdout + result.stderr;
    }
    catch (err) {
        const e = err;
        stdout = (e.stdout ?? '') + (e.stderr ?? '');
        exitCode = e.code ?? 1;
    }
    const { detail } = parseCoverageOutput(stdout);
    const status = exitCode === 0 ? 'pass' : 'warn';
    const evidence = addEvidenceRecord(cwd, {
        summary: `coverage: ${detail}`,
        profile: 'devops',
        evidenceKind: 'report',
        command: 'node --test --experimental-test-coverage',
        exitCode,
        details: detail,
        taskIds: [taskId],
    });
    return { name: 'coverage', status, blocking: false, detail, evidenceId: evidence.id };
}
async function runSecretScan(cwd, taskId) {
    const scanScript = join(cwd, 'scripts', 'secret-scan.js');
    let stdout = '';
    let exitCode = 0;
    try {
        const result = await execFile('node', [scanScript, cwd], { cwd });
        stdout = result.stdout;
    }
    catch (err) {
        const e = err;
        stdout = e.stdout ?? '';
        exitCode = e.code ?? 1;
    }
    const { blocking, detail } = parseSecretScan(stdout);
    const status = blocking ? 'fail' : 'pass';
    const evidence = addEvidenceRecord(cwd, {
        summary: `secret-scan: ${detail}`,
        profile: 'devops',
        evidenceKind: 'command',
        command: 'node scripts/secret-scan.js',
        exitCode,
        details: detail,
        taskIds: [taskId],
    });
    return { name: 'secret-scan', status, blocking, detail, evidenceId: evidence.id };
}
// ─── withTimeout utility ──────────────────────────────────────────────────────
export async function withTimeout(fn, ms) {
    let timer;
    const timeout = new Promise((_, reject) => {
        timer = setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms);
    });
    try {
        return await Promise.race([fn(), timeout]);
    }
    finally {
        if (timer !== undefined)
            clearTimeout(timer);
    }
}
// ─── config loader ────────────────────────────────────────────────────────────
const DEFAULT_GATE_CONFIG = {
    criteria: {
        'ac-evidence-mapping': { enabled: true, classification: 'blocking' },
        'code-analyzer': { enabled: true, classification: 'blocking' },
        'open-blockers': { enabled: true, classification: 'blocking' },
        'threat-model': { enabled: true, classification: 'blocking' },
        'performance-baseline': { enabled: true, classification: 'blocking' },
        'docs-link-check': { enabled: true, classification: 'blocking' },
        'deprecation-policy': { enabled: true, classification: 'blocking' },
        'migration-path': { enabled: true, classification: 'blocking' },
        changelog: { enabled: true, classification: 'blocking' },
        'risk-governance': { enabled: true, classification: 'advisory' },
        'e2e-suite': { enabled: true, classification: 'blocking' },
        'manual-qa-signoff': { enabled: true, classification: 'advisory' },
        'npm-audit': { enabled: true, classification: 'blocking' },
        knip: { enabled: true, classification: 'advisory' },
        coverage: { enabled: true, classification: 'blocking' },
        'secret-scan': { enabled: true, classification: 'blocking' },
    },
    timeoutMs: 15_000,
    totalTimeoutMs: 30_000,
};
export function loadGateConfig(cwd) {
    const configPath = join(cwd, '.setup-agents', 'release-gate.json');
    if (existsSync(configPath)) {
        try {
            const raw = JSON.parse(readFileSync(configPath, 'utf8'));
            const merged = {
                criteria: { ...DEFAULT_GATE_CONFIG.criteria, ...(raw.criteria ?? {}) },
                timeoutMs: raw.timeoutMs ?? DEFAULT_GATE_CONFIG.timeoutMs,
                totalTimeoutMs: raw.totalTimeoutMs ?? DEFAULT_GATE_CONFIG.totalTimeoutMs,
            };
            return { config: merged, source: configPath };
        }
        catch {
            return { config: DEFAULT_GATE_CONFIG, source: 'default (config parse error)' };
        }
    }
    return { config: DEFAULT_GATE_CONFIG, source: 'default' };
}
const CRITERION_META = [
    { id: 'ac-evidence-mapping', name: 'AC-Evidence Mapping', number: 1 },
    { id: 'code-analyzer', name: 'Code Analyzer', number: 2 },
    { id: 'open-blockers', name: 'Open Blockers', number: 3 },
    { id: 'threat-model', name: 'Threat Model', number: 4 },
    { id: 'performance-baseline', name: 'Performance Baseline', number: 5 },
    { id: 'docs-link-check', name: 'Docs Link Check', number: 6 },
    { id: 'deprecation-policy', name: 'Deprecation Policy', number: 7 },
    { id: 'migration-path', name: 'Migration Path', number: 8 },
    { id: 'changelog', name: 'Changelog', number: 9 },
    { id: 'risk-governance', name: 'Risk Governance', number: 10 },
    { id: 'e2e-suite', name: 'E2E Suite', number: 11 },
    { id: 'manual-qa-signoff', name: 'Manual QA Signoff', number: 12 },
    { id: 'npm-audit', name: 'NPM Audit', number: 13 },
    { id: 'knip', name: 'Knip Dead Code', number: 14 },
    { id: 'coverage', name: 'Test Coverage', number: 15 },
    { id: 'secret-scan', name: 'Secret Scan', number: 16 },
];
function loadTasksJson(cwd) {
    const tasksPath = join(cwd, '.agent-workflow', 'tasks.json');
    if (!existsSync(tasksPath))
        return [];
    try {
        const content = readFileSync(tasksPath, 'utf8');
        return JSON.parse(content);
    }
    catch {
        return [];
    }
}
function loadEvidenceRecords(cwd) {
    const evidencePath = join(cwd, '.setup-agents', 'state', 'evidence.jsonl');
    if (!existsSync(evidencePath))
        return [];
    return readJsonlFile(evidencePath);
}
function runAcEvidenceMapping(cwd, taskId) {
    const tasks = loadTasksJson(cwd);
    const task = tasks.find((t) => t.id === taskId);
    const ac = task?.acceptanceCriteria ?? [];
    if (ac.length === 0) {
        return Promise.resolve({
            name: 'ac-evidence-mapping',
            status: 'warn',
            blocking: false,
            detail: 'No acceptance criteria defined for task',
        });
    }
    const evidence = loadEvidenceRecords(cwd).filter((e) => e.taskIds?.includes(taskId));
    const unmapped = ac.filter((criterion) => {
        const normalized = criterion.toLowerCase();
        return !evidence.some((e) => e.summary?.toLowerCase().includes(normalized.slice(0, 30)));
    });
    if (unmapped.length === 0) {
        return Promise.resolve({
            name: 'ac-evidence-mapping',
            status: 'pass',
            blocking: false,
            detail: `All ${ac.length} AC mapped to evidence`,
        });
    }
    return Promise.resolve({
        name: 'ac-evidence-mapping',
        status: 'fail',
        blocking: true,
        detail: `${unmapped.length}/${ac.length} AC lack evidence mapping: ${unmapped.slice(0, 2).join('; ')}`,
    });
}
async function runCodeAnalyzer(cwd) {
    // Check if sf code-analyzer is available
    try {
        await execFile('sf', ['code-analyzer', '--help'], { cwd, timeout: 5000 });
    }
    catch {
        return {
            name: 'code-analyzer',
            status: 'warn',
            blocking: false,
            detail: 'sf code-analyzer not installed (graceful skip)',
        };
    }
    let stdout = '';
    let exitCode = 0;
    try {
        const result = await execFile('sf', ['code-analyzer', 'run', '--target', '.', '--output-format', 'json', '--severity-threshold', '1'], { cwd, timeout: 60_000 });
        stdout = result.stdout;
    }
    catch (err) {
        const e = err;
        stdout = e.stdout ?? '';
        exitCode = e.code ?? 1;
    }
    let violations = 0;
    try {
        const parsed = JSON.parse(stdout);
        violations = (parsed.violations ?? []).filter((v) => (v.severity ?? 99) <= 2).length;
    }
    catch {
        if (exitCode !== 0) {
            return { name: 'code-analyzer', status: 'fail', blocking: true, detail: 'Code analyzer failed to parse output' };
        }
    }
    if (violations > 0) {
        return {
            name: 'code-analyzer',
            status: 'fail',
            blocking: true,
            detail: `${violations} critical/high severity violations`,
        };
    }
    return { name: 'code-analyzer', status: 'pass', blocking: false, detail: 'No critical/high violations' };
}
function runOpenBlockers(cwd, taskId) {
    const tasks = loadTasksJson(cwd);
    const blockedTasks = tasks.filter((t) => t.status === 'blocked' && t.id !== taskId);
    if (blockedTasks.length > 0) {
        return Promise.resolve({
            name: 'open-blockers',
            status: 'fail',
            blocking: true,
            detail: `${blockedTasks.length} blocked task(s): ${blockedTasks
                .slice(0, 3)
                .map((t) => t.id)
                .join(', ')}`,
        });
    }
    return Promise.resolve({
        name: 'open-blockers',
        status: 'pass',
        blocking: false,
        detail: 'No blocked tasks',
    });
}
function runThreatModel(cwd) {
    const threatModelPaths = [
        join(cwd, 'docs', 'threat-model.md'),
        join(cwd, 'THREAT_MODEL.md'),
        join(cwd, '.setup-agents', 'threat-model.md'),
    ];
    const found = threatModelPaths.find((p) => existsSync(p));
    if (found) {
        return Promise.resolve({
            name: 'threat-model',
            status: 'pass',
            blocking: false,
            detail: `Threat model present: ${found}`,
        });
    }
    return Promise.resolve({
        name: 'threat-model',
        status: 'fail',
        blocking: true,
        detail: 'No threat model document found',
    });
}
function runPerformanceBaseline(cwd) {
    const baselinePaths = [
        join(cwd, 'docs', 'performance-baseline.md'),
        join(cwd, '.setup-agents', 'performance-baseline.json'),
        join(cwd, 'benchmarks', 'baseline.json'),
    ];
    const found = baselinePaths.find((p) => existsSync(p));
    if (found) {
        return Promise.resolve({
            name: 'performance-baseline',
            status: 'pass',
            blocking: false,
            detail: `Performance baseline present: ${found}`,
        });
    }
    return Promise.resolve({
        name: 'performance-baseline',
        status: 'fail',
        blocking: true,
        detail: 'No performance baseline document found',
    });
}
function runDocsLinkCheck(cwd) {
    const readmePath = join(cwd, 'README.md');
    if (!existsSync(readmePath)) {
        return Promise.resolve({
            name: 'docs-link-check',
            status: 'warn',
            blocking: false,
            detail: 'No README.md found to check links',
        });
    }
    const content = readFileSync(readmePath, 'utf8');
    // Check for relative links that point to non-existent files
    const linkPattern = /\[([^\]]*)\]\(([^)]+)\)/g;
    let match;
    const brokenLinks = [];
    match = linkPattern.exec(content);
    while (match !== null) {
        const target = match[2];
        // Only check relative links (not http/https/mailto)
        if (!target.startsWith('http') && !target.startsWith('mailto:') && !target.startsWith('#')) {
            const targetPath = join(cwd, target.split('#')[0]);
            if (!existsSync(targetPath)) {
                brokenLinks.push(target);
            }
        }
        match = linkPattern.exec(content);
    }
    if (brokenLinks.length > 0) {
        return Promise.resolve({
            name: 'docs-link-check',
            status: 'fail',
            blocking: true,
            detail: `${brokenLinks.length} broken link(s): ${brokenLinks.slice(0, 3).join(', ')}`,
        });
    }
    return Promise.resolve({
        name: 'docs-link-check',
        status: 'pass',
        blocking: false,
        detail: 'All relative doc links valid',
    });
}
function runDeprecationPolicy(cwd) {
    const policyPaths = [
        join(cwd, 'docs', 'deprecation-policy.md'),
        join(cwd, 'DEPRECATION.md'),
        join(cwd, '.setup-agents', 'deprecation-policy.md'),
    ];
    const found = policyPaths.find((p) => existsSync(p));
    if (found) {
        return Promise.resolve({
            name: 'deprecation-policy',
            status: 'pass',
            blocking: false,
            detail: `Deprecation policy present: ${found}`,
        });
    }
    return Promise.resolve({
        name: 'deprecation-policy',
        status: 'fail',
        blocking: true,
        detail: 'No deprecation policy document found',
    });
}
function runMigrationPath(cwd) {
    const migrationPaths = [
        join(cwd, 'docs', 'migration.md'),
        join(cwd, 'MIGRATION.md'),
        join(cwd, '.setup-agents', 'migration-path.md'),
    ];
    const found = migrationPaths.find((p) => existsSync(p));
    if (found) {
        return Promise.resolve({
            name: 'migration-path',
            status: 'pass',
            blocking: false,
            detail: `Migration path documented: ${found}`,
        });
    }
    return Promise.resolve({
        name: 'migration-path',
        status: 'fail',
        blocking: true,
        detail: 'No migration path document found',
    });
}
function runChangelog(cwd) {
    const changelogPaths = [join(cwd, 'CHANGELOG.md'), join(cwd, 'changelog.md'), join(cwd, 'docs', 'CHANGELOG.md')];
    const found = changelogPaths.find((p) => existsSync(p));
    if (found) {
        return Promise.resolve({
            name: 'changelog',
            status: 'pass',
            blocking: false,
            detail: `Changelog present: ${found}`,
        });
    }
    return Promise.resolve({
        name: 'changelog',
        status: 'fail',
        blocking: true,
        detail: 'No CHANGELOG.md found',
    });
}
function runRiskGovernance(cwd, taskId) {
    const tasks = loadTasksJson(cwd);
    const task = tasks.find((t) => t.id === taskId);
    const risks = task?.risks ?? [];
    if (risks.length === 0) {
        return Promise.resolve({
            name: 'risk-governance',
            status: 'warn',
            blocking: false,
            detail: 'No risks declared for task (advisory)',
        });
    }
    // Check if risks have corresponding evidence/decisions
    const evidence = loadEvidenceRecords(cwd).filter((e) => e.taskIds?.includes(taskId));
    const mitigated = risks.filter((risk) => {
        const normalized = risk.toLowerCase().slice(0, 25);
        return evidence.some((e) => e.summary?.toLowerCase().includes(normalized));
    });
    if (mitigated.length < risks.length) {
        return Promise.resolve({
            name: 'risk-governance',
            status: 'warn',
            blocking: false,
            detail: `${mitigated.length}/${risks.length} risks have mitigation evidence`,
        });
    }
    return Promise.resolve({
        name: 'risk-governance',
        status: 'pass',
        blocking: false,
        detail: `All ${risks.length} risks mitigated`,
    });
}
function runE2eSuite(cwd) {
    // Check for e2e test directory presence
    const e2ePaths = [
        join(cwd, 'test', 'e2e'),
        join(cwd, 'tests', 'e2e'),
        join(cwd, 'e2e'),
        join(cwd, 'test', 'integration'),
    ];
    const found = e2ePaths.find((p) => existsSync(p));
    if (!found) {
        return Promise.resolve({
            name: 'e2e-suite',
            status: 'fail',
            blocking: true,
            detail: 'No e2e/integration test directory found',
        });
    }
    return Promise.resolve({
        name: 'e2e-suite',
        status: 'pass',
        blocking: false,
        detail: `E2E test suite present: ${found}`,
    });
}
function runManualQaSignoff(cwd, taskId) {
    // Check for a QA review record that approves this task
    const reviewsPath = join(cwd, '.setup-agents', 'state', 'reviews.jsonl');
    if (!existsSync(reviewsPath)) {
        return Promise.resolve({
            name: 'manual-qa-signoff',
            status: 'warn',
            blocking: false,
            detail: 'No QA signoff record found (advisory)',
        });
    }
    const reviews = readJsonlFile(reviewsPath);
    const qaApproval = reviews.find((r) => r.taskIds?.includes(taskId) && r.status === 'approved');
    if (qaApproval) {
        return Promise.resolve({
            name: 'manual-qa-signoff',
            status: 'pass',
            blocking: false,
            detail: 'QA signoff approved',
        });
    }
    return Promise.resolve({
        name: 'manual-qa-signoff',
        status: 'warn',
        blocking: false,
        detail: 'No QA approval review found (advisory)',
    });
}
const LEGACY_CHECK_RUNNERS = {
    'npm-audit': runNpmAudit,
    knip: runKnip,
    coverage: runCoverage,
    'secret-scan': runSecretScan,
};
const LEGACY_ALL_CHECKS = ['npm-audit', 'knip', 'coverage', 'secret-scan'];
export async function runReleaseChecks(cwd, taskId, checks = LEGACY_ALL_CHECKS) {
    const validChecks = checks.filter((c) => c in LEGACY_CHECK_RUNNERS);
    const results = await Promise.all(validChecks.map((c) => LEGACY_CHECK_RUNNERS[c](cwd, taskId)));
    const overallPass = results.every((r) => !r.blocking || r.status !== 'fail');
    return {
        taskId,
        checks: results,
        overallPass,
        runAt: new Date().toISOString(),
    };
}
// ─── 12-criterion release gate (new) ─────────────────────────────────────────
const GATE_RUNNERS = {
    'ac-evidence-mapping': runAcEvidenceMapping,
    'code-analyzer': (cwd) => runCodeAnalyzer(cwd),
    'open-blockers': runOpenBlockers,
    'threat-model': (cwd) => runThreatModel(cwd),
    'performance-baseline': (cwd) => runPerformanceBaseline(cwd),
    'docs-link-check': (cwd) => runDocsLinkCheck(cwd),
    'deprecation-policy': (cwd) => runDeprecationPolicy(cwd),
    'migration-path': (cwd) => runMigrationPath(cwd),
    changelog: (cwd) => runChangelog(cwd),
    'risk-governance': runRiskGovernance,
    'e2e-suite': (cwd) => runE2eSuite(cwd),
    'manual-qa-signoff': runManualQaSignoff,
    'npm-audit': runNpmAudit,
    knip: runKnip,
    coverage: runCoverage,
    'secret-scan': runSecretScan,
};
export async function runReleaseGate(cwd, taskId, config) {
    const startTime = Date.now();
    const { config: gateConfig, source } = config ? { config, source: 'provided' } : loadGateConfig(cwd);
    const enabledCriteria = CRITERION_META.filter((m) => gateConfig.criteria[m.id]?.enabled !== false);
    const results = await Promise.allSettled(enabledCriteria.map(async (meta) => {
        const criterionStart = Date.now();
        const classification = gateConfig.criteria[meta.id]?.classification ?? 'blocking';
        try {
            const result = await withTimeout(() => GATE_RUNNERS[meta.id](cwd, taskId), gateConfig.timeoutMs);
            const isBlocking = classification === 'blocking' && result.status === 'fail';
            return {
                id: meta.id,
                name: meta.name,
                number: meta.number,
                status: result.status,
                classification,
                blocking: isBlocking,
                detail: result.detail,
                evidenceId: result.evidenceId,
                executionTimeMs: Date.now() - criterionStart,
            };
        }
        catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            return {
                id: meta.id,
                name: meta.name,
                number: meta.number,
                status: 'fail',
                classification,
                blocking: classification === 'blocking',
                detail: `Error: ${message}`,
                executionTimeMs: Date.now() - criterionStart,
            };
        }
    }));
    const criteria = results.map((r) => {
        if (r.status === 'fulfilled')
            return r.value;
        // Should not happen with our try/catch, but handle rejected promises
        return {
            id: 'npm-audit',
            name: 'Unknown',
            number: 0,
            status: 'fail',
            classification: 'blocking',
            blocking: true,
            detail: `Unexpected rejection: ${r.reason}`,
            executionTimeMs: 0,
        };
    });
    const overallPass = criteria.every((c) => !c.blocking);
    const advisoryWarnings = criteria
        .filter((c) => c.classification === 'advisory' && c.status !== 'pass')
        .map((c) => `[${c.name}] ${c.detail}`);
    return {
        taskId,
        criteria,
        overallPass,
        advisoryWarnings,
        executionTimeMs: Date.now() - startTime,
        runAt: new Date().toISOString(),
        configSource: source,
    };
}
//# sourceMappingURL=release-check.js.map