/**
 * Pure scoring, planning, and analysis functions extracted from OrchestratorEngine.
 * Zero side effects, no class dependencies — safe to call from any module.
 * Re-exported from the orchestrator barrel so callers keep the same import path.
 */
import * as fs from 'fs';
import * as path from 'path';
import { ABSOLUTE_SECONDARY_MIN, REPO_SEARCH_HEAD_BYTES } from './types.js';
// ─── String / scoring utilities ───────────────────────────────────────────────
export function dedupeStrings(values) {
    return [...new Set(values.filter(Boolean))];
}
export function shortLabel(value, limit) {
    const normalized = String(value ?? '').trim();
    if (normalized.length <= limit)
        return normalized;
    return `${normalized.slice(0, Math.max(0, limit - 1)).trimEnd()}…`;
}
export function extractKeywords(value) {
    return value
        .toLowerCase()
        .split(/[^a-z0-9]+/)
        .filter((token) => token.length >= 3)
        .filter((token) => !['with', 'from', 'this', 'that', 'then', 'also', 'into', 'about'].includes(token));
}
export function scoreText(value, keywords, intent) {
    const lower = value.toLowerCase();
    // TF-IDF inspired logic: Term frequency with logarithmic normalization
    let score = keywords.reduce((sum, keyword) => {
        // Avoid regex errors with special characters in keyword
        const safeKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        const matchCount = (lower.match(new RegExp(`\\b${safeKeyword}\\b`, 'g')) || []).length;
        const tfScore = matchCount > 0 ? (1 + Math.log10(matchCount)) * 3 : 0;
        const partialScore = tfScore === 0 && lower.includes(keyword) ? 1.5 : 0;
        return sum + tfScore + partialScore;
    }, 0);
    // Math-based vector intent gravity
    if (intent) {
        if (intent.taskType === 'frontend' && (lower.includes('react') || lower.includes('ui') || lower.includes('css') || lower.includes('component')))
            score += 15;
        if (intent.taskType === 'backend' && (lower.includes('api') || lower.includes('node') || lower.includes('sql') || lower.includes('server') || lower.includes('database')))
            score += 15;
        if (intent.taskType === 'ai' && (lower.includes('prompt') || lower.includes('llm') || lower.includes('embedding') || lower.includes('model') || lower.includes('rag')))
            score += 15;
        if ((intent.taskType === 'ops' || intent.taskType === 'infra') && (lower.includes('docker') || lower.includes('ci/cd') || lower.includes('kubernetes') || lower.includes('deploy') || lower.includes('devops')))
            score += 15;
        if (intent.taskType === 'pm' && (lower.includes('plan') || lower.includes('product') || lower.includes('jira') || lower.includes('roadmap') || lower.includes('requirements')))
            score += 15;
        if (intent.taskType === 'marketing' && (lower.includes('seo') || lower.includes('copy') || lower.includes('marketing') || lower.includes('campaign')))
            score += 15;
        if (intent.taskType === 'sales' && (lower.includes('lead') || lower.includes('pitch') || lower.includes('prospect')))
            score += 15;
        if (intent.taskType === 'data' && (lower.includes('analysis') || lower.includes('query') || lower.includes('dashboard') || lower.includes('metrics')))
            score += 15;
        if (intent.taskType === 'test' && (lower.includes('test') || lower.includes('jest') || lower.includes('e2e') || lower.includes('cypress') || lower.includes('qa')))
            score += 15;
        const technicalIntent = ['bugfix', 'feature', 'review', 'refactor', 'test', 'frontend', 'backend', 'ai'].includes(intent.taskType);
        if (technicalIntent && /(marketing|seo|campaign|social|sales|gtm|product requirement|roadmap|pdlc)/.test(lower))
            score -= 18;
        if (intent.taskType === 'backend' && /(landing page|copywriting|campaign|social|seo)/.test(lower))
            score -= 22;
        if (intent.taskType === 'frontend' && /(seo|campaign|sales|gtm)/.test(lower) && !/(ui|component|layout|design system)/.test(lower))
            score -= 12;
    }
    return score;
}
export function scorePath(filePath, keywords) {
    const normalized = filePath.toLowerCase();
    const baseScore = keywords.reduce((sum, keyword) => sum + (normalized.includes(keyword) ? 4 : 0), 0);
    if (normalized.includes('/src/'))
        return baseScore + 1;
    if (normalized.endsWith('.md'))
        return baseScore + 1;
    return baseScore;
}
// ─── Task analysis ─────────────────────────────────────────────────────────────
export function decomposeTask(task) {
    const normalized = task
        .split(/\n+/)
        .map((segment) => segment.trim())
        .filter(Boolean)
        .join(' ');
    const subtasks = normalized
        .split(/\s*(?:[.;,]|\band\s+then\b|\bthen\b|\bafter\s+that\b|\bfollowed\s+by\b|\balso\b)\s*/i)
        .map((segment) => segment.trim())
        .filter(Boolean);
    return subtasks.length > 0 ? subtasks : [task];
}
export function classifyIntent(task) {
    const lowerTask = task.toLowerCase();
    const taskTokens = new Set(lowerTask.replace(/[^a-z0-9\s]/g, ' ').split(/\s+/).filter((word) => word.length > 2));
    const intents = {
        release: { keywords: ['release', 'publish', 'tag', 'version', 'deploy', 'bump'], phrases: ['release readiness', 'ship it'] },
        feature: { keywords: ['feature', 'add', 'implement', 'build', 'create', 'develop', 'enable', 'introduce', 'extend', 'enhance'], weight: 0.95 },
        review: { keywords: ['review', 'audit', 'check', 'inspect', 'assess', 'verify', 'readiness', 'cto'], phrases: ['lead review', 'production ready', 'world class'] },
        research: { keywords: ['research', 'explore', 'discover', 'spike', 'feasibility', 'investigate', 'analyze', 'evaluate'], weight: 0.9 },
        refactor: { keywords: ['refactor', 'rewrite', 'restructure', 'clean', 'extract', 'reorganize', 'harden'], phrases: ['tech debt'] },
        bugfix: { keywords: ['fix', 'bug', 'broken', 'issue', 'error', 'crash', 'exception', 'patch', 'resolve', 'lag', 'slow'], phrases: ['not working', 'super buggy'] },
        ops: { keywords: ['deploy', 'monitor', 'ops', 'infra', 'devops', 'docker', 'kubernetes', 'pipeline', 'workflow'], phrases: ['ci/cd'] },
        pm: { keywords: ['pm', 'roadmap', 'requirements', 'scope', 'prd', 'story', 'plan', 'spec', 'ticket', 'epic'], phrases: ['user story', 'product requirement'], weight: 0.8 },
        test: { keywords: ['test', 'coverage', 'jest', 'cypress', 'e2e', 'unit', 'integration', 'qa', 'validate', 'regression'], weight: 0.92 },
        frontend: { keywords: ['frontend', 'ui', 'ux', 'react', 'css', 'styling', 'components', 'layout', 'design', 'html', 'page', 'dashboard'], phrases: ['user experience', 'design system'], weight: 1.05 },
        backend: { keywords: ['backend', 'api', 'server', 'database', 'sql', 'endpoint', 'controller', 'service', 'binding', 'runtime', 'synapse', 'architects', 'orchestration', 'pod'], phrases: ['control plane', 'multi agent', 'multiagent'], weight: 1.08 },
        ai: { keywords: ['ai', 'ml', 'llm', 'rag', 'embedding', 'prompt', 'inference', 'agent', 'model'], weight: 0.95 },
        marketing: { keywords: ['marketing', 'seo', 'copywriting', 'campaign', 'blog', 'content', 'growth', 'social', 'email'], weight: 0.65 },
        sales: { keywords: ['sales', 'pitch', 'lead', 'prospecting', 'outreach', 'crm', 'deal', 'quote'], weight: 0.65 },
        data: { keywords: ['analysis', 'data', 'metrics', 'query', 'analytics', 'report', 'etl', 'bi'], phrases: ['data warehouse'], weight: 0.72 },
    };
    const scoreKeyword = (keyword) => {
        if (taskTokens.has(keyword))
            return 1;
        if (lowerTask.includes(keyword))
            return keyword.includes(' ') ? 1.15 : 0.55;
        return 0;
    };
    const intentScores = {};
    for (const [type, config] of Object.entries(intents)) {
        let score = 0;
        for (const keyword of config.keywords) {
            score += scoreKeyword(keyword);
        }
        for (const phrase of config.phrases ?? []) {
            if (lowerTask.includes(phrase))
                score += 1.35;
        }
        intentScores[type] = score * (config.weight ?? 1);
    }
    const hasTechnicalScope = /(dashboard|frontend|backend|api|runtime|control plane|synapse|architect|pod|integration|binding|coordination)/.test(lowerTask);
    const hasAuditPressure = /(review|audit|cto|lag|slow|bug|fix|broken|performance|harden|readiness|not working)/.test(lowerTask);
    const isTechnicalAudit = hasTechnicalScope && hasAuditPressure;
    if (isTechnicalAudit) {
        intentScores.review += 1.2;
        intentScores.backend += 1.35;
        intentScores.frontend += 0.95;
        intentScores.bugfix += 0.75;
        intentScores.pm *= 0.45;
        intentScores.marketing *= 0.2;
        intentScores.sales *= 0.2;
        if (/(dashboard|ui|ux)/.test(lowerTask) && /(lag|slow|bug|performance|frontend|binding|api)/.test(lowerTask)) {
            intentScores.data *= 0.25;
        }
    }
    if (/(dashboard|metrics|analytics)/.test(lowerTask) && !/(ui|ux|frontend|lag|performance|bug|control plane|runtime)/.test(lowerTask)) {
        intentScores.data += 0.8;
    }
    if (/(build|create|implement|add|new|enhance|extend|develop)/.test(lowerTask) && !/(review|audit|fix|bug|lag|broken|readiness)/.test(lowerTask)) {
        intentScores.feature += 1.65;
    }
    // Sort by score descending to find primary and potential secondary intent
    const sorted = Object.entries(intentScores).sort((a, b) => b[1] - a[1]);
    const bestIntent = sorted[0]?.[1] > 0 ? sorted[0][0] : 'feature';
    const bestScore = sorted[0]?.[1] ?? 0;
    // Secondary intent: if 2nd place score >= 80% of 1st place, it's a meaningful secondary signal
    const secondaryThreshold = isTechnicalAudit ? 0.45 : 0.8;
    const threshold = Math.max(secondaryThreshold * bestScore, ABSOLUTE_SECONDARY_MIN);
    const secondaryType = sorted[1]?.[1] > 0 && bestScore > 0 && sorted[1][1] >= threshold
        ? sorted[1][0]
        : undefined;
    const taskType = bestIntent;
    const riskKeywords = {
        high: ['delete', 'migrate', 'release', 'security', 'auth', 'drop', 'production', 'secret', 'destroy'],
        medium: ['refactor', 'planner', 'orchestr', 'update', 'modify', 'change', 'replace'],
    };
    let bestRisk = 'low';
    let highestRiskScore = 0;
    for (const [risk, keywords] of Object.entries(riskKeywords)) {
        let score = 0;
        for (const kw of keywords) {
            if (taskTokens.has(kw))
                score += 1.0;
            else if (task.toLowerCase().includes(kw))
                score += 0.5;
        }
        if (score > highestRiskScore) {
            highestRiskScore = score;
            bestRisk = risk;
        }
    }
    // Safety override: if high risk keywords present, force high risk
    if (highestRiskScore > 0 && bestRisk !== 'high') {
        const hasHighRisk = riskKeywords.high.some((kw) => taskTokens.has(kw) || task.toLowerCase().includes(kw));
        if (hasHighRisk)
            bestRisk = 'high';
    }
    const riskClass = bestRisk;
    const complexity = Math.max(1, Math.min(6, decomposeTask(task).length + (task.length > 120 ? 1 : 0) + (riskClass === 'high' ? 1 : 0)));
    return { taskType, secondaryType, intentScores, riskClass, complexity };
}
// ─── Phase / graph / worker planning ──────────────────────────────────────────
export function classifyPhaseKind(phase, intent, index, total) {
    const lower = phase.toLowerCase();
    if (/(research|investigate|explore|audit|inspect)/.test(lower))
        return 'research';
    if (/(verify|test|review|check|validate)/.test(lower))
        return 'verify';
    if (intent.taskType === 'release' && index === total - 1)
        return 'release';
    if (index === 0 && /(plan|decompose|scope)/.test(lower))
        return 'plan';
    return 'implement';
}
export function buildTaskGraph(task, phases, intent) {
    const graphPhases = [
        {
            id: 'phase-plan',
            title: 'Plan and decompose',
            goal: task,
            kind: 'plan',
            branch: 0,
            dependsOn: [],
        },
        ...phases.map((phase, index) => ({
            id: `phase-${index + 1}`,
            title: shortLabel(phase, 56),
            goal: phase,
            kind: classifyPhaseKind(phase, intent, index, phases.length),
            branch: phases.length > 1 ? index + 1 : 1,
            dependsOn: index === 0 ? ['phase-plan'] : [`phase-${index}`],
        })),
        {
            id: 'phase-verify',
            title: 'Verify and govern',
            goal: `Verify ${task}`,
            kind: 'verify',
            branch: 0,
            dependsOn: phases.length > 0 ? [`phase-${phases.length}`] : ['phase-plan'],
        },
    ];
    const implementationPhases = graphPhases.filter((phase) => phase.kind === 'implement' || phase.kind === 'research');
    const independentBranches = Math.max(implementationPhases.length, phases.length > 1 ? phases.length : 1);
    return {
        generatedAt: Date.now(),
        summary: `${graphPhases.length} phases across ${independentBranches} branch(es).`,
        branchCount: new Set(graphPhases.map((phase) => phase.branch)).size,
        independentBranches,
        dominantKind: implementationPhases[0]?.kind ?? 'plan',
        phases: graphPhases,
    };
}
export function buildWorkerPlan(workerCount, mode, taskGraph, knowledgeFabric) {
    const coderCount = Math.max(1, Math.min(7, workerCount));
    const lanes = [
        {
            role: 'planner',
            count: 1,
            reason: 'Non-trivial autonomous runs always start with planner/decomposer authority.',
        },
        {
            role: 'coder',
            count: coderCount,
            reason: coderCount === 1
                ? 'Single-worker runs keep one coder lane plus planner oversight.'
                : 'Coder lane count mirrors planned execution workers.',
        },
    ];
    if (coderCount > 1 && ((knowledgeFabric.rag.hits.length > 0) || (knowledgeFabric.patterns.selected.length > 0))) {
        lanes.push({
            role: 'researcher',
            count: 1,
            reason: 'RAG, patterns, or external context require a scout lane for grounding.',
        });
    }
    if (taskGraph.independentBranches >= 2) {
        lanes.push({
            role: 'integrator',
            count: 1,
            reason: 'Multiple branches need an integration lane before merge/apply.',
        });
        lanes.push({
            role: 'reviewer',
            count: 1,
            reason: 'Multi-branch work gets a distinct reviewer lane before completion.',
        });
    }
    if (mode === 'continuation-capable') {
        lanes.push({
            role: 'continuation',
            count: 1,
            reason: 'Continuation-capable mode keeps a bounded follow-up lane available.',
        });
    }
    const totalWorkers = lanes.reduce((sum, lane) => sum + lane.count, 0);
    return {
        generatedAt: Date.now(),
        mode,
        totalWorkers,
        continuationAllowed: mode === 'continuation-capable',
        summary: `${totalWorkers} planned worker lanes across ${taskGraph.independentBranches} branch(es).`,
        lanes,
    };
}
export function decideWorkers(requestedWorkers, plannedWorkers, phaseCount, intent, repeatedFailures, knowledgeFabric) {
    if (typeof requestedWorkers === 'number' && requestedWorkers > 0) {
        return Math.max(1, Math.min(7, requestedWorkers));
    }
    if (intent.taskType === 'release') {
        return 1;
    }
    if (intent.riskClass !== 'high'
        && intent.complexity <= 3
        && phaseCount <= 2
        && repeatedFailures < 1
        && (knowledgeFabric?.rag.hits.length ?? 0) < 3
        && (knowledgeFabric?.patterns.selected.length ?? 0) < 3) {
        return 1;
    }
    const baseline = Math.max(plannedWorkers, phaseCount > 2 ? 2 : 1);
    const failureBoost = repeatedFailures > 0 ? 1 : 0;
    const riskBoost = intent.riskClass === 'high' ? 1 : 0;
    const ragBoost = (knowledgeFabric?.rag.hits.length ?? 0) >= 3 ? 1 : 0;
    const patternBoost = (knowledgeFabric?.patterns.selected.length ?? 0) >= 3 ? 1 : 0;
    return Math.max(1, Math.min(4, baseline + failureBoost + riskBoost + ragBoost + patternBoost));
}
/**
 * Determine execution mode. `repeatedFailures` must be passed explicitly
 * (previously read from `this.sessionState.repeatedFailures`).
 */
export function determineMode(intent, phaseCount, workers, repeatedFailures) {
    if (intent.taskType === 'release' || repeatedFailures > 0) {
        return 'continuation-capable';
    }
    if (phaseCount > 1 || workers > 2 || intent.complexity >= 4) {
        return 'bounded-swarm';
    }
    return 'single-pass';
}
// ─── Token budget / RAG ────────────────────────────────────────────────────────
export function toSourceAwareTokenBudget(knowledgeFabric, selectedFiles, reason, task = '') {
    const detectedBlocks = Math.floor((task.match(/```/g) ?? []).length / 2);
    const requested = detectedBlocks > 0 || /(code blocks?|template|patch|diff|snippet|implementation|verification|memory write)/i.test(task);
    const templates = requested
        ? ['work-packet', 'implementation-notes', 'verification-output', 'memory-writeback']
        : ['verification-output', 'memory-writeback'];
    const languages = [...new Set([...task.matchAll(/```([a-z0-9_-]+)/gi)].map((match) => match[1]))].slice(0, 8);
    return {
        applied: true,
        reason,
        totalBudget: knowledgeFabric.tokenBudget.totalBudget,
        bySource: knowledgeFabric.tokenBudget.bySource,
        byStage: knowledgeFabric.tokenBudget.byStage,
        dropped: knowledgeFabric.tokenBudget.dropped,
        dominantSource: knowledgeFabric.sourceMix.dominantSource,
        codeBlocks: {
            requested,
            detectedBlocks,
            reservedTokens: Math.min(2400, Math.max(360, detectedBlocks * 320 + templates.length * 220)),
            templates,
            languages: languages.length ? languages : ['markdown', 'text'],
        },
        qualityGates: [
            'quote selected files and symbols before reading broadly',
            'use code blocks for work packets, patches, verification, and memory write-back',
            selectedFiles.length > 2 ? 'run token optimization before reading selected files' : 'keep single-file reads direct and cheap',
        ],
        recommendations: [
            'Route exploration to cheap lanes, keep implementation and integration on the primary coding model.',
            'Treat no-diff mutate runs as advisory inspected outcomes unless a binding promised a patch.',
        ],
    };
}
export function toRagCandidateStatus(knowledgeFabric) {
    const selectedChunks = knowledgeFabric.provenance.entries.filter((entry) => entry.sourceClass === 'rag' && entry.selected !== false).length;
    const droppedChunks = knowledgeFabric.tokenBudget.dropped.filter((entry) => entry.sourceClass === 'rag').length;
    return {
        attachedCollections: knowledgeFabric.rag.attachedCollections.length,
        retrievedChunks: knowledgeFabric.rag.hits.length,
        selectedChunks,
        droppedChunks,
        attachedNames: knowledgeFabric.rag.attachedCollections.map((collection) => collection.name),
    };
}
export function toRagUsageSummary(knowledgeFabric, usage) {
    const candidate = toRagCandidateStatus(knowledgeFabric);
    return {
        generatedAt: Date.now(),
        attachedCollections: candidate.attachedCollections,
        attachedNames: candidate.attachedNames,
        retrievedChunks: candidate.retrievedChunks,
        selectedChunks: candidate.selectedChunks,
        droppedChunks: candidate.droppedChunks,
        dominantSource: candidate.selectedChunks > 0 ? 'rag' : knowledgeFabric.sourceMix.dominantSource,
        usedInPlanner: usage.usedInPlanner,
        usedInPacket: usage.usedInPacket,
        usedInRuntime: usage.usedInRuntime,
    };
}
// ─── Artifact history ──────────────────────────────────────────────────────────
export function artifactHistoryKey(kind, id) {
    return `${kind}:${id}`;
}
export function buildArtifactOutcome(audit, run) {
    const activeNames = new Set([
        ...(run.activeSkills ?? []).map((skill) => skill.name),
        ...(run.activeWorkflows ?? []).map((workflow) => workflow.name),
        ...(run.activeHooks ?? []).map((hook) => hook.name),
        ...(run.activeAutomations ?? []).map((automation) => automation.name),
        ...(run.plannerState?.selectedSpecialists ?? []).map((specialist) => specialist.name),
        ...(run.plannerState?.selectedCrew ? [run.plannerState.selectedCrew.name] : []),
    ]);
    const verifiedWorkers = run.workerResults.filter((worker) => worker.verified).length;
    const outcomes = audit.selected.map((entry) => {
        const active = activeNames.has(entry.name);
        const helpful = active && (run.state === 'merged' || verifiedWorkers > 0);
        const harmful = run.state === 'failed' && entry.confidence === 'low';
        const redundant = !active && entry.score < 0.75;
        const outcome = harmful
            ? 'harmful'
            : helpful
                ? 'helpful'
                : redundant
                    ? 'redundant'
                    : 'neutral';
        const score = outcome === 'helpful'
            ? 0.9
            : outcome === 'neutral'
                ? 0.5
                : outcome === 'redundant'
                    ? 0.25
                    : -0.35;
        const reason = harmful
            ? 'Low-confidence artifact stayed selected in a failed run.'
            : helpful
                ? 'Artifact stayed active through a verified or merged run.'
                : redundant
                    ? 'Artifact was shortlisted but did not materially contribute at runtime.'
                    : 'Artifact remained valid but the run evidence was mixed.';
        return {
            kind: entry.kind,
            id: entry.id,
            name: entry.name,
            outcome,
            score,
            reason,
        };
    });
    return {
        generatedAt: Date.now(),
        summary: `${outcomes.filter((entry) => entry.outcome === 'helpful').length} helpful · ${outcomes.filter((entry) => entry.outcome === 'redundant').length} redundant · ${outcomes.filter((entry) => entry.outcome === 'harmful').length} harmful`,
        outcomes,
    };
}
export function mergeArtifactOutcomeHistory(existing, snapshot) {
    const next = { ...(existing ?? {}) };
    for (const outcome of snapshot.outcomes) {
        const key = artifactHistoryKey(outcome.kind, outcome.id);
        const previous = next[key] ?? {
            score: 0,
            helpful: 0,
            neutral: 0,
            redundant: 0,
            harmful: 0,
            updatedAt: 0,
            lastVerificationResult: 'none',
        };
        next[key] = {
            score: Number((previous.score * 0.72 + outcome.score).toFixed(2)),
            helpful: previous.helpful + (outcome.outcome === 'helpful' ? 1 : 0),
            neutral: previous.neutral + (outcome.outcome === 'neutral' ? 1 : 0),
            redundant: previous.redundant + (outcome.outcome === 'redundant' ? 1 : 0),
            harmful: previous.harmful + (outcome.outcome === 'harmful' ? 1 : 0),
            updatedAt: Date.now(),
            lastVerificationResult: outcome.outcome === 'helpful'
                ? 'verified'
                : outcome.outcome === 'harmful'
                    ? 'failed'
                    : 'mixed',
        };
    }
    return next;
}
/**
 * Look up an artifact's history record from the supplied map.
 * `historyMap` was formerly `this.sessionState.artifactOutcomeHistory`.
 */
export function getArtifactHistoryRecord(historyMap, kind, id) {
    return historyMap?.[artifactHistoryKey(kind, id)];
}
export function getArtifactHistoryScore(historyMap, kind, id) {
    return getArtifactHistoryRecord(historyMap, kind, id)?.score ?? 0;
}
export function deriveArtifactMemoryClass(history) {
    if (!history)
        return 'scratch';
    if (history.helpful >= 3 && history.harmful === 0 && history.score >= 1.5)
        return 'reusable_pattern';
    if (history.helpful >= 1 && history.score >= 0.9)
        return 'project_fact';
    if (history.score > 0)
        return 'session_learning';
    return 'scratch';
}
export function deriveArtifactFreshness(history) {
    if (!history?.updatedAt)
        return 'stale';
    const ageMs = Date.now() - history.updatedAt;
    if (ageMs <= 24 * 60 * 60 * 1000)
        return 'hot';
    if (ageMs <= 7 * 24 * 60 * 60 * 1000)
        return 'warm';
    if (ageMs <= 30 * 24 * 60 * 60 * 1000)
        return 'cold';
    return 'stale';
}
// ─── Pure repo-search utilities ────────────────────────────────────────────────
export function buildRepoSearchDocument(relativePath, head) {
    const normalizedRelativePath = relativePath.split(path.sep).join('/');
    return [
        normalizedRelativePath,
        normalizedRelativePath.replace(/\//g, ' '),
        path.basename(normalizedRelativePath),
        head,
    ].filter(Boolean).join('\n');
}
export async function readRepoSearchHead(filePath) {
    try {
        const handle = await fs.promises.open(filePath, 'r');
        try {
            const buffer = Buffer.alloc(REPO_SEARCH_HEAD_BYTES);
            const { bytesRead } = await handle.read(buffer, 0, REPO_SEARCH_HEAD_BYTES, 0);
            return buffer.slice(0, bytesRead).toString('utf8');
        }
        finally {
            await handle.close();
        }
    }
    catch {
        return '';
    }
}
export function extractRepoSearchTerms(relativePath, head) {
    return dedupeStrings(extractKeywords(`${relativePath.replace(/\//g, ' ')} ${head}`))
        .filter((term) => term.length >= 3)
        .slice(0, 24);
}
