// Planu — Agent Framework Detector
// SPEC-016a: Detects AI agent frameworks, project types, and multi-agent topology.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
// === Package detection helpers ===
async function loadPackageJson(projectPath) {
    try {
        const content = await readFile(join(projectPath, 'package.json'), 'utf-8');
        return JSON.parse(content);
    }
    catch /* v8 ignore next 2 */ {
        return {};
    }
}
function allDeps(pkg) {
    return [...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})];
}
async function readPyRequirements(projectPath) {
    for (const file of ['requirements.txt', 'pyproject.toml', 'setup.py']) {
        try {
            const content = await readFile(join(projectPath, file), 'utf-8');
            return content.toLowerCase().split('\n');
        }
        catch {
            // ignore
        }
    }
    return [];
}
// === Framework detection ===
/**
 * Detect agent framework from package manifests.
 * Criteria 13: detects Claude SDK, OpenAI Assistants, LangGraph, CrewAI, AutoGen,
 * Vercel AI SDK, Mastra, Semantic Kernel.
 */
export async function detectAgentFramework(projectPath) {
    const [pkg, pyLines] = await Promise.all([
        loadPackageJson(projectPath),
        readPyRequirements(projectPath),
    ]);
    const deps = allDeps(pkg);
    // Claude SDK
    if (deps.some((d) => d === '@anthropic-ai/sdk' || d === '@anthropic-ai/claude-code')) {
        return 'claude-sdk';
    }
    // OpenAI Assistants (openai package with threads/runs usage)
    if (deps.includes('openai')) {
        try {
            const indexContent = await readFile(join(projectPath, 'index.ts'), 'utf-8')
                .catch(async () => readFile(join(projectPath, 'index.js'), 'utf-8'))
                .catch(() => '');
            if (indexContent.includes('threads') || indexContent.includes('.runs.')) {
                return 'openai-assistants';
            }
        }
        catch {
            // not an assistant project
        }
    }
    // LangGraph
    if (deps.includes('langgraph') || pyLines.some((l) => l.includes('langgraph'))) {
        return 'langgraph';
    }
    // CrewAI
    if (pyLines.some((l) => l.includes('crewai'))) {
        return 'crewai';
    }
    // AutoGen
    if (pyLines.some((l) => l.includes('autogen') || l.includes('pyautogen'))) {
        return 'autogen';
    }
    // Vercel AI SDK
    if (deps.includes('ai') && deps.some((d) => d.startsWith('@ai-sdk/'))) {
        return 'vercel-ai';
    }
    // Mastra
    if (deps.includes('@mastra/core') || deps.some((d) => d.startsWith('@mastra/'))) {
        return 'mastra';
    }
    // Semantic Kernel
    if (pyLines.some((l) => l.includes('semantic-kernel')) ||
        deps.some((d) => d.includes('semantic-kernel'))) {
        return 'semantic-kernel';
    }
    return null;
}
// === LLM model detection ===
export async function detectLlmModel(projectPath) {
    for (const file of ['index.ts', 'index.js', 'agent.ts', 'agent.py', 'main.py']) {
        try {
            const content = await readFile(join(projectPath, file), 'utf-8');
            const modelMatch = /model:\s*["']([^"']+)["']/.exec(content) ??
                /model=["']([^"']+)["']/.exec(content) ??
                /claude-[\w.-]+/.exec(content) ??
                /gpt-[\w.-]+/.exec(content);
            if (modelMatch?.[1]) {
                return modelMatch[1];
            }
            if (modelMatch?.[0]) {
                return modelMatch[0];
            }
        }
        catch {
            // ignore
        }
    }
    return null;
}
// === Memory persistence detection ===
export async function detectMemoryPersistence(projectPath) {
    const [pkg, pyLines] = await Promise.all([
        loadPackageJson(projectPath),
        readPyRequirements(projectPath),
    ]);
    const deps = allDeps(pkg);
    if (deps.includes('mem0ai') || deps.includes('@mem0ai/mem0')) {
        return 'mem0';
    }
    if (deps.some((d) => d.includes('pinecone')) || pyLines.some((l) => l.includes('pinecone'))) {
        return 'pinecone';
    }
    if (deps.some((d) => d.includes('chroma') || d.includes('chromadb')) ||
        pyLines.some((l) => l.includes('chromadb'))) {
        return 'chromadb';
    }
    if (deps.some((d) => d.includes('weaviate')) || pyLines.some((l) => l.includes('weaviate'))) {
        return 'weaviate';
    }
    if (deps.some((d) => d.includes('qdrant')) || pyLines.some((l) => l.includes('qdrant'))) {
        return 'qdrant';
    }
    return null;
}
// === Tool detection ===
export async function detectAgentTools(projectPath) {
    const tools = [];
    const pkg = await loadPackageJson(projectPath);
    const deps = allDeps(pkg);
    const toolMappings = [
        [['@modelcontextprotocol/sdk'], 'mcp'],
        [['langchain', 'langchain-core'], 'langchain-tools'],
        [['tavily'], 'tavily-search'],
        [['@anthropic-ai/sdk'], 'computer-use'],
        [['axios', 'node-fetch', 'undici'], 'http-client'],
        [['@playwright/test', 'puppeteer'], 'browser-automation'],
        [['zod'], 'schema-validation'],
    ];
    for (const [packages, toolName] of toolMappings) {
        if (packages.some((p) => deps.includes(p))) {
            tools.push(toolName);
        }
    }
    return tools;
}
// === Project type detection ===
/**
 * Criteria 14: Detects agent-with-tools vs LLM pipeline vs RAG system vs multi-agent router.
 */
export async function detectAgentProjectType(projectPath, framework) {
    if (!framework) {
        return null;
    }
    const [pkg, pyLines] = await Promise.all([
        loadPackageJson(projectPath),
        readPyRequirements(projectPath),
    ]);
    const deps = allDeps(pkg);
    // RAG system: has vector store + embeddings
    const hasVectorStore = deps.some((d) => ['pinecone', 'chromadb', 'weaviate', 'qdrant', '@supabase/supabase-js'].some((v) => d.includes(v))) ||
        /* v8 ignore next 1 */ // Python vector-store path not reached in JS-only test projects
        pyLines.some((l) => ['chromadb', 'pinecone', 'weaviate', 'qdrant'].some((v) => l.includes(v)));
    const hasEmbeddings = deps.some((d) => ['openai', '@anthropic-ai/sdk', '@ai-sdk/openai'].includes(d)) ||
        /* v8 ignore next 1 */ // Python embeddings path not reached in JS-only test projects
        pyLines.some((l) => l.includes('embedding'));
    if (hasVectorStore && hasEmbeddings) {
        return 'rag-system';
    }
    // Multi-agent router: langgraph, crewai, autogen typically orchestrate multiple agents
    if (framework === 'langgraph' || framework === 'crewai' || framework === 'autogen') {
        return 'multi-agent-router';
    }
    // LLM pipeline: basic SDK usage without tools
    const hasTools = deps.some((d) => d.includes('@modelcontextprotocol') || d.includes('langchain')) ||
        /* v8 ignore next 1 */ // Python tools path not reached in JS-only test projects
        pyLines.some((l) => l.includes('tool') || l.includes('function_call'));
    if (!hasTools) {
        return 'llm-pipeline';
    }
    // Default: agent with tools
    return 'agent-with-tools';
}
// === Topology detection ===
/**
 * Criteria 15: Detects hub-and-spoke, swarm, or sequential-pipeline topology.
 */
export async function detectMultiAgentTopology(projectPath, framework) {
    if (!framework) {
        return null;
    }
    for (const file of ['index.ts', 'index.js', 'main.py', 'workflow.py', 'crew.py', 'graph.py']) {
        try {
            const content = await readFile(join(projectPath, file), 'utf-8').catch(() => '');
            if (!content) {
                continue;
            }
            const lower = content.toLowerCase();
            if (lower.includes('swarm') || lower.includes('peer') || lower.includes('handoff')) {
                return 'swarm';
            }
            if (lower.includes('pipeline') ||
                lower.includes('sequential') ||
                lower.includes('chain') ||
                lower.includes('add_edge')) {
                return 'sequential-pipeline';
            }
            if (lower.includes('coordinator') ||
                lower.includes('router') ||
                lower.includes('orchestrator') ||
                lower.includes('dispatcher')) {
                return 'hub-and-spoke';
            }
        }
        catch {
            // ignore
        }
    }
    if (framework === 'langgraph') {
        return 'sequential-pipeline';
    }
    /* v8 ignore start */ // crewai topology path not covered by agent-framework-detector tests
    if (framework === 'crewai') {
        return 'hub-and-spoke';
    }
    return null;
    /* v8 ignore stop */
}
// === Main public API ===
/**
 * Full agent project knowledge detection.
 * Called from analyzer.ts after standard project analysis.
 */
export async function detectAgentProjectKnowledge(projectPath) {
    const framework = await detectAgentFramework(projectPath);
    const [llmModel, memoryPersistence, detectedTools, projectType, topology] = await Promise.all([
        detectLlmModel(projectPath),
        detectMemoryPersistence(projectPath),
        detectAgentTools(projectPath),
        detectAgentProjectType(projectPath, framework),
        detectMultiAgentTopology(projectPath, framework),
    ]);
    return {
        framework,
        llmModel,
        memoryPersistence,
        detectedTools,
        projectType,
        topology,
    };
}
//# sourceMappingURL=agent-framework-detector.js.map