"use strict";
/**
 * Capability descriptors.
 *
 * Each capability the dispatcher knows about has one descriptor. The
 * descriptor bundles the capability's stable id with its aggregate
 * function — how to combine multiple providers' results into one
 * envelope. Aggregation is bespoke per capability rather than a generic
 * 'sum' / 'append' / 'last' strategy because the merge semantics
 * legitimately differ (depVulns sums counts AND concats findings AND
 * joins tool names AND prefers any provider that did osv enrichment),
 * and a 5-strategy enum would lie about that.
 *
 * The dispatcher never calls aggregate() with an empty array — that
 * case is handled upstream by returning null.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.CAPABILITY_REGISTRY = exports.GLOBAL_REGISTRY = exports.PER_PACK_REGISTRY = exports.LICENSES = exports.IMPORTS = exports.STRUCTURAL = exports.DUPLICATION = exports.CODE_PATTERNS = exports.SECRETS = exports.TEST_FRAMEWORK = exports.COVERAGE = exports.LINT = exports.DEP_VULNS = void 0;
function uniqueJoin(values, sep = ', ') {
    const seen = new Set();
    for (const v of values)
        seen.add(v);
    return [...seen].join(sep);
}
exports.DEP_VULNS = {
    id: 'depVulns',
    aggregate(results) {
        let critical = 0;
        let high = 0;
        let medium = 0;
        let low = 0;
        const findings = [];
        let enrichment = null;
        for (const r of results) {
            critical += r.counts.critical;
            high += r.counts.high;
            medium += r.counts.medium;
            low += r.counts.low;
            if (r.findings)
                findings.push(...r.findings);
            if (r.enrichment)
                enrichment = r.enrichment;
        }
        return {
            schemaVersion: 1,
            tool: uniqueJoin(results.map((r) => r.tool)),
            enrichment,
            counts: { critical, high, medium, low },
            findings: findings.length > 0 ? findings : undefined,
        };
    },
};
exports.LINT = {
    id: 'lint',
    aggregate(results) {
        let critical = 0;
        let high = 0;
        let medium = 0;
        let low = 0;
        for (const r of results) {
            critical += r.counts.critical;
            high += r.counts.high;
            medium += r.counts.medium;
            low += r.counts.low;
        }
        return {
            schemaVersion: 1,
            tool: uniqueJoin(results.map((r) => r.tool)),
            counts: { critical, high, medium, low },
        };
    },
};
exports.COVERAGE = {
    id: 'coverage',
    aggregate(results) {
        // Multiple coverage providers in one repo are uncommon (mono-repos with
        // mixed stacks would hit this). Last-wins is the deterministic-but-lossy
        // choice; a smarter merge needs per-language file weighting — future
        // work when a real multi-pack case surfaces.
        return results[results.length - 1];
    },
};
exports.TEST_FRAMEWORK = {
    id: 'testFramework',
    aggregate(results) {
        // Last-wins. Mixed-stack repos resolve to a single framework name
        // deterministically by provider-registration order; per-language
        // reporting is future work (see Phase 10f roadmap).
        return results[results.length - 1];
    },
};
exports.SECRETS = {
    id: 'secrets',
    aggregate(results) {
        // Multiple providers only appear if a future global scanner joins
        // gitleaks (e.g. trufflehog). Concat findings, sum suppression
        // counts, unique-join tool names — identical strategy to DEP_VULNS.
        const findings = [];
        let suppressedCount = 0;
        for (const r of results) {
            findings.push(...r.findings);
            suppressedCount += r.suppressedCount;
        }
        return {
            schemaVersion: 1,
            tool: uniqueJoin(results.map((r) => r.tool)),
            findings,
            suppressedCount,
        };
    },
};
exports.CODE_PATTERNS = {
    id: 'codePatterns',
    aggregate(results) {
        // Same union strategy as SECRETS — multiple providers only enter the
        // picture if a future SAST scanner (codeql, opengrep) joins semgrep.
        const findings = [];
        let suppressedCount = 0;
        for (const r of results) {
            findings.push(...r.findings);
            suppressedCount += r.suppressedCount;
        }
        return {
            schemaVersion: 1,
            tool: uniqueJoin(results.map((r) => r.tool)),
            findings,
            suppressedCount,
        };
    },
};
exports.DUPLICATION = {
    id: 'duplication',
    aggregate(results) {
        // Multiple providers would appear if a future detector (pmd-cpd,
        // sonar-cpd) joins jscpd. Sum the numerics; concat + re-sort
        // topClones by line-count. Percentage is recomputed from summed
        // totals rather than averaged (line counts may overlap across
        // detectors, but a re-weighted mean would lie; summing totals is
        // conservative).
        let totalLines = 0;
        let duplicatedLines = 0;
        let cloneCount = 0;
        const allClones = [];
        for (const r of results) {
            totalLines += r.totalLines;
            duplicatedLines += r.duplicatedLines;
            cloneCount += r.cloneCount;
            allClones.push(...r.topClones);
        }
        const percentage = totalLines > 0 ? Math.round((duplicatedLines / totalLines) * 10000) / 100 : 0;
        // De-dupe identical clone pairs by stable key, keep largest-first.
        const seen = new Set();
        const merged = [];
        for (const c of allClones.sort((a, b) => b.lines - a.lines)) {
            const key = `${c.a.file}:${c.a.startLine}-${c.a.endLine}|${c.b.file}:${c.b.startLine}-${c.b.endLine}`;
            if (seen.has(key))
                continue;
            seen.add(key);
            merged.push(c);
        }
        return {
            schemaVersion: 1,
            tool: uniqueJoin(results.map((r) => r.tool)),
            totalLines,
            duplicatedLines,
            percentage,
            cloneCount,
            topClones: merged.slice(0, 15),
        };
    },
};
exports.STRUCTURAL = {
    id: 'structural',
    aggregate(results) {
        // Last-wins — same strategy as COVERAGE and TEST_FRAMEWORK. Every
        // field is a repo-level scalar produced by one graph pass; summing
        // across providers would double-count the same functions/modules,
        // and averaging would lie about the source of truth. If a second
        // structural tool ever joins graphify, a proper merge would need
        // per-tool weighting — out of scope until that exists.
        return results[results.length - 1];
    },
};
exports.IMPORTS = {
    id: 'imports',
    aggregate(results) {
        // Keys are disjoint between packs (each pack owns its source extensions),
        // so plain-union merge is sound. We still defensively skip later-writer
        // collisions so an accidental overlap would be visible in logs rather
        // than silently dropping edges.
        const extensions = new Set();
        const extracted = new Map();
        const edges = new Map();
        for (const r of results) {
            for (const ext of r.sourceExtensions)
                extensions.add(ext);
            for (const [file, specs] of r.extracted) {
                if (!extracted.has(file))
                    extracted.set(file, specs);
            }
            for (const [file, targets] of r.edges) {
                if (!edges.has(file))
                    edges.set(file, targets);
            }
        }
        return {
            schemaVersion: 1,
            tool: uniqueJoin(results.map((r) => r.tool)),
            sourceExtensions: [...extensions],
            extracted,
            edges,
        };
    },
};
exports.LICENSES = {
    id: 'licenses',
    aggregate(results) {
        // Per-pack providers own disjoint package sets (npm vs PyPI vs crates
        // vs Go modules vs NuGet) so concat-without-dedupe is sound in the
        // common case. A rare polyglot collision (same package name + version
        // in two ecosystems) keeps both rows; downstream formatters can
        // disambiguate by ecosystem if needed.
        const findings = [];
        for (const r of results)
            findings.push(...r.findings);
        return {
            schemaVersion: 1,
            tool: uniqueJoin(results.map((r) => r.tool)),
            findings,
        };
    },
};
/**
 * Per-language capabilities — one provider registered per language pack.
 * Keys must match `keyof LanguagePackCapabilities`; the contract test
 * enforces that symmetry.
 */
exports.PER_PACK_REGISTRY = {
    depVulns: exports.DEP_VULNS,
    lint: exports.LINT,
    coverage: exports.COVERAGE,
    testFramework: exports.TEST_FRAMEWORK,
    imports: exports.IMPORTS,
    licenses: exports.LICENSES,
};
/**
 * Global capabilities — tools that run once per repo (gitleaks, semgrep,
 * jscpd, graphify), not per pack. Keys must match `keyof GlobalCapabilities`.
 * Pack-dependent inputs (e.g. semgrep's rulesets from each active pack's
 * `semgrepRulesets` declaration) are read by the provider itself via
 * `detectActiveLanguages(cwd)` — the provider interface is uniform.
 */
exports.GLOBAL_REGISTRY = {
    secrets: exports.SECRETS,
    codePatterns: exports.CODE_PATTERNS,
    duplication: exports.DUPLICATION,
    structural: exports.STRUCTURAL,
};
/**
 * Union of both registries — kept as the canonical "every descriptor"
 * surface for tests and for callers that don't care about per-pack vs
 * global distinction. `providersFor()` (src/languages/capabilities/index.ts)
 * is the routing layer that picks the right source.
 */
exports.CAPABILITY_REGISTRY = {
    ...exports.PER_PACK_REGISTRY,
    ...exports.GLOBAL_REGISTRY,
};
//# sourceMappingURL=descriptors.js.map