"use strict";
// src/orchestrator/CapabilityIndex.ts
// Phase 2 of catalog→execution: a real capability index over the 314 catalog
// agents. Replaces AgentRegistry's tag-only substring matching with relevance
// scoring derived from the RICH signal already present in each AGENT.md body
// (title, role, whenToUse, focus, identity) — not just the noisy import tags.
//
// Pure + deterministic: no LLM, no network. Built once, queried many times.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CapabilityIndex = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
/** Field weights — the core ranking decision. focus/whenToUse describe WHAT
 *  the agent is FOR, so they outrank generic body mentions. Tune here. */
const WEIGHTS = {
    focus: 4,
    whenToUse: 3,
    title: 3,
    role: 2,
    tags: 2,
    body: 1,
};
const STOPWORDS = new Set([
    'the', 'and', 'for', 'with', 'when', 'that', 'this', 'are', 'you', 'your',
    'who', 'what', 'how', 'why', 'into', 'from', 'has', 'have', 'will', 'can',
    'use', 'using', 'used', 'need', 'needs', 'needing', 'about', 'any', 'all',
    'não', 'para', 'com', 'que', 'uma', 'dos', 'das', 'por', 'como', 'mais',
    'agent', 'agente', 'lara', 'skill', 'squad', 'openlife',
]);
const TAG_NOISE = new Set(['lara', 'agente', 'skill', 'squad', 'openlife']);
function tokenize(text) {
    return text
        .toLowerCase()
        .split(/[^a-z0-9áàâãéêíóôõúüç]+/i)
        .filter(t => t.length >= 3 && !STOPWORDS.has(t));
}
function agentRoots() {
    const env = process.env.OPENLIFE_AGENT_ROOTS;
    if (env)
        return env.split(':').filter(Boolean);
    const cwdRoot = path.join(process.cwd(), '.catalog', 'agents');
    const packageRoot = path.join(__dirname, '..', '..', '.catalog', 'agents');
    return cwdRoot === packageRoot ? [cwdRoot] : [cwdRoot, packageRoot];
}
function firstQuoted(content, field) {
    const m = content.match(new RegExp(`${field}:\\s*"([^"]+)"`, 'i'));
    return m?.[1]?.trim();
}
function extractTags(content) {
    const out = [];
    const re = /tags:\s*\[([^\]]+)\]/gi;
    let m;
    while ((m = re.exec(content)) !== null) {
        m[1].split(',')
            .map(s => s.trim().replace(/^['"]+|['"]+$/g, ''))
            .filter(s => s && !TAG_NOISE.has(s.toLowerCase()))
            .forEach(s => { if (!out.includes(s))
            out.push(s); });
    }
    return out;
}
class CapabilityIndex {
    profiles = [];
    constructor(roots) {
        this.build(roots ?? agentRoots());
    }
    get size() {
        return this.profiles.length;
    }
    getProfile(id) {
        return this.profiles.find(p => p.id === id) ?? null;
    }
    build(roots) {
        const seen = new Set();
        for (const root of roots) {
            if (!fs.existsSync(root))
                continue;
            let entries;
            try {
                entries = fs.readdirSync(root, { withFileTypes: true });
            }
            catch {
                continue;
            }
            for (const entry of entries) {
                if (!entry.isDirectory())
                    continue;
                if (entry.name.startsWith('.') || entry.name.startsWith('test-'))
                    continue;
                if (seen.has(entry.name))
                    continue; // first root wins (project over package)
                const file = path.join(root, entry.name, 'AGENT.md');
                if (!fs.existsSync(file))
                    continue;
                let content;
                try {
                    content = fs.readFileSync(file, 'utf-8');
                }
                catch {
                    continue;
                }
                seen.add(entry.name);
                this.profiles.push(this.buildProfile(entry.name, file, content));
            }
        }
    }
    buildProfile(id, sourcePath, content) {
        const name = content.match(/^name:\s*(.+)$/m)?.[1]?.trim() || id;
        const role = firstQuoted(content, 'role') || content.match(/^role:\s*(.+)$/m)?.[1]?.trim() || 'specialist';
        const title = firstQuoted(content, 'title') || '';
        const whenToUseRaw = firstQuoted(content, 'whenToUse') || '';
        const focusRaw = firstQuoted(content, 'focus') || '';
        const identity = firstQuoted(content, 'identity') || '';
        const tags = extractTags(content);
        const whenToUse = whenToUseRaw.split(/[.,;]/).map(s => s.trim()).filter(Boolean);
        const focus = focusRaw.split(/[,;]/).map(s => s.trim()).filter(Boolean);
        // Build the weighted term map. Higher-signal fields contribute more weight.
        const terms = new Map();
        const add = (text, weight) => {
            for (const tok of tokenize(text)) {
                terms.set(tok, (terms.get(tok) ?? 0) + weight);
            }
        };
        add(focusRaw, WEIGHTS.focus);
        add(whenToUseRaw, WEIGHTS.whenToUse);
        add(title, WEIGHTS.title);
        add(role, WEIGHTS.role);
        add(tags.join(' '), WEIGHTS.tags);
        add(identity, WEIGHTS.body);
        add(name, WEIGHTS.body);
        return { id, name, role, title, whenToUse, focus, tags, terms, sourcePath };
    }
    /** Rank agents by relevance to a free-text query. Returns top `limit`
     *  with score > 0, highest first. Deterministic tie-break by id. */
    search(query, limit = 5) {
        const queryTerms = Array.from(new Set(tokenize(query)));
        if (queryTerms.length === 0)
            return [];
        const scored = [];
        for (const p of this.profiles) {
            let score = 0;
            const matched = [];
            for (const qt of queryTerms) {
                const w = p.terms.get(qt);
                if (w) {
                    score += w;
                    matched.push(qt);
                }
            }
            if (score > 0) {
                scored.push({ id: p.id, name: p.name, role: p.role, score, matched });
            }
        }
        scored.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
        return scored.slice(0, limit);
    }
}
exports.CapabilityIndex = CapabilityIndex;
