"use strict";
/**
 * SkillCreator — authoring system for skills under `.catalog/skills/`.
 *
 * Story 5.2 — OpenLife v1.2 Royal Stack.
 *
 * Parallel to SquadCreator (Story 5.1) but simpler: a skill is a single
 * SKILL.md file rendered from `dist-templates/skill-template/`. No
 * sub-directories, no nested agents.
 *
 * v1.2 implements 4 of 8 methods. The remaining 4 (design, migrate,
 * extend, publish) are v1.3 placeholders with stable signatures.
 */
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.SkillCreator = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const crypto = __importStar(require("crypto"));
const AtomicWriter_1 = require("./util/AtomicWriter");
const TemplateRenderer_1 = require("./util/TemplateRenderer");
class SkillCreator {
    catalogRoot;
    templateRoot;
    constructor(opts = {}) {
        this.catalogRoot = opts.catalogRoot || path.join(process.cwd(), '.catalog', 'skills');
        this.templateRoot = opts.templateRoot || path.resolve(__dirname, '..', '..', 'dist-templates', 'skill-template');
    }
    create(proposal) {
        if (!/^[a-z0-9][a-z0-9._-]+$/i.test(proposal.id)) {
            return { ok: false, error: 'invalid_skill_id' };
        }
        const skillDir = path.join(this.catalogRoot, proposal.id);
        const skillFile = path.join(skillDir, 'SKILL.md');
        if (fs.existsSync(skillDir)) {
            return { ok: false, error: 'skill_already_exists', detail: skillDir };
        }
        try {
            fs.mkdirSync(skillDir, { recursive: true });
            const vars = this.buildVars(proposal);
            const content = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'SKILL.md.template'), vars);
            (0, AtomicWriter_1.writeStringAtomic)(skillFile, content);
            return { ok: true, skillId: proposal.id, skillDir, filePath: skillFile };
        }
        catch (err) {
            try {
                fs.rmSync(skillDir, { recursive: true, force: true });
            }
            catch { /* ignore */ }
            return { ok: false, error: 'render_failed', detail: err instanceof Error ? err.message : String(err) };
        }
    }
    validate(skillId) {
        const errors = [];
        const warnings = [];
        const skillFile = path.join(this.catalogRoot, skillId, 'SKILL.md');
        if (!fs.existsSync(skillFile)) {
            errors.push(`SKILL.md not found for ${skillId}`);
            return { ok: false, skillId, errors, warnings };
        }
        const content = fs.readFileSync(skillFile, 'utf-8');
        const fm = this.parseFrontmatter(content);
        if (!fm.id)
            errors.push('frontmatter missing id');
        if (!fm.name)
            errors.push('frontmatter missing name');
        if (fm.id && fm.id !== skillId) {
            errors.push(`frontmatter id '${fm.id}' does not match directory '${skillId}'`);
        }
        if (!/##\s+Procedure/.test(content))
            warnings.push('missing ## Procedure section');
        if (!/##\s+When to use/.test(content))
            warnings.push('missing ## When to use section');
        return { ok: errors.length === 0, skillId, errors, warnings };
    }
    list(filter) {
        if (!fs.existsSync(this.catalogRoot))
            return [];
        const out = [];
        for (const entry of fs.readdirSync(this.catalogRoot)) {
            const skillFile = path.join(this.catalogRoot, entry, 'SKILL.md');
            if (!fs.existsSync(skillFile))
                continue;
            const fm = this.parseFrontmatter(fs.readFileSync(skillFile, 'utf-8'));
            const item = { id: fm.id || entry, name: fm.name || entry, status: fm.status || 'unknown' };
            if (filter?.status && item.status !== filter.status)
                continue;
            out.push(item);
        }
        return out;
    }
    analyze(skillId) {
        const skillFile = path.join(this.catalogRoot, skillId, 'SKILL.md');
        if (!fs.existsSync(skillFile))
            return null;
        const content = fs.readFileSync(skillFile, 'utf-8');
        const result = {
            skillId,
            hasWhenToUse: /##\s+When to use/.test(content) && content.split('##')[1] !== undefined,
            hasProcedure: /##\s+Procedure/.test(content),
            hasValidation: /##\s+Validation/.test(content),
            hasGuardrails: /##\s+Guardrails/.test(content),
            hasExamples: /##\s+Examples/.test(content),
            hasReferences: /##\s+References/.test(content),
            suggestions: [],
        };
        if (!result.hasWhenToUse)
            result.suggestions.push('add a ## When to use section');
        if (!result.hasProcedure)
            result.suggestions.push('add a ## Procedure section');
        if (!result.hasValidation)
            result.suggestions.push('add a ## Validation section');
        if (!result.hasGuardrails)
            result.suggestions.push('add a ## Guardrails section');
        return result;
    }
    // v1.3 — heuristic skill proposal from a free-text brief (Story 3.1).
    // Brain-driven proposal lands in v1.4; signature stays stable.
    design(brief, opts = {}) {
        const trimmed = (brief || '').trim();
        if (!trimmed)
            return { ok: false, error: 'invalid_brief' };
        const id = opts.id || trimmed.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 50);
        if (!/^[a-z0-9][a-z0-9._-]+$/i.test(id))
            return { ok: false, error: 'invalid_skill_id' };
        const mode = opts.mode || 'professional';
        const procedure = mode === 'quick'
            ? [`Read the brief: ${trimmed}`, 'Execute the core action', 'Return the result']
            : [`Read the brief: ${trimmed}`, 'Plan the approach', 'Execute the action', 'Validate the output'];
        const proposal = {
            id,
            name: id,
            description: trimmed,
            whenToUse: [trimmed],
            procedure,
            version: '0.1.0',
            status: 'draft',
            source: 'skill-creator.design',
        };
        return { ok: true, proposal };
    }
    /**
     * Story 8.2 — LLM-driven design.
     *
     * Mirrors SquadCreator.designWithBrain. If a Brain provider is configured,
     * asks the Brain for a structured JSON proposal. On any failure, falls
     * back to the heuristic `design()`.
     */
    async designWithBrain(brain, brief, opts = {}) {
        if (!brain.isAnyProviderAvailable()) {
            const r = this.design(brief, opts);
            return r.ok ? { ok: true, proposal: r.proposal, source: 'heuristic' } : r;
        }
        const trimmed = (brief || '').trim();
        if (!trimmed)
            return { ok: false, error: 'invalid_brief' };
        const id = opts.id || trimmed.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 50);
        if (!/^[a-z0-9][a-z0-9._-]+$/i.test(id))
            return { ok: false, error: 'invalid_skill_id' };
        const mode = opts.mode || 'professional';
        const systemPrompt = `You are SkillCreator. Given a brief and an authoring mode (quick|professional|elite), return a STRICT JSON object describing a reusable skill. The output MUST be a single JSON object — no prose, no markdown fences. Shape:
{
  "description": "<one-line summary>",
  "whenToUse": ["<bullet1>", "<bullet2>", "..."],
  "procedure": ["step 1", "step 2", "..."],
  "validation": ["check 1", "check 2"],
  "guardrails": ["rule 1", "rule 2"]
}
Rules:
- quick: 3 procedure steps, 1 whenToUse, no validation/guardrails
- professional: 4-5 steps, 2-3 whenToUse, 1-2 validations
- elite: 5-7 steps, 2-3 whenToUse, 3+ validations, 2+ guardrails`;
        const userMessage = `Brief: ${trimmed}\nMode: ${mode}\nSkill id: ${id}`;
        try {
            const { extractFirstJson } = require('./SquadCreator');
            const raw = await brain.think(systemPrompt, userMessage);
            const parsed = extractFirstJson(raw);
            if (!parsed || typeof parsed !== 'object')
                throw new Error('no_json_in_response');
            const p = parsed;
            if (!Array.isArray(p.procedure) || p.procedure.length < 1)
                throw new Error('missing_procedure');
            const proposal = {
                id, name: id,
                description: p.description || trimmed,
                whenToUse: Array.isArray(p.whenToUse) ? p.whenToUse : [trimmed],
                procedure: p.procedure,
                validations: Array.isArray(p.validation) ? p.validation : undefined,
                guardrails: Array.isArray(p.guardrails) ? p.guardrails : undefined,
                version: '0.1.0',
                status: 'draft',
                source: 'skill-creator.designWithBrain',
            };
            return { ok: true, proposal, source: 'brain' };
        }
        catch {
            const r = this.design(brief, opts);
            return r.ok ? { ok: true, proposal: r.proposal, source: 'heuristic' } : r;
        }
    }
    // ─────────────────────────────────────────────────────────
    // migrate — rewrite SKILL.md frontmatter version atomically
    // ─────────────────────────────────────────────────────────
    migrate(skillId, fromVersion, toVersion) {
        if (!/^[a-z0-9][a-z0-9._-]+$/i.test(skillId))
            return { ok: false, skillId, error: 'invalid_skill_id' };
        if (!/^\d+\.\d+(\.\d+)?$/.test(fromVersion) || !/^\d+\.\d+(\.\d+)?$/.test(toVersion)) {
            return { ok: false, skillId, error: 'invalid_version' };
        }
        const skillFile = path.join(this.catalogRoot, skillId, 'SKILL.md');
        if (!fs.existsSync(skillFile))
            return { ok: false, skillId, error: 'skill_not_found' };
        const content = fs.readFileSync(skillFile, 'utf-8');
        const fm = this.parseFrontmatter(content);
        if (fm.version && fm.version !== fromVersion) {
            return { ok: false, skillId, error: 'version_mismatch', detail: `current=${fm.version} expected=${fromVersion}` };
        }
        const next = this.rewriteFrontmatterField(content, 'version', toVersion);
        (0, AtomicWriter_1.writeStringAtomic)(skillFile, next);
        return { ok: true, skillId, fromVersion, toVersion, filesChanged: [skillFile] };
    }
    // ─────────────────────────────────────────────────────────
    // extend — append bullet items to a known SKILL.md section
    // ─────────────────────────────────────────────────────────
    extend(skillId, patch) {
        if (!/^[a-z0-9][a-z0-9._-]+$/i.test(skillId))
            return { ok: false, skillId, error: 'invalid_skill_id' };
        if (!patch || !patch.items?.length)
            return { ok: false, skillId, error: 'empty_patch' };
        const skillFile = path.join(this.catalogRoot, skillId, 'SKILL.md');
        if (!fs.existsSync(skillFile))
            return { ok: false, skillId, error: 'skill_not_found' };
        const heading = {
            whenToUse: 'When to use',
            procedure: 'Procedure',
            guardrails: 'Guardrails',
            validation: 'Validation',
            references: 'References',
        };
        const sectionHeading = heading[patch.section];
        if (!sectionHeading)
            return { ok: false, skillId, error: 'unknown_section' };
        const content = fs.readFileSync(skillFile, 'utf-8');
        const updated = this.appendItemsToSection(content, sectionHeading, patch.section === 'procedure' ? 'numbered' : 'bullet', patch.items);
        (0, AtomicWriter_1.writeStringAtomic)(skillFile, updated);
        return { ok: true, skillId, section: patch.section, itemsAdded: patch.items.length };
    }
    // ─────────────────────────────────────────────────────────
    // publish — hash + ledger append + status flip to active
    // ─────────────────────────────────────────────────────────
    publish(skillId) {
        if (!/^[a-z0-9][a-z0-9._-]+$/i.test(skillId))
            return { ok: false, skillId, error: 'invalid_skill_id' };
        const skillFile = path.join(this.catalogRoot, skillId, 'SKILL.md');
        if (!fs.existsSync(skillFile))
            return { ok: false, skillId, error: 'skill_not_found' };
        let content = fs.readFileSync(skillFile, 'utf-8');
        const fm = this.parseFrontmatter(content);
        if (fm.status === 'archived')
            return { ok: false, skillId, error: 'skill_archived' };
        if (fm.status !== 'active') {
            content = this.rewriteFrontmatterField(content, 'status', 'active');
            (0, AtomicWriter_1.writeStringAtomic)(skillFile, content);
        }
        const sha256 = crypto.createHash('sha256').update(content).digest('hex');
        const stateDir = process.env.OPENLIFE_STATE_DIR || path.join(process.cwd(), '.openlife');
        fs.mkdirSync(stateDir, { recursive: true });
        const ledger = path.join(stateDir, 'published-assets.jsonl');
        const entry = JSON.stringify({ kind: 'skill', id: skillId, sha256, file: skillFile, publishedAt: new Date().toISOString() }) + '\n';
        fs.appendFileSync(ledger, entry, 'utf-8');
        return { ok: true, skillId, sha256, ledgerEntry: entry.trim(), status: 'active' };
    }
    /**
     * Story 14.1 (v1.5) — Publish + push to a remote registry.
     * Mirror of SquadCreator.publishWithRemote. Local seal first, then
     * RemotePublisher.publishAsset; the remote outcome lives on the
     * returned envelope's `remote` field.
     */
    async publishWithRemote(skillId) {
        const local = this.publish(skillId);
        if (!local.ok || !local.sha256)
            return local;
        const skillFile = path.join(this.catalogRoot, skillId, 'SKILL.md');
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const { RemotePublisher } = require('./RemotePublisher');
        const remote = await new RemotePublisher().publishAsset('skill', skillId, local.sha256, skillFile);
        return { ...local, remote };
    }
    // ─────────────────────────────────────────────────────────
    // Helpers
    // ─────────────────────────────────────────────────────────
    rewriteFrontmatterField(content, field, value) {
        const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
        if (!fmMatch)
            return `---\n${field}: ${value}\n---\n${content}`;
        const lines = fmMatch[1].split('\n');
        const idx = lines.findIndex((l) => l.match(new RegExp(`^${field}:`)));
        if (idx >= 0)
            lines[idx] = `${field}: ${value}`;
        else
            lines.push(`${field}: ${value}`);
        return content.replace(fmMatch[0], `---\n${lines.join('\n')}\n---\n`);
    }
    appendItemsToSection(content, sectionHeading, listKind, items) {
        const heading = `## ${sectionHeading}`;
        const idx = content.indexOf(heading);
        if (idx < 0) {
            // Section missing — append a new section at end.
            const block = [`\n${heading}\n`, ...this.renderItems(listKind, items, 1)].join('');
            return content + block;
        }
        // Find the start of the next ## heading (or end-of-file)
        const after = content.indexOf('\n## ', idx + heading.length);
        const end = after < 0 ? content.length : after;
        const sectionBody = content.slice(idx, end).replace(/\n+$/, '');
        // For numbered lists, find current max number to continue counting.
        let startNum = 1;
        if (listKind === 'numbered') {
            const matches = [...sectionBody.matchAll(/^(\d+)\.\s/gm)];
            if (matches.length)
                startNum = Math.max(...matches.map((m) => Number(m[1]))) + 1;
        }
        const newItems = this.renderItems(listKind, items, startNum).join('');
        return content.slice(0, end).replace(/\n+$/, '') + '\n' + newItems + content.slice(end);
    }
    renderItems(listKind, items, startNum) {
        if (listKind === 'numbered') {
            return items.map((item, i) => `${startNum + i}. ${item}\n`);
        }
        return items.map((item) => `- ${item}\n`);
    }
    buildVars(p) {
        return {
            SKILL_ID: p.id,
            SKILL_NAME: p.name,
            SOURCE: p.source || 'aiobuilder-built',
            STATUS: p.status || 'draft',
            VERSION: p.version || '1.0.0',
            DESCRIPTION: p.description,
            WHEN_1: p.whenToUse[0] || 'TBD',
            WHEN_2: p.whenToUse[1] || 'TBD',
            WHEN_3: p.whenToUse[2] || 'TBD',
            STEP_1: p.procedure[0] || 'TBD',
            STEP_2: p.procedure[1] || 'TBD',
            STEP_3: p.procedure[2] || 'TBD',
            STEP_4: p.procedure[3] || 'TBD',
            INPUT_1: p.inputs?.[0] || 'TBD',
            INPUT_2: p.inputs?.[1] || 'TBD',
            OUTPUT_1: p.outputs?.[0] || 'TBD',
            OUTPUT_2: p.outputs?.[1] || 'TBD',
            VALIDATION_1: p.validations?.[0] || 'TBD',
            VALIDATION_2: p.validations?.[1] || 'TBD',
            GUARDRAIL_1: p.guardrails?.[0] || 'No secrets in skill body',
            GUARDRAIL_2: p.guardrails?.[1] || 'Promote draft→active only after real use',
            EXAMPLES: p.examples || '_TBD_',
            REFERENCES: p.references || '_TBD_',
        };
    }
    parseFrontmatter(content) {
        const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
        if (!match)
            return {};
        const out = {};
        for (const line of match[1].split('\n')) {
            const m = line.match(/^([\w-]+):\s*(.*)$/);
            if (m)
                out[m[1]] = m[2].trim();
        }
        return out;
    }
}
exports.SkillCreator = SkillCreator;
