"use strict";
/**
 * SquadCreator — authoring system for squads under `.catalog/squads/`.
 *
 * Story 5.1 — OpenLife v1.2 Royal Stack.
 *
 * Replaces the old `aiobuilder create-squad` stub (which wrote a 16-line
 * markdown file with hardcoded frontmatter) with a real authoring
 * pipeline that renders `dist-templates/squad-template/` into a
 * structured squad directory including agents, tasks, and a checklist.
 *
 * v1.2 implements 4 of the 8 methods promised by the SQUAD.md spec:
 *   - create   — full template rendering + directory layout
 *   - validate — frontmatter + components reference check
 *   - list     — delegate to SquadRegistry composite read
 *   - analyze  — surface coverage gaps (missing tasks/orphan agents)
 *
 * 4 are placeholders for v1.3 (Capability Genesis Engine):
 *   - design   — LLM-driven proposal from a brief (needs Brain wiring)
 *   - migrate  — schema migration between versions
 *   - extend   — append components to an existing squad
 *   - publish  — promote draft→active, sync to remote registry
 */
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.SquadCreator = void 0;
exports.extractFirstJson = extractFirstJson;
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");
const ToolsetGuard_1 = require("./toolset/ToolsetGuard");
class SquadCreator {
    catalogRoot;
    templateRoot;
    constructor(opts = {}) {
        this.catalogRoot = opts.catalogRoot || path.join(process.cwd(), '.catalog', 'squads');
        this.templateRoot = opts.templateRoot || path.resolve(__dirname, '..', '..', 'dist-templates', 'squad-template');
    }
    // ─────────────────────────────────────────────────────────
    // create — full directory rendering from templates
    // ─────────────────────────────────────────────────────────
    create(proposal) {
        (0, ToolsetGuard_1.assertToolsetAllowed)('squads', 'SquadCreator.create');
        if (!this.isValidId(proposal.id)) {
            return { ok: false, error: 'invalid_squad_id', detail: 'must match /^[a-z0-9][a-z0-9._-]+$/i' };
        }
        if (!Array.isArray(proposal.agents) || proposal.agents.length === 0) {
            return { ok: false, error: 'no_agents', detail: 'a squad needs at least one agent' };
        }
        const squadDir = path.join(this.catalogRoot, proposal.id);
        if (fs.existsSync(squadDir)) {
            return { ok: false, error: 'squad_already_exists', detail: squadDir };
        }
        const filesCreated = [];
        try {
            fs.mkdirSync(squadDir, { recursive: true });
            fs.mkdirSync(path.join(squadDir, 'agents'), { recursive: true });
            if (proposal.tasks?.length)
                fs.mkdirSync(path.join(squadDir, 'tasks'), { recursive: true });
            if (proposal.checklists?.length)
                fs.mkdirSync(path.join(squadDir, 'checklists'), { recursive: true });
            // Render SQUAD.md
            const squadVars = this.buildSquadVars(proposal);
            const squadContent = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'SQUAD.md.template'), squadVars);
            const squadFile = path.join(squadDir, 'SQUAD.md');
            (0, AtomicWriter_1.writeStringAtomic)(squadFile, squadContent);
            filesCreated.push(squadFile);
            // Render each agent
            for (const agent of proposal.agents) {
                const agentVars = this.buildAgentVars(agent, proposal);
                const agentContent = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'agent-template.md'), agentVars);
                const agentFile = path.join(squadDir, 'agents', `${agent.id}.md`);
                (0, AtomicWriter_1.writeStringAtomic)(agentFile, agentContent);
                filesCreated.push(agentFile);
            }
            // Render each task
            for (const task of proposal.tasks || []) {
                const taskVars = this.buildTaskVars(task, proposal);
                const taskContent = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'task-template.md'), taskVars);
                const taskFile = path.join(squadDir, 'tasks', `${task.id}.md`);
                (0, AtomicWriter_1.writeStringAtomic)(taskFile, taskContent);
                filesCreated.push(taskFile);
            }
            // Render each checklist
            for (const checklist of proposal.checklists || []) {
                const ckVars = {
                    CHECKLIST_NAME: checklist.id,
                    SQUAD_NAME: proposal.name,
                    PURPOSE: checklist.purpose,
                    ITEM_1: checklist.items[0] || '',
                    ITEM_2: checklist.items[1] || '',
                    ITEM_3: checklist.items[2] || '',
                    ITEM_4: checklist.items[3] || '',
                    ITEM_5: checklist.items[4] || '',
                };
                const ckContent = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'checklist-template.md'), ckVars);
                const ckFile = path.join(squadDir, 'checklists', `${checklist.id}.md`);
                (0, AtomicWriter_1.writeStringAtomic)(ckFile, ckContent);
                filesCreated.push(ckFile);
            }
            return { ok: true, squadId: proposal.id, squadDir, filesCreated };
        }
        catch (err) {
            // Roll back partial directory on error
            try {
                fs.rmSync(squadDir, { recursive: true, force: true });
            }
            catch { /* ignore */ }
            return { ok: false, error: 'render_failed', detail: err instanceof Error ? err.message : String(err) };
        }
    }
    // ─────────────────────────────────────────────────────────
    // validate — frontmatter + component reference check
    // ─────────────────────────────────────────────────────────
    validate(squadId) {
        const errors = [];
        const warnings = [];
        const squadDir = path.join(this.catalogRoot, squadId);
        const squadFile = path.join(squadDir, 'SQUAD.md');
        if (!fs.existsSync(squadDir)) {
            errors.push(`squad directory not found: ${squadDir}`);
            return { ok: false, squadId, errors, warnings };
        }
        if (!fs.existsSync(squadFile)) {
            errors.push('SQUAD.md missing');
            return { ok: false, squadId, errors, warnings };
        }
        const content = fs.readFileSync(squadFile, 'utf-8');
        // Frontmatter must have id/name/status
        const frontmatter = this.parseFrontmatter(content);
        if (!frontmatter.id)
            errors.push('frontmatter missing id');
        if (!frontmatter.name)
            errors.push('frontmatter missing name');
        if (!frontmatter.status)
            errors.push('frontmatter missing status');
        if (frontmatter.id && frontmatter.id !== squadId) {
            errors.push(`frontmatter id '${frontmatter.id}' does not match directory '${squadId}'`);
        }
        // Imported squads (lara-squads-import, obsidian-mirror, etc.) reference
        // agents/tasks from the global .catalog/{agents,skills}/ pool, not squad-local.
        const isImported = typeof frontmatter.source === 'string' && frontmatter.source.endsWith('-import');
        if (!isImported) {
            const componentRefs = this.parseComponentRefs(content);
            for (const refPath of componentRefs.agents) {
                const full = path.join(squadDir, 'agents', refPath);
                if (!fs.existsSync(full))
                    warnings.push(`referenced agent not found: agents/${refPath}`);
            }
            for (const refPath of componentRefs.tasks) {
                const full = path.join(squadDir, 'tasks', refPath);
                if (!fs.existsSync(full))
                    warnings.push(`referenced task not found: tasks/${refPath}`);
            }
        }
        return { ok: errors.length === 0, squadId, errors, warnings };
    }
    // ─────────────────────────────────────────────────────────
    // list — direct read of .catalog/squads/
    // ─────────────────────────────────────────────────────────
    list(filter) {
        if (!fs.existsSync(this.catalogRoot))
            return [];
        const out = [];
        for (const entry of fs.readdirSync(this.catalogRoot)) {
            const squadFile = path.join(this.catalogRoot, entry, 'SQUAD.md');
            if (!fs.existsSync(squadFile))
                continue;
            const fm = this.parseFrontmatter(fs.readFileSync(squadFile, '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 — coverage gaps + suggestions
    // ─────────────────────────────────────────────────────────
    analyze(squadId) {
        const squadDir = path.join(this.catalogRoot, squadId);
        if (!fs.existsSync(squadDir))
            return null;
        const agentsDir = path.join(squadDir, 'agents');
        const tasksDir = path.join(squadDir, 'tasks');
        const workflowsDir = path.join(squadDir, 'workflows');
        const checklistsDir = path.join(squadDir, 'checklists');
        const listFiles = (d) => (fs.existsSync(d) && fs.statSync(d).isDirectory() ? fs.readdirSync(d) : []);
        const agentFiles = listFiles(agentsDir).filter((f) => f.endsWith('.md'));
        const taskFiles = listFiles(tasksDir).filter((f) => f.endsWith('.md'));
        const workflowFiles = listFiles(workflowsDir).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'));
        const checklistFiles = listFiles(checklistsDir).filter((f) => f.endsWith('.md'));
        // Orphan agents: agent files not referenced in any task
        const orphanAgents = [];
        for (const af of agentFiles) {
            const aid = af.replace(/\.md$/, '');
            const referenced = taskFiles.some((tf) => fs.readFileSync(path.join(tasksDir, tf), 'utf-8').includes(aid));
            if (!referenced && taskFiles.length > 0)
                orphanAgents.push(aid);
        }
        // Unreferenced tasks: task files whose owner agent file is missing
        const unreferencedTasks = [];
        for (const tf of taskFiles) {
            const content = fs.readFileSync(path.join(tasksDir, tf), 'utf-8');
            const ownerMatch = content.match(/Owner:\s*([\w-]+)/);
            if (ownerMatch && !agentFiles.includes(`${ownerMatch[1]}.md`)) {
                unreferencedTasks.push(tf.replace(/\.md$/, ''));
            }
        }
        const suggestions = [];
        if (agentFiles.length === 0)
            suggestions.push('squad has no agents — at least one is required');
        if (taskFiles.length === 0)
            suggestions.push('squad has no tasks — consider adding at least one canonical task');
        if (workflowFiles.length === 0)
            suggestions.push('squad has no workflows — consider adding a primary workflow for discoverability');
        if (checklistFiles.length === 0)
            suggestions.push('squad has no quality gates — consider adding a checklist');
        if (orphanAgents.length > 0)
            suggestions.push(`${orphanAgents.length} agent(s) not referenced by any task`);
        if (unreferencedTasks.length > 0)
            suggestions.push(`${unreferencedTasks.length} task(s) reference a missing owner agent`);
        return {
            squadId,
            agentCount: agentFiles.length,
            taskCount: taskFiles.length,
            workflowCount: workflowFiles.length,
            checklistCount: checklistFiles.length,
            orphanAgents,
            unreferencedTasks,
            suggestions,
        };
    }
    // ─────────────────────────────────────────────────────────
    // v1.3 placeholders — stable surface, return clear "not implemented"
    // so callers can wire UI / CLI today and the real implementation
    // arrives behind the same API.
    // ─────────────────────────────────────────────────────────
    /**
     * Propose a SquadProposal from a free-text brief.
     *
     * Story 3.1 — OpenLife v1.3.
     *
     * Heuristic mode: produces a minimum-viable squad that the caller can
     * pass directly to create(). No API call — works offline, no keys
     * needed. Returns the proposal; caller decides whether to call create()
     * with it or refine first. The heuristic picks 3 default agents
     * (lead, executor, reviewer) and one task; for richer drafts, the
     * CapabilityGenesisEngine wires this together with deep-research-style
     * patterns.
     *
     * Brain-mode (live LLM proposal) lands in v1.4; the signature stays
     * stable so callers don't change.
     */
    design(brief, opts = {}) {
        const trimmed = (brief || '').trim();
        if (!trimmed)
            return { ok: false, error: 'invalid_brief' };
        const mode = opts.mode || 'professional';
        const id = opts.id || slugifyForId(trimmed).slice(0, 50);
        if (!/^[a-z0-9][a-z0-9._-]+$/i.test(id))
            return { ok: false, error: 'invalid_squad_id', };
        // Heuristic agent roster — same shape SquadCreator.create accepts.
        const agents = [
            { id: `${id}-lead`, name: `${id}-lead`, role: 'orchestrator' },
            { id: `${id}-executor`, name: `${id}-executor`, role: 'executor' },
        ];
        if (mode === 'professional' || mode === 'elite') {
            agents.push({ id: `${id}-reviewer`, name: `${id}-reviewer`, role: 'reviewer' });
        }
        if (mode === 'elite') {
            agents.push({ id: `${id}-strategist`, name: `${id}-strategist`, role: 'strategist' });
        }
        const tasks = [
            { id: `${id}-default`, ownerAgentId: agents[0].id, purpose: trimmed },
        ];
        const checklists = mode === 'elite' ? [{
                id: `${id}-quality-gate`,
                purpose: `quality gate for ${id}`,
                items: ['Brief understood', 'Agents assigned', 'Output validated'],
            }] : [];
        const proposal = {
            id,
            name: id,
            description: trimmed,
            version: '0.1.0',
            author: 'squad-creator.design',
            agents,
            tasks,
            checklists,
        };
        return { ok: true, proposal };
    }
    /**
     * Story 8.1 — LLM-driven design.
     *
     * If `brain.isAnyProviderAvailable()` returns true, asks the Brain for a
     * structured JSON proposal and parses it. On any parse / API failure,
     * falls back to the heuristic `design()` so the call is never lossy.
     *
     * Signature is additive: existing callers continue using sync `design()`.
     * No env flag — the presence of an API key IS the opt-in (locked decision
     * 2 of the v1.4 plan).
     */
    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 mode = opts.mode || 'professional';
        const id = opts.id || slugifyForId(trimmed).slice(0, 50);
        if (!/^[a-z0-9][a-z0-9._-]+$/i.test(id))
            return { ok: false, error: 'invalid_squad_id' };
        const systemPrompt = `You are SquadCreator. Given a brief and an authoring mode (quick|professional|elite), return a STRICT JSON object describing a squad. The output MUST be a single JSON object — no prose, no markdown fences. Shape:
{
  "agents": [{"id":"<slug>","role":"<role>"}],
  "tasks":  [{"id":"<slug>","ownerAgentId":"<agent-id>","purpose":"<one-line>"}],
  "checklists": [{"id":"<slug>","purpose":"<one-line>","items":["..."]}]
}
Rules:
- quick mode: 2 agents (lead, executor), 1 task, no checklist
- professional: + reviewer (3 agents), 1-2 tasks, optional checklist
- elite: + strategist (4 agents), 2-3 tasks, mandatory quality-gate checklist
- All ids must be lowercase a-z0-9 with hyphens
- Prefix ids with "${id}-" so they're unique within the squad`;
        const userMessage = `Brief: ${trimmed}\nMode: ${mode}\nSquad id: ${id}`;
        try {
            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.agents) || p.agents.length < 2)
                throw new Error('missing_or_too_few_agents');
            const proposal = {
                id, name: id, description: trimmed,
                version: '0.1.0',
                author: 'squad-creator.designWithBrain',
                agents: p.agents.map((a) => ({ id: a.id, name: a.id, role: a.role })),
                tasks: Array.isArray(p.tasks) ? p.tasks : [{ id: `${id}-default`, ownerAgentId: p.agents[0].id, purpose: trimmed }],
                checklists: Array.isArray(p.checklists) ? p.checklists : [],
            };
            return { ok: true, proposal, source: 'brain' };
        }
        catch {
            // Any failure (network, parse, schema) → heuristic fallback. Never lossy.
            const r = this.design(brief, opts);
            return r.ok ? { ok: true, proposal: r.proposal, source: 'heuristic' } : r;
        }
    }
    // ─────────────────────────────────────────────────────────
    // migrate — rewrite SQUAD.md frontmatter version atomically
    // ─────────────────────────────────────────────────────────
    migrate(squadId, fromVersion, toVersion) {
        if (!this.isValidId(squadId))
            return { ok: false, squadId, error: 'invalid_squad_id' };
        if (!this.isValidVersion(fromVersion) || !this.isValidVersion(toVersion)) {
            return { ok: false, squadId, error: 'invalid_version', detail: 'expect semver-like X.Y or X.Y.Z' };
        }
        const squadFile = path.join(this.catalogRoot, squadId, 'SQUAD.md');
        if (!fs.existsSync(squadFile))
            return { ok: false, squadId, error: 'squad_not_found' };
        const content = fs.readFileSync(squadFile, 'utf-8');
        const current = this.findSquadVersion(content);
        if (current && current !== fromVersion) {
            return { ok: false, squadId, error: 'version_mismatch', detail: `current=${current} expected=${fromVersion}` };
        }
        const next = this.rewriteSquadVersion(content, toVersion);
        (0, AtomicWriter_1.writeStringAtomic)(squadFile, next);
        return { ok: true, squadId, fromVersion, toVersion, filesChanged: [squadFile] };
    }
    findSquadVersion(content) {
        const fm = this.parseFrontmatter(content);
        if (fm.version)
            return fm.version.replace(/^["']|["']$/g, '');
        // Fall back to an embedded yaml `version: "X.Y.Z"` line.
        const m = content.match(/^version:\s*"?([^\n"']+?)"?\s*$/m);
        return m ? m[1] : null;
    }
    rewriteSquadVersion(content, toVersion) {
        // 1. Replace the embedded yaml version line (if any), outside frontmatter.
        const fmMatch = content.match(/^---\n[\s\S]*?\n---\n/);
        const head = fmMatch ? content.slice(0, fmMatch[0].length) : '';
        const body = fmMatch ? content.slice(fmMatch[0].length) : content;
        const bodyReplaced = body.replace(/^version:\s*"?[^\n"']+"?\s*$/m, `version: "${toVersion}"`);
        // 2. Update or insert the frontmatter version field.
        return this.rewriteFrontmatterField(head + bodyReplaced, 'version', `"${toVersion}"`);
    }
    // ─────────────────────────────────────────────────────────
    // extend — append component without re-rendering the whole squad
    // ─────────────────────────────────────────────────────────
    extend(squadId, component) {
        if (!this.isValidId(squadId))
            return { ok: false, squadId, error: 'invalid_squad_id' };
        const squadDir = path.join(this.catalogRoot, squadId);
        const squadFile = path.join(squadDir, 'SQUAD.md');
        if (!fs.existsSync(squadFile))
            return { ok: false, squadId, error: 'squad_not_found' };
        const fm = this.parseFrontmatter(fs.readFileSync(squadFile, 'utf-8'));
        const squadName = fm.name || squadId;
        const filesAdded = [];
        try {
            if (component.kind === 'agent') {
                fs.mkdirSync(path.join(squadDir, 'agents'), { recursive: true });
                const proposalLike = { id: squadId, name: squadName, description: fm.description || '', agents: [component.spec] };
                const vars = this.buildAgentVars(component.spec, proposalLike);
                const out = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'agent-template.md'), vars);
                const file = path.join(squadDir, 'agents', `${component.spec.id}.md`);
                if (fs.existsSync(file))
                    return { ok: false, squadId, error: 'component_exists', detail: file };
                (0, AtomicWriter_1.writeStringAtomic)(file, out);
                filesAdded.push(file);
            }
            else if (component.kind === 'task') {
                fs.mkdirSync(path.join(squadDir, 'tasks'), { recursive: true });
                const proposalLike = { id: squadId, name: squadName, description: fm.description || '', agents: [] };
                const vars = this.buildTaskVars(component.spec, proposalLike);
                const out = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'task-template.md'), vars);
                const file = path.join(squadDir, 'tasks', `${component.spec.id}.md`);
                if (fs.existsSync(file))
                    return { ok: false, squadId, error: 'component_exists', detail: file };
                (0, AtomicWriter_1.writeStringAtomic)(file, out);
                filesAdded.push(file);
            }
            else if (component.kind === 'checklist') {
                fs.mkdirSync(path.join(squadDir, 'checklists'), { recursive: true });
                const ckVars = {
                    CHECKLIST_NAME: component.spec.id,
                    SQUAD_NAME: squadName,
                    PURPOSE: component.spec.purpose,
                    ITEM_1: component.spec.items[0] || '',
                    ITEM_2: component.spec.items[1] || '',
                    ITEM_3: component.spec.items[2] || '',
                    ITEM_4: component.spec.items[3] || '',
                    ITEM_5: component.spec.items[4] || '',
                };
                const out = (0, TemplateRenderer_1.renderTemplateFile)(path.join(this.templateRoot, 'checklist-template.md'), ckVars);
                const file = path.join(squadDir, 'checklists', `${component.spec.id}.md`);
                if (fs.existsSync(file))
                    return { ok: false, squadId, error: 'component_exists', detail: file };
                (0, AtomicWriter_1.writeStringAtomic)(file, out);
                filesAdded.push(file);
            }
            else if (component.kind === 'workflow') {
                fs.mkdirSync(path.join(squadDir, 'workflows'), { recursive: true });
                const file = path.join(squadDir, 'workflows', `${component.id}.yaml`);
                if (fs.existsSync(file))
                    return { ok: false, squadId, error: 'component_exists', detail: file };
                (0, AtomicWriter_1.writeStringAtomic)(file, component.content);
                filesAdded.push(file);
            }
            else {
                return { ok: false, squadId, error: 'unknown_component_kind' };
            }
            this.appendExtensionLog(squadFile, component.kind, filesAdded);
            return { ok: true, squadId, kind: component.kind, filesAdded };
        }
        catch (err) {
            return { ok: false, squadId, error: 'extend_failed', detail: err instanceof Error ? err.message : String(err) };
        }
    }
    // ─────────────────────────────────────────────────────────
    // publish — hash + ledger append + status flip to active
    // ─────────────────────────────────────────────────────────
    publish(squadId) {
        if (!this.isValidId(squadId))
            return { ok: false, squadId, error: 'invalid_squad_id' };
        const squadFile = path.join(this.catalogRoot, squadId, 'SQUAD.md');
        if (!fs.existsSync(squadFile))
            return { ok: false, squadId, error: 'squad_not_found' };
        let content = fs.readFileSync(squadFile, 'utf-8');
        const fm = this.parseFrontmatter(content);
        if (fm.status === 'archived') {
            return { ok: false, squadId, error: 'squad_archived', detail: 'cannot publish archived squad' };
        }
        if (fm.status !== 'active') {
            content = this.rewriteFrontmatterField(content, 'status', 'active');
            (0, AtomicWriter_1.writeStringAtomic)(squadFile, 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: 'squad', id: squadId, sha256, file: squadFile, publishedAt: new Date().toISOString() }) + '\n';
        fs.appendFileSync(ledger, entry, 'utf-8');
        return { ok: true, squadId, sha256, ledgerEntry: entry.trim(), status: 'active' };
    }
    /**
     * Story 14.1 (v1.5) — Publish + push to a remote registry.
     *
     * Composes the existing local-ledger publish with RemotePublisher.
     * The local seal lands first (so callers still have a sealed asset
     * if the remote push fails). The returned envelope carries both
     * results: `ok` reflects the LOCAL publish; the remote outcome lives
     * on the `remote` field so failures can be inspected without
     * losing the local seal.
     *
     * Refuses to attempt the remote push if RemotePublisher is not
     * configured — returns `remote.error='remote_publish_not_configured'`.
     */
    async publishWithRemote(squadId) {
        const local = this.publish(squadId);
        if (!local.ok || !local.sha256)
            return local;
        const squadFile = path.join(this.catalogRoot, squadId, 'SQUAD.md');
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const { RemotePublisher } = require('./RemotePublisher');
        const remote = await new RemotePublisher().publishAsset('squad', squadId, local.sha256, squadFile);
        return { ...local, remote };
    }
    // ─────────────────────────────────────────────────────────
    // Helpers — used by migrate/extend/publish
    // ─────────────────────────────────────────────────────────
    isValidVersion(v) {
        return /^\d+\.\d+(\.\d+)?$/.test(v);
    }
    rewriteFrontmatterField(content, field, value) {
        const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
        if (!fmMatch) {
            // No frontmatter — prepend one.
            return `---\n${field}: ${value}\n---\n${content}`;
        }
        const block = fmMatch[1];
        const lines = block.split('\n');
        const idx = lines.findIndex((l) => l.match(new RegExp(`^${field}:`)));
        if (idx >= 0)
            lines[idx] = `${field}: ${value}`;
        else
            lines.push(`${field}: ${value}`);
        const newBlock = lines.join('\n');
        return content.replace(fmMatch[0], `---\n${newBlock}\n---\n`);
    }
    appendExtensionLog(squadFile, kind, files) {
        try {
            const marker = '\n## Extensions\n';
            let content = fs.readFileSync(squadFile, 'utf-8');
            if (!content.includes('## Extensions'))
                content += marker;
            const ts = new Date().toISOString();
            content += `- ${ts} added ${kind}: ${files.map((f) => path.basename(f)).join(', ')}\n`;
            (0, AtomicWriter_1.writeStringAtomic)(squadFile, content);
        }
        catch { /* extension log is best-effort */ }
    }
    // ─────────────────────────────────────────────────────────
    // Internals
    // ─────────────────────────────────────────────────────────
    buildSquadVars(p) {
        const agentRows = p.agents.map((a) => `| ${a.id} | ${a.role} |`).join('\n');
        const tagsList = (p.tags || []).map((t) => `  - ${t}`).join('\n');
        const componentAgents = p.agents.map((a) => `    - ${a.id}.md`).join('\n');
        const componentTasks = (p.tasks || []).map((t) => `    - ${t.id}.md`).join('\n');
        const componentWorkflows = (p.workflows || []).map((w) => `    - ${w}`).join('\n');
        const componentChecklists = (p.checklists || []).map((c) => `    - ${c.id}.md`).join('\n');
        return {
            SQUAD_ID: p.id,
            SQUAD_NAME: p.name,
            SOURCE: p.source || 'aiobuilder-built',
            STATUS: p.status || 'draft',
            DESCRIPTION: p.description,
            VERSION: p.version || '1.0.0',
            SHORT_TITLE: p.shortTitle || p.name,
            AUTHOR: p.author || 'OpenLife',
            LICENSE: p.license || 'MIT',
            SLASH_PREFIX: p.slashPrefix || p.id,
            TAGS_LIST: tagsList || '  - squad',
            AGENTS_TABLE: agentRows,
            COMPONENT_AGENTS: componentAgents,
            COMPONENT_TASKS: componentTasks || '    []',
            COMPONENT_WORKFLOWS: componentWorkflows || '    []',
            COMPONENT_CHECKLISTS: componentChecklists || '    []',
        };
    }
    buildAgentVars(a, p) {
        const base = {
            AGENT_ID: a.id,
            AGENT_NAME: a.name,
            AGENT_ROLE: a.role,
            AGENT_TITLE: a.name,
            SQUAD_NAME: p.name,
            ICON: '🤖',
            WHEN_TO_USE: `when ${a.role} is needed`,
            DESCRIPTION: `${a.name} — ${a.role}`,
            ROLE: a.role,
            STYLE: 'analytical',
            IDENTITY: `Specialist in ${a.role}`,
            FOCUS: a.role,
            PRINCIPLE_1: 'Respect tool permissions',
            PRINCIPLE_2: 'Emit audit events',
            PRINCIPLE_3: 'Defer to human approval beyond autonomy budget',
            COMMAND_1: 'execute',
            COMMAND_1_DESC: 'Execute the canonical task for this role',
            CAPABILITIES_LIST: '- TBD',
        };
        return { ...base, ...(a.vars || {}) };
    }
    buildTaskVars(t, p) {
        const base = {
            TASK_NAME: t.id,
            OWNER_AGENT: t.ownerAgentId,
            SQUAD_NAME: p.name,
            PURPOSE: t.purpose,
            INPUT_1: 'TBD',
            INPUT_2: 'TBD',
            OUTPUT_1: 'TBD',
            OUTPUT_2: 'TBD',
            STEP_1: 'Load context',
            STEP_2: 'Execute primary action',
            STEP_3: 'Record evidence and exit',
            VALIDATION_1: 'Output exists and is non-empty',
            VALIDATION_2: 'Audit event emitted',
            NOTES: '',
        };
        return { ...base, ...(t.vars || {}) };
    }
    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;
    }
    parseComponentRefs(content) {
        // Look for the embedded squad.yaml block; parse components: lists.
        const out = { agents: [], tasks: [], workflows: [], checklists: [] };
        const section = content.match(/components:\s*\n([\s\S]*?)(?=\nconfig:|\n```|$)/);
        if (!section)
            return out;
        let current = null;
        for (const line of section[1].split('\n')) {
            const keyMatch = line.match(/^\s\s(agents|tasks|workflows|checklists):/);
            if (keyMatch) {
                current = keyMatch[1];
                continue;
            }
            const itemMatch = line.match(/^\s\s\s\s-\s+(.+)$/);
            if (itemMatch && current) {
                const v = itemMatch[1].trim();
                if (v && v !== '[]')
                    out[current].push(v);
            }
        }
        return out;
    }
    isValidId(id) {
        return /^[a-z0-9][a-z0-9._-]+$/i.test(id);
    }
}
exports.SquadCreator = SquadCreator;
function slugifyForId(s) {
    return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
}
/**
 * Story 8.1 helper — extract the first JSON object from raw model output.
 * Tolerates markdown fences (```json ... ```) and surrounding prose.
 * Returns the parsed object or null. Never throws.
 */
function extractFirstJson(raw) {
    if (typeof raw !== 'string')
        return null;
    // Strip common markdown fences first.
    let s = raw.replace(/```(?:json)?\s*\n?/gi, '').replace(/```/g, '');
    const start = s.indexOf('{');
    if (start === -1)
        return null;
    // Find matching closing brace by depth counting (handles nested).
    let depth = 0;
    for (let i = start; i < s.length; i++) {
        const c = s[i];
        if (c === '{')
            depth++;
        else if (c === '}') {
            depth--;
            if (depth === 0) {
                try {
                    return JSON.parse(s.slice(start, i + 1));
                }
                catch {
                    return null;
                }
            }
        }
    }
    return null;
}
