// engine/config-health/mcp-config-checker.ts — SPEC-1062
// Detects unsafe Planu MCP host configuration.
import { access, readFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { isAbsolute, join } from 'node:path';
async function fileExists(path) {
    try {
        await access(path);
        return true;
    }
    catch {
        return false;
    }
}
async function readIfExists(path) {
    try {
        return await readFile(path, 'utf-8');
    }
    catch {
        return null;
    }
}
function replacementFix() {
    return 'Replace the Planu MCP entry with command "npx", args ["-y", "@planu/cli@latest"], and startup_timeout_sec 120 where the host supports it.';
}
async function checkPathTarget(configFile, target) {
    if (!target || !isAbsolute(target) || (await fileExists(target))) {
        return [];
    }
    return [
        {
            severity: 'error',
            checker: 'mcp-config',
            file: configFile,
            message: `Planu MCP config points to missing path '${target}'`,
            fix: replacementFix(),
        },
    ];
}
async function checkJsonConfig(basePath, relativePath, displayPath = relativePath) {
    const filePath = join(basePath, relativePath);
    const raw = await readIfExists(filePath);
    if (!raw) {
        return [];
    }
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch {
        return [];
    }
    const planu = parsed.mcpServers?.planu;
    if (!planu) {
        return [];
    }
    const findings = await checkPathTarget(displayPath, planu.command);
    for (const arg of planu.args ?? []) {
        findings.push(...(await checkPathTarget(displayPath, arg)));
    }
    return findings;
}
function extractPlanuCodexServer(content) {
    const serverBlocks = content.split(/\n(?=\[\[mcp\.servers\]\])/);
    return serverBlocks.find((block) => /name\s*=\s*"planu"/.test(block)) ?? null;
}
function extractTomlString(block, key) {
    const match = new RegExp(`^${key}\\s*=\\s*"([^"]+)"`, 'm').exec(block);
    return match?.[1];
}
function extractTomlStringArray(block, key) {
    const match = new RegExp(`^${key}\\s*=\\s*\\[([^\\]]*)\\]`, 'm').exec(block);
    const body = match?.[1];
    if (!body) {
        return [];
    }
    return [...body.matchAll(/"([^"]+)"/g)].map((item) => item[1] ?? '');
}
async function checkCodexConfig(basePath, relativePath, displayPath = relativePath) {
    const raw = await readIfExists(join(basePath, relativePath));
    if (!raw) {
        return [];
    }
    const planuBlock = extractPlanuCodexServer(raw);
    if (!planuBlock) {
        return [];
    }
    const findings = await checkPathTarget(displayPath, extractTomlString(planuBlock, 'command'));
    for (const arg of extractTomlStringArray(planuBlock, 'args')) {
        findings.push(...(await checkPathTarget(displayPath, arg)));
    }
    const timeoutMatch = /^startup_timeout_sec\s*=\s*(\d+)/m.exec(planuBlock);
    const timeout = timeoutMatch?.[1] ? Number(timeoutMatch[1]) : 0;
    if (timeout < 60) {
        findings.push({
            severity: 'warning',
            checker: 'mcp-config',
            file: displayPath,
            message: 'Planu Codex MCP config is missing startup_timeout_sec >= 60',
            fix: 'Add startup_timeout_sec = 120 to the planu MCP server block.',
        });
    }
    return findings;
}
function uniqueLocations(locations) {
    const seen = new Set();
    return locations.filter((location) => {
        const key = join(location.basePath, location.relativePath);
        if (seen.has(key)) {
            return false;
        }
        seen.add(key);
        return true;
    });
}
export async function checkMcpConfig(projectPath, options = {}) {
    const homePath = options.homePath ?? homedir();
    const jsonLocations = uniqueLocations([
        { basePath: projectPath, relativePath: '.mcp.json' },
        { basePath: projectPath, relativePath: '.claude.json' },
        { basePath: projectPath, relativePath: '.claude/claude.json' },
        { basePath: homePath, relativePath: '.claude.json', displayPath: '~/.claude.json' },
        {
            basePath: homePath,
            relativePath: '.claude/claude.json',
            displayPath: '~/.claude/claude.json',
        },
    ]);
    const codexLocations = uniqueLocations([
        { basePath: projectPath, relativePath: '.openai/config.toml' },
        { basePath: homePath, relativePath: '.codex/config.toml', displayPath: '~/.codex/config.toml' },
        {
            basePath: homePath,
            relativePath: '.openai/config.toml',
            displayPath: '~/.openai/config.toml',
        },
    ]);
    const results = await Promise.all([
        ...jsonLocations.map((location) => checkJsonConfig(location.basePath, location.relativePath, location.displayPath)),
        ...codexLocations.map((location) => checkCodexConfig(location.basePath, location.relativePath, location.displayPath)),
    ]);
    return results.flat();
}
//# sourceMappingURL=mcp-config-checker.js.map