/**
 * `olam flywheel sanitize-persona-output` — K1 persona output sanitizer.
 *
 * Port of `shared/engineering/scripts/sanitize-persona-output.py`. Implements
 * the 4 sanitization rules from chief-of-staff-dispatch.md § Sanitization
 * (numbered to match the Python source):
 *
 *   0. Control-sequence strip (ADV-C2-02): reject ANSI escapes, bidi-override
 *      codepoints (U+202A-202E, U+2066-2069), and raw C0 control bytes (except
 *      \n and \t). Applied FIRST, before header check.
 *   1. Header-rejection: reject persona output containing plan-body section
 *      headers (`## Open questions`, `## K1 persona review`,
 *      `## Significant decisions`, `## Pass log`) — these are skill-owned
 *      sections; persona output must not inject into them.
 *   2. Pipe-escaping: replace `|` with `\|` to prevent table-column injection
 *      when persona output lands in OQ rows.
 *   3. Appendix size cap: truncate to ≤800 tokens (≈3200 chars) with
 *      `[...truncated by K1 size cap]` marker.
 *
 * CLI:
 *   echo "<persona output>" | olam flywheel sanitize-persona-output
 *   Exit 0 = PASS (sanitized output on stdout)
 *   Exit 1 = REJECT (reason on stderr, nothing on stdout)
 *
 * Plan reference: docs/plans/olam-flywheel-cli-namespace/phase-a-tasks.md § A6
 */
const FORBIDDEN_HEADERS = [
    '## Open questions',
    '## K1 persona review',
    '## Significant decisions',
    '## Pass log',
];
const TOKEN_CAP = 800;
const CHARS_PER_TOKEN = 4;
const CHAR_CAP = TOKEN_CAP * CHARS_PER_TOKEN;
// Bidi-override codepoints U+202A-U+202E + U+2066-U+2069. These can visually
// reorder terminal output to hide malicious content.
const BIDI_OVERRIDE_RE = /[‪-‮⁦-⁩]/;
// C0 control bytes except \n (0x0A) and \t (0x09). Includes ESC (0x1B).
// eslint-disable-next-line no-control-regex
const C0_CONTROL_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
// ANSI/VT escape sequences: ESC [ ... and ESC ] ... (OSC). Mirrors the Python
// _ANSI_ESCAPE_RE shape: CSI sequences, OSC sequences (BEL or ST terminator),
// charset selection, and single-character ESC commands.
// eslint-disable-next-line no-control-regex
const ANSI_ESCAPE_RE = /\x1b(?:\[[0-9;]*[a-zA-Z]|\][^\x07]*(?:\x07|\x1b\\)|[()][0-9A-Za-z]|[^[])/;
/**
 * Sanitize persona output before K1 injection into the plan body.
 */
export function sanitize(text) {
    // Rule 0 (ADV-C2-02) — Control-sequence rejection, applied FIRST.
    if (ANSI_ESCAPE_RE.test(text)) {
        return { passed: false, sanitized: '', reason: 'control-sequence: output contains ANSI escape sequence (\\x1b)' };
    }
    if (BIDI_OVERRIDE_RE.test(text)) {
        return { passed: false, sanitized: '', reason: 'control-sequence: output contains bidi-override codepoint' };
    }
    if (C0_CONTROL_RE.test(text)) {
        return { passed: false, sanitized: '', reason: 'control-sequence: output contains C0 control byte (non-printable)' };
    }
    // Rule 1 — Header-rejection (case-sensitive, at start of any line).
    for (const header of FORBIDDEN_HEADERS) {
        const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        const pattern = new RegExp(`^${escaped}\\s*$`, 'm');
        if (pattern.test(text)) {
            return { passed: false, sanitized: '', reason: `header-injection: output contains forbidden header '${header}'` };
        }
    }
    // Rule 2 — Pipe-escaping (escape ALL `|` per the Python script's policy:
    // persona output should not be generating raw table syntax anyway).
    let sanitized = text.replace(/\|/g, '\\|');
    // Rule 3 — Appendix size cap.
    if (sanitized.length > CHAR_CAP) {
        sanitized = sanitized.slice(0, CHAR_CAP) + '\n[...truncated by K1 size cap]';
    }
    return { passed: true, sanitized, reason: '' };
}
async function readStdin() {
    const chunks = [];
    for await (const chunk of process.stdin) {
        chunks.push(chunk);
    }
    return Buffer.concat(chunks).toString('utf8');
}
export function registerFlywheelSanitizePersonaOutput(parent) {
    parent
        .command('sanitize-persona-output')
        .description('Pipe persona output through K1 sanitization (rejects ANSI/bidi/headers; escapes pipes; truncates >3200 chars)')
        .action(async () => {
        const input = await readStdin();
        const result = sanitize(input);
        if (result.passed) {
            process.stdout.write(result.sanitized);
            process.exit(0);
        }
        else {
            process.stderr.write(`REJECTED: ${result.reason}\n`);
            process.exit(1);
        }
    });
}
//# sourceMappingURL=sanitize-persona-output.js.map