"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
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.__testing = void 0;
exports.validateProposal = validateProposal;
exports.renderPhaseHint = renderPhaseHint;
/**
 * Render a phase_hints YAML entry from a review agent's strict-JSON proposal.
 *
 * The LLM never writes YAML. It selects a closed `failureCategoryEnum`,
 * supplies evidence + candidate fields, and the backend deterministically
 * renders the entry from a per-category template. Same input → same output;
 * the patchFingerprint can be relied on across re-renders.
 *
 * Schema validation runs first and bails on:
 *   - unknown failureCategoryEnum (closed enum from PR4)
 *   - candidateKeywords length / per-token length violation
 *   - candidateConstraints over the per-template content scan or length cap
 *   - candidateCriticalTools that aren't in the supplied registry (when one
 *     is provided)
 *
 * See docs/architecture/self-improving-design.md §13 (Phase Hints Template-Driven Patching).
 */
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const yaml = __importStar(require("js-yaml"));
const failureTaxonomy_1 = require("./failureTaxonomy");
const contentScanner_1 = require("./contentScanner");
const hintFingerprint_1 = require("./hintFingerprint");
const TEMPLATES_DIR = path.resolve(__dirname, '..', '..', '..', 'strategies', 'phase_hint_templates');
const MAX_KEYWORDS = 6;
const MAX_KEYWORD_CHARS = 40;
const MAX_CONSTRAINTS_CHARS = 400;
const MAX_TOOLS = 6;
const MAX_TOOL_CHARS = 80;
const MAX_EVIDENCE_CHARS = 600;
/**
 * Validate a proposal and emit a stable patch fingerprint without rendering.
 * Useful when the caller needs to detect a duplicate proposal before the
 * (more expensive) template substitution.
 */
function validateProposal(raw, toolRegistry) {
    if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
        return { ok: false, reason: 'invalid_payload', details: 'proposal must be a JSON object' };
    }
    const r = raw;
    if (!(0, failureTaxonomy_1.isKnownCategory)(r.failureCategoryEnum)) {
        return {
            ok: false,
            reason: 'unknown_category',
            details: `failureCategoryEnum must be one of: ${failureTaxonomy_1.FAILURE_CATEGORIES.join(', ')}`,
        };
    }
    if (typeof r.evidenceSummary !== 'string' || r.evidenceSummary.trim().length === 0) {
        return { ok: false, reason: 'invalid_payload', details: 'evidenceSummary must be a non-empty string' };
    }
    const keywords = normalizeStringArray(r.candidateKeywords, MAX_KEYWORDS, MAX_KEYWORD_CHARS);
    if (keywords === 'invalid') {
        return { ok: false, reason: 'invalid_payload', details: 'candidateKeywords must be string[]' };
    }
    const constraints = typeof r.candidateConstraints === 'string'
        ? r.candidateConstraints.substring(0, MAX_CONSTRAINTS_CHARS)
        : '';
    const tools = normalizeStringArray(r.candidateCriticalTools, MAX_TOOLS, MAX_TOOL_CHARS);
    if (tools === 'invalid') {
        return { ok: false, reason: 'invalid_payload', details: 'candidateCriticalTools must be string[]' };
    }
    if (toolRegistry && tools.length > 0) {
        const missing = tools.find(t => !toolRegistry.has(t));
        if (missing) {
            return { ok: false, reason: 'tool_not_in_registry', details: `unknown tool/skill: ${missing}` };
        }
    }
    // Security scan across every free-form surface the agent could pollute.
    for (const text of [r.evidenceSummary, constraints, ...keywords, ...tools]) {
        const matches = (0, contentScanner_1.scanContent)(text);
        if (matches.length > 0) {
            return { ok: false, reason: 'security_scan', details: (0, contentScanner_1.formatThreats)(matches) };
        }
    }
    const value = {
        failureCategoryEnum: r.failureCategoryEnum,
        evidenceSummary: r.evidenceSummary.substring(0, MAX_EVIDENCE_CHARS),
        candidateKeywords: keywords,
        candidateConstraints: constraints,
        candidateCriticalTools: tools,
        appliedAt: typeof r.appliedAt === 'number' ? r.appliedAt : undefined,
    };
    return { ok: true, value };
}
/**
 * Render a phase_hints entry. Returns a yaml string that callers append
 * verbatim to the target strategy file's `phase_hints:` block.
 */
function renderPhaseHint(raw, opts = {}) {
    const validation = validateProposal(raw, opts.toolRegistry);
    if (!validation.ok)
        return validation;
    const proposal = validation.value;
    const templatesDir = opts.templatesDir ?? TEMPLATES_DIR;
    const templatePath = path.join(templatesDir, `${proposal.failureCategoryEnum}.template.yaml`);
    if (!fs.existsSync(templatePath)) {
        return {
            ok: false,
            reason: 'no_template',
            details: `no template at ${templatePath} — no auto-patch for this category yet`,
        };
    }
    const fingerprint = computePatchFingerprint(proposal);
    const phaseHintId = `auto_${proposal.failureCategoryEnum.replace(/_/g, '-')}_${fingerprint.substring(0, 8)}`;
    const obj = {
        id: phaseHintId,
        keywords: [...proposal.candidateKeywords].sort(),
        constraints: proposal.candidateConstraints,
        critical_tools: [...proposal.candidateCriticalTools].sort(),
        critical: false,
        auto_generated: true,
        applied_at: proposal.appliedAt ?? Date.now(),
        evidence: proposal.evidenceSummary,
    };
    // Wrap in a one-element array so the result drops cleanly into the
    // existing `phase_hints:` block under a strategy file's frontmatter.
    const rendered = yaml.dump([obj], {
        noRefs: true,
        sortKeys: false,
        lineWidth: 100,
    });
    return { ok: true, yaml: rendered, patchFingerprint: fingerprint, phaseHintId };
}
function computePatchFingerprint(proposal) {
    // Phase 0.1 of v2.1 — share canonical form with `strategyFingerprint` so
    // a freshly-rendered auto-patch's fingerprint matches what `detectDrift`
    // computes off the on-disk hint. The renderer always emits
    // `critical: false`, so the canonical input fixes that here.
    return (0, hintFingerprint_1.computeHintFingerprint)({
        keywords: proposal.candidateKeywords,
        constraints: proposal.candidateConstraints,
        criticalTools: proposal.candidateCriticalTools,
        critical: false,
    });
}
function normalizeStringArray(input, maxLen, maxItemChars) {
    if (input === undefined)
        return [];
    if (!Array.isArray(input))
        return 'invalid';
    const out = [];
    for (const item of input) {
        if (typeof item !== 'string')
            return 'invalid';
        out.push(item.substring(0, maxItemChars));
        if (out.length >= maxLen)
            break;
    }
    return out;
}
/** Useful for tests + the patch orchestration layer. */
exports.__testing = {
    TEMPLATES_DIR,
    MAX_KEYWORDS,
    MAX_KEYWORD_CHARS,
    MAX_CONSTRAINTS_CHARS,
    MAX_TOOLS,
    MAX_TOOL_CHARS,
    computePatchFingerprint,
};
//# sourceMappingURL=phaseHintsRenderer.js.map