// @crash-shield-ignore-file — config/cache reader for Planu-controlled JSON; writer is this codebase, shape guaranteed by build/seed.
// engine/client-detection.ts — SPEC-645: 4-signal cascade LLM client detection
import { readFile, writeFile, access, mkdir } from 'node:fs/promises';
import { join } from 'node:path';
import { getConnectedClient } from './llm-runtime/index.js';
// Signal 2: file paths that indicate a specific client
const FILE_SIGNALS = [
    { path: '.cursor', client: 'cursor' },
    { path: '.windsurf', client: 'windsurf' },
    { path: '.github/copilot-instructions.md', client: 'copilot' },
    { path: '.agents/skills', client: 'codex' },
    { path: '.claude', client: 'claude-code' },
];
// Signal 3: env var names that indicate a specific client
const ENV_SIGNALS = [
    { vars: ['CURSOR_AI', 'CURSOR_TRACE_ID'], client: 'cursor' },
    { vars: ['WINDSURF_MODE', 'WINDSURF_AGENT'], client: 'windsurf' },
    { vars: ['GITHUB_COPILOT_ENABLED', 'COPILOT_AGENT'], client: 'copilot' },
    { vars: ['CODEX_AGENT', 'OPENAI_CODEX'], client: 'codex' },
    { vars: ['CLAUDE_CODE_ENTRYPOINT', 'ANTHROPIC_CLAUDE_CODE'], client: 'claude-code' },
    { vars: ['GEMINI_API_KEY', 'GEMINI_MODEL'], client: 'gemini' },
];
/** Normalize clientInfo.name → LLMClientKind */
function normalizeClientName(name) {
    const lower = name.toLowerCase();
    if (lower.includes('cursor')) {
        return 'cursor';
    }
    if (lower.includes('windsurf')) {
        return 'windsurf';
    }
    if (lower.includes('copilot')) {
        return 'copilot';
    }
    if (lower.includes('codex')) {
        return 'codex';
    }
    if (lower.includes('claude')) {
        return 'claude-code';
    }
    if (lower.includes('gemini')) {
        return 'gemini';
    }
    return 'unknown';
}
async function pathExists(p) {
    try {
        await access(p);
        return true;
    }
    catch {
        return false;
    }
}
/** Signal 1: MCP handshake clientInfo.name */
function detectFromHandshake() {
    const client = getConnectedClient();
    if (!client?.name) {
        return null;
    }
    const kind = normalizeClientName(client.name);
    return kind === 'unknown' ? null : kind;
}
/** Signal 2: workspace file signals */
async function detectFromFiles(projectPath) {
    for (const signal of FILE_SIGNALS) {
        if (await pathExists(join(projectPath, signal.path))) {
            return signal.client;
        }
    }
    return null;
}
/** Signal 3: environment variables */
function detectFromEnv() {
    for (const signal of ENV_SIGNALS) {
        if (signal.vars.some((v) => process.env[v])) {
            return signal.client;
        }
    }
    return null;
}
/**
 * SPEC-645: Run the 4-signal detection cascade and return the detected client.
 * Order: handshake → file signals → env vars → fallback "unknown"
 */
export async function detectLLMClient(projectPath) {
    return (detectFromHandshake() ?? (await detectFromFiles(projectPath)) ?? detectFromEnv() ?? 'unknown');
}
/**
 * SPEC-645: Detect and cache client in planu/conventions.json.
 * No-op if detectedClient is already cached and forceRefresh is false.
 */
export async function detectAndCacheClient(projectPath, forceRefresh = false) {
    const conventionsPath = join(projectPath, 'planu', 'conventions.json');
    let existing = null;
    try {
        const raw = await readFile(conventionsPath, 'utf-8');
        existing = JSON.parse(raw);
    }
    catch {
        // file absent — ok
    }
    if (existing?.detectedClient && !forceRefresh) {
        return existing.detectedClient;
    }
    const detected = await detectLLMClient(projectPath);
    const updated = existing
        ? { ...existing, detectedClient: detected }
        : {
            detectedAt: new Date().toISOString(),
            stack: {
                language: 'unknown',
                framework: null,
                testRunner: null,
                linter: null,
                formatter: null,
                bundler: null,
            },
            conventions: {
                fileNaming: 'unknown',
                functionNaming: 'unknown',
                testLocation: 'unknown',
                importStyle: 'unknown',
                errorHandling: 'unknown',
            },
            baseline: { testCount: 0, lintWarnings: null, coveragePercent: null },
            detectedClient: detected,
        };
    await mkdir(join(projectPath, 'planu'), { recursive: true });
    await writeFile(conventionsPath, JSON.stringify(updated, null, 2) + '\n', 'utf-8');
    return detected;
}
//# sourceMappingURL=client-detection.js.map