#!/usr/bin/env node
// write_skill.mjs — extracting-fable step 7. DETERMINISTICALLY (re)creates the `fable` mode-skill.
//
// Why this exists: step 7 used to be a prose "agent, please author the skill" instruction — non-deterministic,
// and it SKIPPED when a skill already existed (the gap that left a stale skill in place on a real run). This
// generator ALWAYS create-or-overwrites ~/.claude/skills/fable/ from a canonical template, so every pipeline
// run yields the same high-quality, self-contained skill.
//
// What is template vs injected:
//   - Validated levers (opener 20->0, terseness) + honesty anchor + lessons = CANONICAL TEMPLATE, verbatim.
//     These are carried matched-pair assets (§Validate), NOT regenerable from the corpus — never auto-rewrite them.
//   - Corpus signature numbers = INJECTED live from analyze.py into {{SIGNATURE_BLOCK}}.
//   - §D inferred strategy = INJECTED from <out>/strategy/inferred.md into {{INFERRED_STRATEGY}} (needs the
//     step-6 ultrathink pass; placeholder if absent).
//
// Usage: node pipeline/write_skill.mjs [--model claude-fable-5] [--baseline claude-opus-4-8]
//        [--out ~/.claude/fable] [--skill-dir ~/.claude/skills/fable] [--strategy <file>] [--dry <dir>]

import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';

const HERE = path.dirname(fileURLToPath(import.meta.url));
const expand = p => p.replace(/^~/, os.homedir());

function arg(name, def) {
  const i = process.argv.indexOf(`--${name}`);
  return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : def;
}

const MODEL = arg('model', 'claude-fable-5');
const BASELINE = arg('baseline', 'claude-opus-4-8');

// GUARD — this generator is fable-specific by design. The canonical template carries fable's MATCHED-PAIR
// validated levers (self-opener 20->0, words 47->20) verbatim in §A/§Validate. Splice another model's signature
// into that prose and you ship a skill asserting fable's RED->GREEN numbers for a different model — the exact
// "no validation -> no claim" violation extracting-fable exists to prevent. Generalizing requires parameterizing
// the template name + frontmatter + skill-dir AND dropping the carried levers (re-run step 8 per model). Until
// then: refuse. --model/--baseline still tune which transcripts the signature is measured over for fable itself.
if (MODEL !== 'claude-fable-5') {
  console.error(`REFUSED: write_skill.mjs is fable-specific (carries fable's matched-pair validated levers).`);
  console.error(`  --model ${MODEL} would label a "${MODEL}" skill with fable's validated numbers = overclaim.`);
  console.error(`  To target another model: parameterize template name/frontmatter/skill-dir + drop carried levers + re-run step 8.`);
  process.exit(2);
}
const OUT = expand(arg('out', '~/.claude/fable'));
const DRY = arg('dry', null);                       // throwaway dir for verify-by-diff; skips kb copy
const SKILL_DIR = DRY ? expand(DRY) : expand(arg('skill-dir', '~/.claude/skills/fable'));
const STRATEGY = expand(arg('strategy', path.join(OUT, 'strategy', 'inferred.md')));
const VERIFICATION = expand(arg('verification', path.join(OUT, 'verification', 'profile.md')));
const TEMPLATE = path.join(HERE, '..', 'templates', 'fable.SKILL.md.tmpl');

// 1. Live corpus signature from analyze.py — embed the load-bearing lines verbatim (robust to format tweaks).
let sigBlock;
try {
  const raw = execFileSync('python3', [path.join(HERE, 'analyze.py'), '--model', MODEL, '--baseline', BASELINE],
    { encoding: 'utf8' });
  const keep = raw.split('\n').filter(l => /\bTARGET\b|\bBASELINE\b|thinking blocks|ENCRYPTED|0 chars/.test(l));
  sigBlock = '```\n' + keep.join('\n').trim() + '\n```';
} catch (e) {
  sigBlock = '```\n[analyze.py failed — re-run step 2 and regenerate]\n```';
  console.error('WARN analyze.py failed:', e.message);
}

// 2. Inferred strategy (§D) — from step 6, or an explicit placeholder (never silently empty).
let strat;
if (fs.existsSync(STRATEGY)) {
  strat = fs.readFileSync(STRATEGY, 'utf8').trim();
} else {
  strat = `*No inferred-strategy artifact at ${STRATEGY}.* Run step 6 (\`contrast.mjs\` + the ultrathink agent),\n` +
    `save the agent's [INFERRED] model there, and re-run this generator. Until then §D is intentionally empty —\n` +
    `do NOT hand-fill it with guesses (that would be the confabulation §0 forbids).`;
}

// 2b. Verification profile (§E) — from the mining pass, or an explicit placeholder (never silently empty).
let verif;
if (fs.existsSync(VERIFICATION)) {
  verif = fs.readFileSync(VERIFICATION, 'utf8').trim();
} else {
  verif = `*No verification-profile artifact at ${VERIFICATION}.* Run the §E mining step (\`pipeline/verify_scan.mjs\`\n` +
    `+ synthesis), save the Fable-distinctive verification deltas there, and re-run this generator. Until then §E is\n` +
    `intentionally empty — do NOT hand-fill it with generic verification advice (that is the dilution §E exists to avoid).`;
}

// 3. Fill template. Fail loud if a slot is missing — a silent no-replace ships a broken skill.
let doc = fs.readFileSync(TEMPLATE, 'utf8');
for (const [slot, val] of [['{{SIGNATURE_BLOCK}}', sigBlock], ['{{INFERRED_STRATEGY}}', strat],
                           ['{{VERIFICATION_PROFILE}}', verif]]) {
  if (!doc.includes(slot)) { console.error(`FATAL: template missing slot ${slot}`); process.exit(1); }
  doc = doc.replace(slot, val);
}

// 4. ALWAYS create-or-overwrite. Never skip-because-exists — that skip is the bug this step fixes.
fs.mkdirSync(SKILL_DIR, { recursive: true });
const skillPath = path.join(SKILL_DIR, 'SKILL.md');
fs.writeFileSync(skillPath, doc);

// 5. Self-containment: copy the scripts the skill calls at runtime into <skill-dir>/kb/ (skip on --dry).
let copied = [];
if (!DRY) {
  const kb = path.join(SKILL_DIR, 'kb');
  fs.mkdirSync(kb, { recursive: true });
  for (const f of ['query_kb.py', 'measure_agent.py', 'build_kb.py']) {
    const src = path.join(HERE, f);
    if (fs.existsSync(src)) { fs.copyFileSync(src, path.join(kb, f)); copied.push(f); }
    else console.error(`WARN missing pipeline script: ${f}`);
  }
}

console.log(`wrote ${skillPath} (${doc.length} chars)`);
console.log(`  signature: ${sigBlock.includes('failed') ? 'FAILED' : 'live'}  ·  §D: ${fs.existsSync(STRATEGY) ? 'injected' : 'placeholder'}  ·  §E: ${fs.existsSync(VERIFICATION) ? 'injected' : 'placeholder'}`);
if (!DRY) console.log(`  kb scripts copied: ${copied.join(', ') || 'none'}`);
else console.log(`  --dry: kb copy skipped`);
