// engine/skill-registry/skill-security-scanner.ts — SPEC-1081
// Optional SkillSpector adapter for scanning external AI agent skills.
import { execFile } from 'node:child_process';
const SCANNER = 'skillspector';
const COMMAND = 'skillspector scan <skillDir> --no-llm --format json';
export class SkillSecurityScannerUnavailableError extends Error {
    constructor(skillName, cause) {
        const detail = cause instanceof Error ? cause.message : String(cause);
        super(`Skill security scanner unavailable for "${skillName}". Command: ${COMMAND}. ${detail}`);
        this.name = 'SkillSecurityScannerUnavailableError';
    }
}
function normalizeSeverity(value) {
    if (value === 'LOW' || value === 'MEDIUM' || value === 'HIGH' || value === 'CRITICAL') {
        return value;
    }
    return 'UNKNOWN';
}
function normalizeRecommendation(value) {
    if (value === 'SAFE' || value === 'CAUTION' || value === 'DO_NOT_INSTALL') {
        return value;
    }
    return 'UNKNOWN';
}
function normalizeScore(value) {
    if (typeof value !== 'number' || !Number.isFinite(value)) {
        return 100;
    }
    return Math.max(0, Math.min(100, value));
}
function parseReport(stdout, skillName) {
    let parsed;
    try {
        parsed = JSON.parse(stdout);
    }
    catch (err) {
        throw new SkillSecurityScannerUnavailableError(skillName, err);
    }
    const risk = parsed.risk_assessment ?? {};
    const version = parsed.metadata?.skillspector_version;
    return {
        scanner: SCANNER,
        scannerVersion: typeof version === 'string' ? version : undefined,
        scannedAt: new Date().toISOString(),
        score: normalizeScore(risk.score),
        severity: normalizeSeverity(risk.severity),
        recommendation: normalizeRecommendation(risk.recommendation),
        issuesCount: Array.isArray(parsed.issues) ? parsed.issues.length : 0,
        command: COMMAND,
        available: true,
    };
}
function runSkillSpector(skillDir) {
    return new Promise((resolve, reject) => {
        execFile(SCANNER, ['scan', skillDir, '--no-llm', '--format', 'json'], (err, stdout) => {
            if (err) {
                const error = err instanceof Error ? err : new Error('Unknown execFile error');
                reject(Object.assign(error, { stdout }));
                return;
            }
            resolve(stdout);
        });
    });
}
/**
 * Scan a prepared skill directory using SkillSpector static analysis only.
 *
 * SkillSpector returns exit code 1 for high-risk reports. Planu still parses stdout
 * in that case because the report is valid and should drive the gate decision.
 */
export async function scanSkillSecurity(skillDir, skillName) {
    try {
        const stdout = await runSkillSpector(skillDir);
        return parseReport(stdout, skillName);
    }
    catch (err) {
        if (typeof err === 'object' &&
            err !== null &&
            'stdout' in err &&
            typeof err.stdout === 'string' &&
            err.stdout.trim().length > 0) {
            return parseReport(err.stdout, skillName);
        }
        throw new SkillSecurityScannerUnavailableError(skillName, err);
    }
}
//# sourceMappingURL=skill-security-scanner.js.map