/**
 * `olam flywheel emit-breadcrumb` — K7 breadcrumb emitter (D24/D27/D28 compliant).
 *
 * Port of `shared/engineering/scripts/emit-breadcrumb.py`. Matches the same record
 * schema, exit codes, and stdout message format. Adds `fcntl.flock`-equivalent
 * cross-process locking via `withFileLock` from `@olam/core/lib/file-lock` —
 * closes K1 adversarial finding #1 (the parallel K7 write corruption scenario
 * where two concurrent personas writing >4KB payloads interleave bytes).
 *
 * Lock semantics: per-target-directory advisory lock. Lock file is
 * `<breadcrumb-dir>/.flywheel-emit.lock` (distinct filename from skill-sync's
 * `.olam-sync.lock` so the two domains don't collide if their lock-dirs ever
 * overlap).
 *
 * Plan reference: docs/plans/olam-flywheel-cli-namespace/phase-a-tasks.md § A3
 * Closes: K1 pass-1 adversarial finding FS-1 (P1/likely/risk-3.0).
 */
import { mkdirSync, appendFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { dirname, join } from 'node:path';
import { randomUUID } from 'node:crypto';
import { withFileLock } from '@olam/core/src/lib/file-lock.js';
const VALID_SEVERITIES = new Set(['critical', 'high', 'medium', 'low', 'info', 'warn']);
const PROMPT_FEEDING_FIELDS = ['extracted_pattern', 'severity', 'affected_persona', 'proposed_edit'];
const BREADCRUMBS_BASE = join(homedir(), '.local', 'share', 'claude', 'breadcrumbs');
const LOCK_FILENAME = '.flywheel-emit.lock';
function buildRecord(opts) {
    const rec = {
        breadcrumb_id: randomUUID().replace(/-/g, ''),
        emitted_at: new Date().toISOString(),
        emitted_by_skill: opts.skill,
        pass_number: opts.pass !== undefined ? Number.parseInt(opts.pass, 10) : 0,
        plan_slug: opts.plan,
        project_slug: opts.project ?? 'claude-skills',
        extracted_pattern: opts.pattern,
        severity: opts.severity,
        affected_persona: opts.persona,
        proposed_edit: opts.edit,
        notes: opts.notes ?? '',
        raw_body: opts.rawBody ?? '',
        attribution: opts.attribution ?? '',
        schema_version: '1',
    };
    // K5 fields: present-only-when-supplied. Use key absence to distinguish "not scored"
    // from "scored zero" (K4 readers depend on this).
    if (opts.k5ScoresJson !== undefined) {
        try {
            rec.k5_scores = JSON.parse(opts.k5ScoresJson);
        }
        catch (err) {
            process.stderr.write(`[K7-reject] --k5-scores-json is not valid JSON: ${err instanceof Error ? err.message : 'unknown'}\n`);
            process.exit(2);
        }
    }
    if (opts.k5Boost !== undefined)
        rec.k5_boost = Number.parseFloat(opts.k5Boost);
    if (opts.k5Composite !== undefined)
        rec.k5_composite = Number.parseInt(opts.k5Composite, 10);
    if (opts.k5ScoredAt !== undefined)
        rec.k5_scored_at = opts.k5ScoredAt;
    return rec;
}
function validatePromptFeeding(rec) {
    const missing = [];
    for (const field of PROMPT_FEEDING_FIELDS) {
        const val = rec[field];
        if (val === undefined || val === null || (typeof val === 'string' && val.trim() === '')) {
            missing.push(field);
        }
    }
    return missing;
}
function destPath(projectSlug) {
    const today = new Date().toISOString().slice(0, 10); // UTC YYYY-MM-DD
    return join(BREADCRUMBS_BASE, projectSlug, `${today}.jsonl`);
}
async function emitBreadcrumb(opts) {
    if (!VALID_SEVERITIES.has(opts.severity)) {
        process.stderr.write(`[K7-reject] invalid severity '${opts.severity}'; expected one of: ${Array.from(VALID_SEVERITIES).sort().join(', ')}\n`);
        process.exit(2);
    }
    const rec = buildRecord(opts);
    const missing = validatePromptFeeding(rec);
    if (missing.length > 0) {
        process.stderr.write(`[K7-reject] missing or empty prompt-feeding fields: ${JSON.stringify(missing.sort())}\n`);
        process.exit(2);
    }
    const path = destPath(rec.project_slug);
    const lockDir = dirname(path);
    mkdirSync(lockDir, { recursive: true });
    // withFileLock serializes concurrent emitters writing to the same project/date
    // file. POSIX O_APPEND is only atomic for payloads ≤PIPE_BUF (~4KB); large
    // raw_body fields would interleave without this. K1 epic dispatch (11 parallel
    // personas) is exactly the case the original Python script's comment flagged
    // as "add fcntl.flock if parallel emitters exist".
    const line = JSON.stringify(rec) + '\n';
    await withFileLock(lockDir, () => {
        appendFileSync(path, line, 'utf8');
    }, {
        lockFilename: LOCK_FILENAME,
        reason: `flywheel emit-breadcrumb skill=${opts.skill} plan=${opts.plan}`,
        // Tight timeout — breadcrumb writes are <100ms; long backoff is a sign
        // of pathology, not contention. 5s gives 200 retries at 25ms each.
        acquireTimeoutMs: 5_000,
    });
    process.stdout.write(`[K7-emit] ${path}: ${rec.extracted_pattern} (${rec.severity})\n`);
}
export function registerFlywheelEmitBreadcrumb(parent) {
    parent
        .command('emit-breadcrumb')
        .description('Append a K7 breadcrumb JSONL row (parallel-safe via withFileLock)')
        .requiredOption('--skill <slug>', 'Chain skill slug (e.g. plan-hard)')
        .requiredOption('--plan <slug>', 'Plan slug (e.g. olam-flywheel-cli-namespace)')
        .requiredOption('--pattern <name>', 'Pattern name (concise slug-like label)')
        .requiredOption('--severity <level>', 'Severity: critical | high | medium | low | info | warn')
        .requiredOption('--persona <slug>', "Affected persona slug or 'chain'")
        .requiredOption('--edit <text>', 'Structured patch description')
        .option('--notes <text>', 'Free-form operator notes (display-only)')
        .option('--pass <int>', 'Pass number (display-only)')
        .option('--project <slug>', 'Project slug', 'claude-skills')
        .option('--raw-body <text>', 'Free-form markdown context (display-only)')
        .option('--attribution <text>', 'Source attribution (display-only)')
        .option('--k5-scores-json <json>', 'K5 sub-scores as JSON (display-only; omitted if not supplied)')
        .option('--k5-boost <float>', 'K5 boost multiplier (display-only; omitted if not supplied)')
        .option('--k5-composite <int>', 'K5 composite score (display-only; omitted if not supplied)')
        .option('--k5-scored-at <date>', 'ISO date of most recent K5 scoring pass')
        .action(async (opts) => {
        try {
            await emitBreadcrumb(opts);
        }
        catch (err) {
            process.stderr.write(`[K7-error] emit-breadcrumb failed: ${err instanceof Error ? err.message : 'unknown error'}\n`);
            process.exit(1);
        }
    });
}
//# sourceMappingURL=emit-breadcrumb.js.map