/**
 * Tier 1 substrate: the reasoning that does NOT go to the LLM.
 *
 * We own the micro-harness (testExec), so failure messages are structured by
 * construction — classification is string work, not parsing luck. From the
 * classKeys flow three zero-token capabilities:
 *
 *   1. taxonomy      — every failure gets a deterministic classKey
 *   2. mechanical    — wiring failures (import paths, missing imports of a
 *                      sibling export) are FIXED without a model draw;
 *                      IR case-body syntax is also healed here (SET 1)
 *   3. lessons       — classKey-matched idioms from the corpus and from past
 *                      runs ride into repair prompts; healed classes distill
 *                      new lessons, so the substrate compounds across runs
 *
 * The LLM only sees what survives the mechanical pass — and it sees it one
 * failure class at a time (serial resolution), never as a shotgun blast.
 *
 * SET 39c — clade/superbuffer escalation is residual AFTER stratum exhaust or
 * catalog-drift only; never the first response to a classifiable error.
 * (Do not edit superbuffer.ts from this stream — Integration wires the gate.)
 */
import type { FeatureSpec, GeneratedFile } from '../core/types';
import {
  classifyCataloged,
  formatCatalogDriftTripwire,
  type ErrorStratum,
} from './errorCatalog.generated';
import {
  caseSyntaxErrors,
  dynamicEvalLint,
  parseExecutionCrash,
  rewriteJestToVi,
  suiteSyntaxError,
} from './testExec';

export type FailureKind =
  | 'unresolved-import'
  | 'undefined-symbol'
  | 'is-not-a-function'
  | 'transpile'
  | 'regex-literal'
  | 'duplicate-decl'
  | 'assertion'
  | 'throw-mismatch'
  | 'runtime'
  | 'crash'
  | 'empty-draw'
  | 'catalog-drift';

export interface ClassifiedFailure {
  kind: FailureKind;
  classKey: string;
  /** Test case name, or '__suite__' for module-level crashes. */
  name: string;
  message: string;
  /** SET 39 — catalog template + stratum (catalog-drift when unmatched). */
  templateId?: string;
  stratum?: ErrorStratum | 'catalog-drift';
  /** Extracted structure, when the message yields it. */
  detail: {
    spec?: string;
    importer?: string;
    symbol?: string;
    matcher?: string;
    file?: string;
    /** SET 35a — case name from execution-position attribution. */
    caseName?: string;
    /** SET 36f — assertion / throw-mismatch expected vs received. */
    expected?: string;
    received?: string;
    /** SET 86 — the literal regex source printed by the runtime, used to
     * attribute an invalid-regex crash to the file that contains it. */
    pattern?: string;
    /** SET 36f — throw-mismatch: whether the suite expected a throw. */
    expectedThrow?: boolean;
    threw?: boolean;
    thrownMessage?: string;
  };
}

/** SET 20 — crash classes that must not naked-fall-through to K draws. */
export function isCrashClass(kind: FailureKind | undefined | null): boolean {
  return kind === 'crash' || kind === 'duplicate-decl' || kind === 'regex-literal';
}

/**
 * SET 36f — render expected vs received (and throw/no-throw) into the repair
 * prompt so semantic classes do not fly blind.
 */
export function formatAssertionDiff(failure: ClassifiedFailure): string | null {
  if (failure.kind === 'assertion') {
    if (failure.detail.expected == null && failure.detail.received == null) return null;
    return `assertion diff (${failure.detail.matcher ?? 'matcher'}): expected ${failure.detail.expected ?? '(missing)'} , received ${failure.detail.received ?? '(missing)'}`;
  }
  if (failure.kind === 'throw-mismatch') {
    const want = failure.detail.expectedThrow === false ? 'no-throw' : 'throw';
    const got = failure.detail.threw
      ? `threw: ${failure.detail.thrownMessage ?? failure.detail.received ?? '(unknown)'}`
      : 'no-throw';
    return `throw-mismatch: expected ${want} vs actual ${got}`
      + (failure.detail.expected != null ? ` (matcher expected: ${failure.detail.expected})` : '');
  }
  return null;
}

/** Normalized crash signature for quarantine / external-fault (SET 20c/20d). */
export function normalizeCrashSignature(message: string): string {
  return classifyFailure('__suite__', message).classKey;
}

/**
 * SET 20b — LLM repair on a crash requires detail.file, and when a crashing
 * identifier is known the attributed file must contain/reference it.
 */
export function crashRepairPrecondition(
  failure: ClassifiedFailure,
  artifact: GeneratedFile[],
  suite?: GeneratedFile,
): boolean {
  const file = failure.detail.file;
  if (!file) return false;
  const everything = suite ? [...artifact, suite] : artifact;
  const target = everything.find(f => f.path === file)
    ?? everything.find(f => keyOf(f.path) === keyOf(file));
  if (!target) return false;
  const ident = failure.detail.symbol;
  if (!ident) return true;
  return new RegExp(`\\b${ident.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(target.content);
}

export interface Tier1Lesson {
  classKey: string;
  idiom: string;
  resolution: string;
  ts: string;
}

/** SET 37c — mock-API classKeys that NEVER request a model draw. */
const MOCK_API_CLASSKEY_RE =
  /^(undefined-symbol\|(jest|fn)|is-not-a-function\|spyOn)$/;

/** True when substrate must refuse model draws (mechanical / fixed path only). */
export function refusesModelDraw(classKey: string | undefined | null): boolean {
  if (!classKey) return false;
  return MOCK_API_CLASSKEY_RE.test(classKey) || classKey === 'empty-draw';
}

/** SET 101a — classKey is the stub-not-implemented absence class. */
export function isStubNotImplementedClass(classKey: string | undefined | null): boolean {
  if (!classKey) return false;
  return classKey === 'runtime|stub-not-implemented'
    || /(^|\|)stub-not-implemented(\||$)/.test(classKey);
}

/**
 * SET 101a — owned symbol body is still the stub (byte-equal body or whole file).
 * Absence of implementation, not a defective implementation.
 */
export function isOwnedSymbolStillStub(
  implContent: string,
  stubContent: string,
  symbol?: string | null,
): boolean {
  if (implContent.trim() === stubContent.trim()) return true;
  if (!symbol) {
    return /stub:\s*not implemented/i.test(implContent)
      && /stub:\s*not implemented/i.test(stubContent);
  }
  const bodyOf = (src: string): string | null => {
    const esc = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const fn = new RegExp(
      `export\\s+(?:async\\s+)?function\\s+${esc}\\s*\\([^)]*\\)[^{]*\\{`,
    ).exec(src);
    const arrow = !fn
      ? new RegExp(`(?:export\\s+)?const\\s+${esc}\\s*=[^=>{;\\n]*=>\\s*`).exec(src)
      : null;
    const classCtor = !fn && !arrow
      ? new RegExp(
        `(?:export\\s+)?class\\s+${esc}\\b[^{]*\\{[\\s\\S]*?constructor\\s*\\([^)]*\\)\\s*\\{`,
      ).exec(src)
      : null;
    const head = fn ?? arrow ?? classCtor;
    if (!head) return null;
    const braceAt = src.indexOf('{', head.index! + head[0].length - 1);
    if (braceAt < 0) return null;
    let depth = 0;
    for (let i = braceAt; i < src.length; i++) {
      if (src[i] === '{') depth++;
      else if (src[i] === '}') {
        depth--;
        if (depth === 0) return src.slice(braceAt + 1, i).trim();
      }
    }
    return null;
  };
  const a = bodyOf(implContent);
  const b = bodyOf(stubContent);
  if (a != null && b != null) return a === b;
  return /stub:\s*not implemented/i.test(implContent)
    && implContent.trim() === stubContent.trim();
}

/**
 * SET 47b — scaffold-owned `undefined-symbol|<sym>` never burns impl redraws.
 * Suite-import surgery (freeze / mechanical) is the only licensed fix.
 */
export function refusesImplRedrawForOwnedSymbol(
  classKey: string | undefined | null,
  spec: FeatureSpec,
): boolean {
  if (!classKey || !/^undefined-symbol\|/.test(classKey)) return false;
  const sym = classKey.slice('undefined-symbol|'.length);
  if (!sym || sym === 'jest' || sym === 'fn') return false;
  return ownerFileForSymbol(spec, sym) != null;
}

/** SET 37d — empty draws never count as attempts in drop messaging. */
export function formatEmptyDraws(n: number): string {
  const k = Math.max(0, Math.floor(n));
  return `${k} empty draw${k === 1 ? '' : 's'}`;
}

/** SET 37d — detect empty model body / unparseable-at-(empty). */
export function isEmptyDrawMessage(message: string): boolean {
  const m = message ?? '';
  if (/unparseable at "\(empty\)"/i.test(m)) return true;
  if (/unparseable:.*at "\(empty\)"/i.test(m)) return true;
  if (/^model returned empty$/i.test(m.trim())) return true;
  if (m.trim() === '' || m.trim() === '(empty)') return true;
  return false;
}

/** Attach catalog templateId/stratum; never clobber a richer classKey. */
function withCatalog(failure: ClassifiedFailure, message: string): ClassifiedFailure {
  const cat = classifyCataloged(message);
  return { ...failure, templateId: cat.templateId, stratum: cat.stratum };
}

function kindFromCatalogClassKey(classKey: string): FailureKind {
  const head = classKey.split('|')[0] ?? 'runtime';
  const known: FailureKind[] = [
    'unresolved-import', 'undefined-symbol', 'is-not-a-function', 'transpile',
    'regex-literal', 'duplicate-decl', 'assertion', 'throw-mismatch', 'runtime',
    'crash', 'empty-draw', 'catalog-drift',
  ];
  return (known as string[]).includes(head) ? head as FailureKind : 'runtime';
}

// ---------------------------------------------------------------------------
// 1. Taxonomy
// ---------------------------------------------------------------------------

export function classifyFailure(name: string, message: string): ClassifiedFailure {
  return withCatalog(classifyFailureCore(name, message), message);
}

function classifyFailureCore(name: string, message: string): ClassifiedFailure {
  const first = message.split('\n')[0];

  // SET 37d — empty model body / unparseable at "(empty)" is a DISTINCT class.
  if (isEmptyDrawMessage(message)) {
    return {
      kind: 'empty-draw',
      classKey: 'empty-draw',
      name,
      message,
      detail: {},
    };
  }

  // SET 35a/36a — execution-position / load-line attribution (may wrap a deeper message).
  const execPos = parseExecutionCrash(message);
  if (execPos) {
    const nested = message.split('\n').filter(l =>
      !/^\s*at\s/.test(l)
      && !/^crash while executing /.test(l)
      && !/^crash while loading /.test(l)).join('\n').trim();
    const inner = nested ? classifyFailure(name, nested) : null;
    const fileHint = execPos.localLine != null
      ? (execPos.module.endsWith('.ts') || execPos.module.endsWith('.js')
        ? execPos.module
        : `${execPos.module}.ts`)
      : execPos.caseName
        ? (execPos.module.endsWith('.test') || /tests?\//.test(execPos.module)
          ? (execPos.module.endsWith('.ts') ? execPos.module : `${execPos.module}.ts`)
          : undefined)
        : (execPos.module !== '(unknown)' && execPos.module !== '(suite)'
          ? (execPos.module.endsWith('.ts') ? execPos.module : `${execPos.module}.ts`)
          : undefined);
    if (inner && inner.kind !== 'runtime' && inner.kind !== 'crash') {
      return {
        ...inner,
        message,
        detail: {
          ...inner.detail,
          file: inner.detail.file ?? fileHint,
          caseName: execPos.caseName ?? undefined,
        },
      };
    }
    return {
      kind: 'crash',
      classKey: execPos.localLine != null
        ? `crash|load|${(fileHint ?? execPos.module).slice(0, 40)}`
        : execPos.caseName
          ? `crash|exec|${execPos.caseName.slice(0, 40)}`
          : `crash|exec|${execPos.module.slice(0, 40)}`,
      name, message,
      detail: { file: fileHint, caseName: execPos.caseName ?? undefined },
    };
  }

  // SET 35b — lint / WARN findings for runtime code synthesis.
  if (/^dynamic-eval:/.test(first)) {
    return {
      kind: 'runtime',
      classKey: `dynamic-eval|${first.replace(/^dynamic-eval:\s*/, '').slice(0, 40)}`,
      name, message,
      detail: {},
    };
  }

  let m = /^unresolved import (.+?) \(from (.+?)\)$/.exec(first);
  if (m) {
    return {
      kind: 'unresolved-import',
      classKey: `unresolved-import|${m[1]}`,
      name, message,
      detail: { spec: m[1], importer: m[2] },
    };
  }

  m = /^transpile ([^:]+):/.exec(first);
  if (m) {
    return { kind: 'transpile', classKey: `transpile|${m[1]}`, name, message, detail: { file: m[1] } };
  }

  m = /expect\(\.\.\.\)\.((?:not\.)?[A-Za-z]+) failed/.exec(first);
  if (m) {
    const matcher = m[1]!;
    const kind: FailureKind = matcher.includes('toThrow') ? 'throw-mismatch' : 'assertion';
    // SET 36f — capture expected vs received (harness: actual: X, expected: Y).
    const actualM = /actual:\s*(.*?)(?:,\s*expected:|$)/.exec(first);
    const expectedM = /,\s*expected:\s*(.*)$/.exec(first);
    const received = actualM ? actualM[1]!.trim() : undefined;
    const expected = expectedM ? expectedM[1]!.trim() : undefined;
    const detail: ClassifiedFailure['detail'] = { matcher, expected, received };
    if (kind === 'throw-mismatch') {
      const negated = matcher.startsWith('not.');
      detail.expectedThrow = !negated;
      // actual side holds thrown message when a throw occurred, or a sentinel.
      if (received && received !== 'undefined' && received !== '(resolved)') {
        detail.threw = true;
        detail.thrownMessage = received;
      } else {
        detail.threw = false;
      }
    }
    return { kind, classKey: `${kind}|${matcher}`, name, message, detail };
  }

  m = /^([A-Za-z_$][\w$]*) is not defined/.exec(first);
  if (m) {
    return { kind: 'undefined-symbol', classKey: `undefined-symbol|${m[1]}`, name, message, detail: { symbol: m[1] } };
  }

  // SET 61 — transpiled ESM-interop form: `(0 , slugify) is not a function`
  // is how a bare-import mismatch presents after CJS lowering. Without this
  // matcher the dominant bare-call failure class-drifts out of the catalog.
  m = /\(0\s*,\s*([A-Za-z_$][\w$]*)\)\s+is not a function/.exec(first);
  if (m) {
    return { kind: 'is-not-a-function', classKey: `is-not-a-function|${m[1]}`, name, message, detail: { symbol: m[1] } };
  }

  m = /([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*) is not a function/.exec(first);
  if (m) {
    const sym = m[1]!.split('.').pop()!;
    return { kind: 'is-not-a-function', classKey: `is-not-a-function|${sym}`, name, message, detail: { symbol: sym } };
  }

  // SET 98 — Class constructor / not-a-constructor → catalog-drift|missing-new|<Sym>.
  m = /Class constructor (\w+) cannot be invoked without 'new'/.exec(first);
  if (m) {
    return {
      kind: 'catalog-drift',
      classKey: `catalog-drift|missing-new|${m[1]}`,
      name, message,
      detail: { symbol: m[1] },
    };
  }
  m = /\(0\s*,\s*_\w+\.(\w+)\)\s+is not a constructor/.exec(first);
  if (m) {
    return {
      kind: 'catalog-drift',
      classKey: `catalog-drift|missing-new|${m[1]}`,
      name, message,
      detail: { symbol: m[1] },
    };
  }
  m = /([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*) is not a constructor/.exec(first);
  if (m) {
    const sym = m[1]!.split('.').pop()!;
    return {
      kind: 'catalog-drift',
      classKey: `catalog-drift|missing-new|${sym}`,
      name, message,
      detail: { symbol: sym },
    };
  }

  m = /Cannot read propert(?:y|ies) of (?:undefined|null) \(reading '([^']+)'\)/.exec(first);
  if (m) {
    return { kind: 'runtime', classKey: `runtime|nullread|${m[1]}`, name, message, detail: { symbol: m[1] } };
  }

  // SET 20a — duplicate top-level declaration across modules.
  m = /Identifier '([A-Za-z_$][\w$]*)' has already been declared/i.exec(first);
  if (m) {
    return {
      kind: 'duplicate-decl',
      classKey: `duplicate-decl|${m[1]}`,
      name, message,
      detail: { symbol: m[1] },
    };
  }

  // SET 6a — eval-time regex literal crash (sucrase accepts; runtime rejects).
  // SET 86 — the offending PATTERN is printed in the message; capture it so
  // the file can be attributed by searching the artifact for that literal.
  // Without this the class was unattributable, every repair refused, and the
  // feature PARKED — one bad regex in one module parked 7 of 11 features.
  if (/Invalid regular expression/i.test(first)) {
    const pat = /Invalid regular expression:\s*(\/[\s\S]*?\/[gimsuy]*)\s*:/.exec(first)
      ?? /Invalid regular expression:\s*(\/[^\s]+)/.exec(first);
    return {
      kind: 'regex-literal',
      classKey: `regex-literal|${first.slice(0, 40)}`,
      name, message,
      detail: pat ? { pattern: pat[1] } : {},
    };
  }

  // SET 18c — eval-time token/syntax crashes (attribution fills detail.file).
  if (/Invalid or unexpected token|Unexpected token|Unexpected end of input|Unexpected identifier/i.test(first)) {
    return {
      kind: 'crash',
      classKey: `crash|token|${first.slice(0, 40)}`,
      name, message,
      detail: {},
    };
  }

  if (name === '__suite__') {
    return { kind: 'crash', classKey: 'crash|suite', name, message, detail: {} };
  }
  // SET 39 — catalog may still classify what the rich path missed.
  const cat = classifyCataloged(message);
  if (cat.templateId !== 'catalog-drift') {
    return {
      kind: kindFromCatalogClassKey(cat.classKey),
      classKey: cat.classKey,
      name,
      message,
      detail: {},
    };
  }
  // SET 39 — unmatched by every template → catalog-drift (tripwire, not shrug).
  return { kind: 'catalog-drift', classKey: 'catalog-drift', name, message, detail: {} };
}

// ---------------------------------------------------------------------------
// 2. Mechanical fixes — zero tokens, provable outcomes only
// ---------------------------------------------------------------------------

export interface MechanicalFix {
  /** Repo-relative path of the file that was rewritten. */
  path: string;
  content: string;
  note: string;
  classKey: string;
}

/** SET 43a — max mechanical permutation candidates before refuse. */
export const MECHANICAL_PERM_CAP = 4;

/** SET 43a — fitness for substrate K-select (no LLM). */
export interface MechanicalCandidateFitness {
  /** Prefer parse-clean over syntax-error. */
  parseClean: boolean;
  /** Prefer fewer files touched. */
  filesTouched: number;
  /** Tertiary: stable path order (lexicographic). */
  pathOrderKey: string;
}

export type MechanicalSelectResult<T> =
  | { status: 'picked'; winner: T }
  | { status: 'refused'; reason: string };

/**
 * SET 43a — K-candidate doctrine for mechanical fixes: algorithm selects by
 * fitness; refuse on empty, >cap, or fitness tie. 0 tokens.
 */
export function selectMechanicalCandidate<T>(
  candidates: readonly T[],
  measure: (candidate: T) => MechanicalCandidateFitness,
): MechanicalSelectResult<T> {
  if (candidates.length === 0) {
    return { status: 'refused', reason: 'zero candidates' };
  }
  if (candidates.length > MECHANICAL_PERM_CAP) {
    return { status: 'refused', reason: `>4 candidates (${candidates.length})` };
  }
  const scored = candidates.map(c => ({ c, fit: measure(c) }));
  const cmp = (a: MechanicalCandidateFitness, b: MechanicalCandidateFitness): number => {
    if (a.parseClean !== b.parseClean) return a.parseClean ? -1 : 1;
    if (a.filesTouched !== b.filesTouched) return a.filesTouched - b.filesTouched;
    if (a.pathOrderKey < b.pathOrderKey) return -1;
    if (a.pathOrderKey > b.pathOrderKey) return 1;
    return 0;
  };
  scored.sort((a, b) => cmp(a.fit, b.fit));
  const best = scored[0]!;
  const tied = scored.filter(s => cmp(s.fit, best.fit) === 0);
  if (tied.length > 1) {
    return { status: 'refused', reason: `fitness tie among ${tied.length} candidates` };
  }
  return { status: 'picked', winner: best.c };
}

/** SET 43b(i) — true when a classify detail token is a JSON/JS literal. */
export function isSpecLiteral(token: string | undefined | null): boolean {
  if (token == null) return false;
  const t = token.trim();
  if (t === 'true' || t === 'false' || t === 'null') return true;
  if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(t)) return true;
  if (t.length >= 2) {
    const q = t[0]!;
    if ((q === '"' || q === "'") && t[t.length - 1] === q) {
      if (q === '"') {
        try { JSON.parse(t); return true; } catch { return false; }
      }
      return true;
    }
  }
  return false;
}

function escapeRegExp(s: string): string {
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

/** Search forms for a received literal (string quote alternates). */
function literalSearchForms(token: string): string[] {
  const t = token.trim();
  if (t.length >= 2 && (t[0] === '"' || t[0] === "'") && t[t.length - 1] === t[0]) {
    const inner = t.slice(1, -1);
    return [`"${inner}"`, `'${inner}'`];
  }
  return [t];
}

function adaptLiteralForm(expected: string, foundForm: string): string {
  const exp = expected.trim();
  if (
    (foundForm.startsWith("'") || foundForm.startsWith('"'))
    && (exp.startsWith('"') || exp.startsWith("'"))
    && exp.length >= 2
  ) {
    const q = foundForm[0]!;
    return `${q}${exp.slice(1, -1)}${q}`;
  }
  return exp;
}

interface LiteralHit {
  path: string;
  index: number;
  form: string;
  content: string;
}

function findLiteralHits(files: GeneratedFile[], token: string): LiteralHit[] {
  const t = token.trim();
  const hits: LiteralHit[] = [];
  const numericOrBool = /^(true|false|null|-?\d+(\.\d+)?([eE][+-]?\d+)?)$/.test(t);
  for (const f of files) {
    if (numericOrBool) {
      const re = new RegExp(`(?<![A-Za-z0-9_$])${escapeRegExp(t)}(?![A-Za-z0-9_$])`, 'g');
      let m: RegExpExecArray | null;
      while ((m = re.exec(f.content)) !== null) {
        hits.push({ path: f.path, index: m.index, form: m[0]!, content: f.content });
      }
    } else {
      const seenAt = new Set<number>();
      for (const form of literalSearchForms(t)) {
        let from = 0;
        while (from <= f.content.length) {
          const i = f.content.indexOf(form, from);
          if (i < 0) break;
          if (!seenAt.has(i)) {
            seenAt.add(i);
            hits.push({ path: f.path, index: i, form, content: f.content });
          }
          from = i + form.length;
        }
      }
    }
  }
  return hits;
}

/**
 * SET 43b(i) — spec-constant conformance: replace unique received literal with
 * expected in owned impl files. Returns null to fall through to catalog.
 * Integration may re-measure; unit accept = unique replace + parse-clean.
 */
export function attemptSpecConstantConformance(
  failure: ClassifiedFailure,
  artifact: GeneratedFile[],
): MechanicalFixAttempt | null {
  const matcher = failure.detail.matcher ?? '';
  if (failure.kind !== 'assertion') return null;
  if (!(matcher === 'toBe' || failure.classKey === 'assertion|toBe')) return null;
  const received = failure.detail.received;
  const expected = failure.detail.expected;
  if (!isSpecLiteral(received) || !isSpecLiteral(expected)) return null;

  const hits = findLiteralHits(artifact, received!);
  if (hits.length === 0) return null;
  if (hits.length > 1) {
    return {
      fix: null,
      status: 'refused',
      reason: 'ambiguous literal occurrence',
    };
  }
  const hit = hits[0]!;
  const replacement = adaptLiteralForm(expected!, hit.form);
  const next = hit.content.slice(0, hit.index) + replacement + hit.content.slice(hit.index + hit.form.length);
  if (next === hit.content) return null;
  if (suiteSyntaxError(next) != null) {
    return {
      fix: null,
      status: 'refused',
      reason: 'spec-constant replace not parse-clean',
    };
  }
  return {
    status: 'fixed',
    fix: {
      path: hit.path,
      content: next,
      note: `spec-constant: replaced received ${hit.form} → ${replacement} in ${hit.path} (0 tok)`,
      classKey: failure.classKey,
    },
  };
}

const norm = (s: string) => s.toLowerCase().replace(/[-_]/g, '');

/** Module key for a file path: 'src/foo.ts' -> 'src/foo'. */
const keyOf = (p: string) => p.replace(/\.(ts|js|mjs|cjs)$/, '');

/** SET 18d — harness may report importer with or without extension. */
const normalizeImporterKey = (importer: string) =>
  importer.replace(/\.(ts|js|mjs|cjs)$/, '');

function findImporterFile(
  everything: GeneratedFile[],
  importer: string,
): GeneratedFile | undefined {
  const key = normalizeImporterKey(importer);
  return everything.find(f => keyOf(f.path) === key)
    ?? everything.find(f => f.path === importer)
    ?? everything.find(f => keyOf(f.path).endsWith(`/${key.split('/').pop()}`));
}

function relativeSpec(importerKey: string, targetKey: string): string {
  const importerDir = normalizeImporterKey(importerKey).split('/').slice(0, -1);
  if (importerDir.join('/') === targetKey.split('/').slice(0, -1).join('/')) {
    return `./${targetKey.split('/').pop()}`;
  }
  const ups = importerDir.length;
  return `${'../'.repeat(ups)}${targetKey}`;
}

function replaceImportSpec(content: string, oldSpec: string, newSpec: string): string | null {
  for (const q of [`'`, `"`]) {
    const fromNeedle = `from ${q}${oldSpec}${q}`;
    if (content.includes(fromNeedle)) {
      return content.split(fromNeedle).join(`from ${q}${newSpec}${q}`);
    }
    // Side-effect import: `import './data_model'`
    const sideNeedle = `import ${q}${oldSpec}${q}`;
    if (content.includes(sideNeedle)) {
      return content.split(sideNeedle).join(`import ${q}${newSpec}${q}`);
    }
  }
  return null;
}

/**
 * SET 39b — `no-handler` abolished as a primary outcome. Non-mechanical
 * outcomes are `cataloged` or `catalog-drift`. Legacy `no-handler` remains in
 * the union for Integration migration; attemptMechanicalFix never emits it.
 */
export type MechanicalFixStatus = 'fixed' | 'refused' | 'cataloged' | 'catalog-drift' | 'no-handler';

export interface MechanicalFixAttempt {
  fix: MechanicalFix | null;
  status: MechanicalFixStatus;
  reason?: string;
  /** SET 39 — response stratum when status is cataloged. */
  stratum?: ErrorStratum | 'catalog-drift';
  /** SET 20a — multi-file mechanical rewrites (duplicate-decl). */
  fixes?: MechanicalFix[];
  /** SET 45b — named candidate sites when refuse is ambiguity (model should pick). */
  candidateSites?: string[];
}

/** SET 39b — human/metrics label; replaces bare no-handler. */
export function formatAttemptStatus(attempt: MechanicalFixAttempt): string {
  if (attempt.status === 'catalog-drift') return 'catalog-drift';
  if (attempt.status === 'cataloged' || attempt.status === 'no-handler') {
    const s = attempt.stratum
      ?? (attempt.reason?.match(/stratum:\s*([^)]+)/)?.[1]?.trim())
      ?? 'unknown';
    return `cataloged (stratum: ${s})`;
  }
  return attempt.status;
}

const MECHANICAL_KINDS = new Set<FailureKind>([
  'unresolved-import', 'undefined-symbol', 'regex-literal', 'duplicate-decl', 'transpile', 'crash',
]);

/**
 * SET 20a — top-level declaration names in a module (brace/string-aware).
 * Captures `export class|function|const|let|var|type|interface|enum Name`.
 */
export function scanTopLevelDeclarations(content: string): string[] {
  const names: string[] = [];
  const seen = new Set<string>();
  let depth = 0;
  let inStr: string | null = null;
  let i = 0;
  const push = (n: string) => {
    if (!seen.has(n)) { seen.add(n); names.push(n); }
  };
  while (i < content.length) {
    const c = content[i]!;
    if (inStr) {
      if (c === '\\') { i += 2; continue; }
      if (c === inStr) inStr = null;
      i++;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; i++; continue; }
    if (c === '/' && content[i + 1] === '/') {
      while (i < content.length && content[i] !== '\n') i++;
      continue;
    }
    if (c === '/' && content[i + 1] === '*') {
      i += 2;
      while (i < content.length && !(content[i] === '*' && content[i + 1] === '/')) i++;
      i += 2;
      continue;
    }
    if (c === '{') { depth++; i++; continue; }
    if (c === '}') { depth = Math.max(0, depth - 1); i++; continue; }
    if (depth !== 0) { i++; continue; }
    const slice = content.slice(i);
    const m = /^(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function\*?|class|const|let|var|type|interface|enum)\s+([A-Za-z_$][\w$]*)/.exec(slice);
    if (m) {
      push(m[1]!);
      i += m[0].length;
      continue;
    }
    i++;
  }
  return names;
}

/** Owner file path for a symbol: stub / exports that declare it (SET 20a). */
export function ownerFileForSymbol(spec: FeatureSpec, symbol: string): string | null {
  const declRe = new RegExp(
    `export\\s+(?:default\\s+)?(?:async\\s+)?(?:function\\*?|class|const|let|var|type|interface|enum)\\s+${symbol}\\b`,
  );
  for (const feature of spec.features) {
    for (const ex of feature.exports) {
      if (ex.symbol === symbol) return ex.file;
    }
  }
  for (const feature of spec.features) {
    for (const stub of feature.stubs) {
      if (declRe.test(stub.content)) return stub.path;
    }
  }
  return null;
}

/** Strip one top-level class/function/const/let/var declaration of `name`. */
export function stripTopLevelDecl(content: string, name: string): string | null {
  let depth = 0;
  let inStr: string | null = null;
  let i = 0;
  while (i < content.length) {
    const c = content[i]!;
    if (inStr) {
      if (c === '\\') { i += 2; continue; }
      if (c === inStr) inStr = null;
      i++;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; i++; continue; }
    if (c === '/' && content[i + 1] === '/') {
      while (i < content.length && content[i] !== '\n') i++;
      continue;
    }
    if (c === '/' && content[i + 1] === '*') {
      i += 2;
      while (i < content.length && !(content[i] === '*' && content[i + 1] === '/')) i++;
      i += 2;
      continue;
    }
    if (c === '{') { depth++; i++; continue; }
    if (c === '}') { depth = Math.max(0, depth - 1); i++; continue; }
    if (depth !== 0) { i++; continue; }
    const slice = content.slice(i);
    const m = /^(export\s+)?(?:default\s+)?(?:async\s+)?(function\*?|class|const|let|var)\s+([A-Za-z_$][\w$]*)/.exec(slice);
    if (m && m[3] === name) {
      const start = i;
      const kind = m[2]!;
      const wasExport = !!m[1];
      i += m[0].length;
      if (kind === 'class' || kind.startsWith('function')) {
        // Skip optional heritage / params to the body `{` … matching `}`.
        while (i < content.length && content[i] !== '{') {
          if (content[i] === '"' || content[i] === "'" || content[i] === '`') {
            const q = content[i]!;
            i++;
            while (i < content.length && content[i] !== q) {
              if (content[i] === '\\') i++;
              i++;
            }
            i++;
            continue;
          }
          i++;
        }
        if (content[i] === '{') {
          let d = 0;
          for (; i < content.length; i++) {
            const ch = content[i]!;
            if (ch === '"' || ch === "'" || ch === '`') {
              const q = ch;
              i++;
              while (i < content.length && content[i] !== q) {
                if (content[i] === '\\') i++;
                i++;
              }
              continue;
            }
            if (ch === '{') d++;
            else if (ch === '}') {
              d--;
              if (d === 0) { i++; break; }
            }
          }
        }
      } else {
        // const/let/var — consume through semicolon or newline at depth 0.
        let d = 0;
        while (i < content.length) {
          const ch = content[i]!;
          if (ch === '"' || ch === "'" || ch === '`') {
            const q = ch;
            i++;
            while (i < content.length && content[i] !== q) {
              if (content[i] === '\\') i++;
              i++;
            }
            i++;
            continue;
          }
          if (ch === '{') d++;
          else if (ch === '}') d = Math.max(0, d - 1);
          else if (d === 0 && (ch === ';' || ch === '\n')) { i++; break; }
          i++;
        }
      }
      while (i < content.length && (content[i] === '\n' || content[i] === ';')) i++;
      const next = content.slice(0, start) + content.slice(i);
      return wasExport ? next : next;
    }
    i++;
  }
  return null;
}

/** Ensure `import { name } from 'spec'` (and re-export when stripped was exported). */
function rewireDeclToImport(
  content: string,
  name: string,
  fromSpec: string,
  reExport: boolean,
): string {
  if (new RegExp(`import\\s*(?:type\\s*)?\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from\\s*['"]${fromSpec.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"]`).test(content)) {
    return reExport && !new RegExp(`export\\s*\\{[^}]*\\b${name}\\b`).test(content)
      ? `export { ${name} };\n${content}`
      : content;
  }
  if (reExport) {
    return `export { ${name} } from '${fromSpec}';\n${content}`;
  }
  return `import { ${name} } from '${fromSpec}';\n${content}`;
}

/**
 * SET 16a/39b — attempt a deterministic repair and report
 * fixed|refused|cataloged|catalog-drift (`no-handler` abolished).
 * Ambiguity / missing structure → refused (handler exists but cannot prove a fix).
 */
/**
 * SET 98 — insert `new` before bare `Sym(` call sites (skip already-`new`,
 * and `function`/`class` declarators).
 */
export function insertNewBeforeCalls(content: string, sym: string): string | null {
  if (!/^[A-Za-z_$][\w$]*$/.test(sym)) return null;
  const re = new RegExp(`\\b(?:new\\s+)?${escapeRegExp(sym)}\\s*\\(`, 'g');
  let changed = false;
  const next = content.replace(re, (match, offset: number) => {
    if (/^new\s/.test(match)) return match;
    const before = content.slice(Math.max(0, offset - 20), offset);
    if (/\b(?:async\s+)?function\s*$/.test(before)) return match;
    if (/\bclass\s*$/.test(before)) return match;
    if (/\b(?:get|set|static)\s*$/.test(before)) return match;
    changed = true;
    return `new ${match}`;
  });
  return changed ? next : null;
}

/**
 * SET 98 — rewrite `export const Sym = (…) => …` / `export const Sym = function`
 * (or `export async function Sym`) into a constructable `export function Sym`.
 */
export function rewriteConstArrowToConstructor(content: string, sym: string): string | null {
  if (!/^[A-Za-z_$][\w$]*$/.test(sym)) return null;

  const constFn = new RegExp(
    `export\\s+const\\s+${sym}\\s*=\\s*(?:async\\s+)?function\\s*\\(`,
  );
  if (constFn.test(content)) {
    const next = content.replace(constFn, `export function ${sym}(`);
    return next !== content ? next : null;
  }

  const asyncFn = new RegExp(`export\\s+async\\s+function\\s+${sym}\\b`);
  if (asyncFn.test(content)) {
    return content.replace(asyncFn, `export function ${sym}`);
  }

  const arrowRe = new RegExp(
    `export\\s+const\\s+${sym}\\s*=\\s*(?:async\\s*)?(\\([^)]*\\)|[A-Za-z_$][\\w$]*)\\s*=>\\s*`,
  );
  const m = arrowRe.exec(content);
  if (!m) return null;
  const paramsRaw = m[1]!;
  const params = paramsRaw.startsWith('(') ? paramsRaw : `(${paramsRaw})`;
  const headStart = m.index;
  const bodyStart = m.index + m[0].length;

  if (content[bodyStart] === '{') {
    let depth = 0;
    let i = bodyStart;
    let inStr: string | null = null;
    for (; i < content.length; i++) {
      const c = content[i]!;
      if (inStr) {
        if (c === '\\') { i++; continue; }
        if (c === inStr) inStr = null;
        continue;
      }
      if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
      if (c === '{') depth++;
      else if (c === '}') {
        depth--;
        if (depth === 0) { i++; break; }
      }
    }
    if (depth !== 0) return null;
    let end = i;
    if (content[end] === ';') end++;
    const body = content.slice(bodyStart, i);
    return content.slice(0, headStart) + `export function ${sym}${params} ${body}` + content.slice(end);
  }

  let depth = 0;
  let i = bodyStart;
  let inStr: string | null = null;
  for (; i < content.length; i++) {
    const c = content[i]!;
    if (inStr) {
      if (c === '\\') { i++; continue; }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
    if (c === '(' || c === '[' || c === '{') depth++;
    else if (c === ')' || c === ']' || c === '}') depth = Math.max(0, depth - 1);
    else if ((c === ';' || c === '\n') && depth === 0) break;
  }
  const expr = content.slice(bodyStart, i).trim();
  if (!expr) return null;
  let end = i;
  if (content[end] === ';') end++;
  return content.slice(0, headStart)
    + `export function ${sym}${params} { return ${expr}; }`
    + content.slice(end);
}

/**
 * SET 98 — catalog-drift|missing-new|<Sym>: insert `new` at test call sites when
 * the impl exports `class Sym`, else rewrite const/arrow impl to a constructor
 * when the suite already uses `new Sym`.
 */
export function attemptMissingNewFix(
  failure: ClassifiedFailure,
  artifact: GeneratedFile[],
  suite: GeneratedFile,
): MechanicalFixAttempt | null {
  if (!/^catalog-drift\|missing-new\|/.test(failure.classKey)) return null;
  const sym = failure.detail.symbol
    ?? failure.classKey.slice('catalog-drift|missing-new|'.length);
  if (!sym || !/^[A-Za-z_$][\w$]*$/.test(sym)) {
    return { fix: null, status: 'refused', reason: 'missing-new symbol unproven' };
  }
  const classKey = `catalog-drift|missing-new|${sym}`;
  const classDeclRe = new RegExp(
    `(?:^|\\n)[ \\t]*export\\s+(?:default\\s+)?class\\s+${sym}\\b`,
  );

  // Preference (a): test bare-calls `Sym(...)` + impl `export class Sym` → insert `new`.
  const classFiles = artifact.filter(f => classDeclRe.test(f.content));
  if (classFiles.length === 1) {
    const suiteNext = insertNewBeforeCalls(suite.content, sym);
    if (suiteNext && suiteNext !== suite.content && suiteSyntaxError(suiteNext) == null) {
      return {
        status: 'fixed',
        fix: {
          path: suite.path,
          content: suiteNext,
          note: `missing-new: inserted 'new' before ${sym}(...) in ${suite.path} (0 tok)`,
          classKey,
        },
      };
    }
  } else if (classFiles.length > 1) {
    return {
      fix: null,
      status: 'refused',
      reason: `ambiguous: class ${sym} declared in ${classFiles.length} files`,
    };
  }

  // Preference (b): suite uses `new Sym` + impl drawn as const/arrow → constructor fn.
  if (!new RegExp(`\\bnew\\s+${sym}\\s*\\(`).test(suite.content)) {
    return { fix: null, status: 'refused', reason: 'missing-new call shape unproven' };
  }
  const constArrowRe = new RegExp(
    `export\\s+const\\s+${sym}\\s*=`
    + `|export\\s+async\\s+function\\s+${sym}\\b`,
  );
  const donors = artifact.filter(f => constArrowRe.test(f.content));
  if (donors.length === 0) {
    return { fix: null, status: 'refused', reason: `no const/arrow donor for '${sym}'` };
  }
  if (donors.length > 1) {
    return {
      fix: null,
      status: 'refused',
      reason: `ambiguous: ${donors.length} const/arrow donors for '${sym}'`,
    };
  }
  const donor = donors[0]!;
  const rewritten = rewriteConstArrowToConstructor(donor.content, sym);
  if (!rewritten || rewritten === donor.content) {
    return { fix: null, status: 'refused', reason: 'const/arrow → constructor rewrite failed' };
  }
  if (suiteSyntaxError(rewritten) != null) {
    return { fix: null, status: 'refused', reason: 'constructor rewrite not parse-clean' };
  }
  return {
    status: 'fixed',
    fix: {
      path: donor.path,
      content: rewritten,
      note: `missing-new: rewrote ${sym} to constructor in ${donor.path} (0 tok)`,
      classKey,
    },
  };
}

/**
 * SET 61 — is-not-a-function|<m>: exact-name delegation shims.
 *
 * The dominant small-model failure: right member NAMES, wrong container —
 * the suite calls `store.create(...)` while the impl put `create` at module
 * level (or vice versa). Repair is deterministic when names match exactly:
 * append a delegation shim; never rewrite bodies, never guess names.
 * Ambiguity (multiple donors) and non-trivial constructors REFUSE — this
 * handler proves its preconditions or stands aside.
 */
export function attemptMemberConformance(
  failure: ClassifiedFailure,
  artifact: GeneratedFile[],
  suite: GeneratedFile,
): MechanicalFixAttempt | null {
  const m = failure.detail.symbol;
  if (!m || !/^[A-Za-z_$][\w$]*$/.test(m)) return null;
  const s = suite.content;

  // ── How does the suite expect to call `m`? ─────────────────────────────
  const recvCall = new RegExp(`\\b([A-Za-z_$][\\w$]*)\\s*\\.\\s*${m}\\s*\\(`).exec(s);
  let expect: { kind: 'instance'; klass: string } | { kind: 'static'; klass: string } | { kind: 'bare' } | null = null;
  if (recvCall) {
    const recv = recvCall[1]!;
    const ctor = new RegExp(
      `(?:const|let|var)\\s+${recv}\\s*=\\s*(?:await\\s+)?new\\s+([A-Za-z_$][\\w$]*)\\s*\\(`,
    ).exec(s);
    if (ctor) expect = { kind: 'instance', klass: ctor[1]! };
    else if (/^[A-Z]/.test(recv)) expect = { kind: 'static', klass: recv };
  } else if (new RegExp(`(?:^|[^.\\w$])${m}\\s*\\(`).test(s)) {
    expect = { kind: 'bare' };
  }
  if (!expect) return null; // call shape unproven → catalog as before

  // ── Donor scans (exact name only) ──────────────────────────────────────
  const moduleFnRe = new RegExp(
    `(?:^|\\n)[ \\t]*(?:export\\s+)?(?:async\\s+)?function\\s+${m}\\b`
    + `|(?:^|\\n)[ \\t]*(?:export\\s+)?const\\s+${m}\\s*=`,
  );
  const classDeclRe = (k: string) =>
    new RegExp(`(?:^|\\n)[ \\t]*export\\s+(?:default\\s+)?class\\s+${k}\\b`);
  const methodInBodyRe = new RegExp(`\\n[ \\t]+(?:public\\s+|private\\s+|protected\\s+)?(?:static\\s+)?(?:async\\s+)?${m}\\s*\\(`);
  const zeroArgCtor = (content: string, k: string): boolean => {
    const body = content.slice(content.search(classDeclRe(k)));
    const ctor = /constructor\s*\(([^)]*)\)/.exec(body);
    if (!ctor) return true; // implicit constructor
    const params = ctor[1]!.trim();
    return params === '' || /^\.\.\./.test(params) || /=/.test(params.split(',')[0] ?? '');
  };
  const shimNote = (what: string) => `member-conformance: ${what} (exact-name delegation, 0 tok)`;
  const done = (file: GeneratedFile, shim: string, what: string): MechanicalFixAttempt => ({
    status: 'fixed',
    fix: {
      path: file.path,
      content: `${file.content}\n// SET 61 — mechanical member conformance (0 tok)\n${shim}\n`,
      note: shimNote(what),
      classKey: failure.classKey,
    },
    fixes: undefined,
  });

  if (expect.kind === 'instance' || expect.kind === 'static') {
    const k = expect.klass;
    const classFiles = artifact.filter(f => classDeclRe(k).test(f.content));
    if (classFiles.length !== 1) {
      return { fix: null, status: 'refused', reason: `class ${k} declared in ${classFiles.length} files` };
    }
    const cf = classFiles[0]!;
    if (expect.kind === 'instance') {
      if (moduleFnRe.test(cf.content)) {
        // H1 — same-file module function → prototype delegation.
        return done(cf,
          `(${k}.prototype as unknown as Record<string, unknown>)["${m}"] = function (...args: unknown[]) { return (${m} as unknown as (...a: unknown[]) => unknown)(...args); };`,
          `prototype ${k}.${m} → module ${m} (same file)`);
      }
      const donors = artifact.filter(f => f.path !== cf.path && moduleFnRe.test(f.content)
        && new RegExp(`export\\s+(?:async\\s+)?(?:function|const)\\s+${m}\\b`).test(f.content));
      if (donors.length === 1) {
        // H2 — cross-file exported function → import + prototype delegation.
        const from = relativeSpec(keyOf(cf.path), keyOf(donors[0]!.path));
        return done(cf,
          `import { ${m} as __rha61_${m} } from '${from}';\n(${k}.prototype as unknown as Record<string, unknown>)["${m}"] = function (...args: unknown[]) { return (__rha61_${m} as unknown as (...a: unknown[]) => unknown)(...args); };`,
          `prototype ${k}.${m} → ${donors[0]!.path} export ${m}`);
      }
      if (donors.length > 1) {
        return { fix: null, status: 'refused', reason: `ambiguous: ${donors.length} exported '${m}' donors` };
      }
      if (methodInBodyRe.test(cf.content) === false) {
        return { fix: null, status: 'refused', reason: `no exact-name donor for '${m}'` };
      }
      return null;
    }
    // static expected
    if (moduleFnRe.test(cf.content)) {
      return done(cf,
        `(${k} as unknown as Record<string, unknown>)["${m}"] = function (...args: unknown[]) { return (${m} as unknown as (...a: unknown[]) => unknown)(...args); };`,
        `static ${k}.${m} → module ${m}`);
    }
    if (methodInBodyRe.test(cf.content) && zeroArgCtor(cf.content, k)) {
      return done(cf,
        `(${k} as unknown as Record<string, unknown>)["${m}"] = function (...args: unknown[]) { return ((new ${k}()) as unknown as Record<string, (...a: unknown[]) => unknown>)["${m}"](...args); };`,
        `static ${k}.${m} → instance method (zero-arg ctor)`);
    }
    return { fix: null, status: 'refused', reason: `no provable donor for static ${k}.${m}` };
  }

  // bare call expected: donor = exactly one class carrying method m, zero-arg ctor.
  const classCarriers = artifact.filter(f =>
    /(?:^|\n)[ \t]*export\s+(?:default\s+)?class\s+[A-Za-z_$][\w$]*/.test(f.content)
    && methodInBodyRe.test(f.content));
  if (classCarriers.length !== 1) {
    return classCarriers.length === 0
      ? { fix: null, status: 'refused', reason: `no exact-name donor for bare '${m}'` }
      : { fix: null, status: 'refused', reason: `ambiguous: ${classCarriers.length} class donors for '${m}'` };
  }
  const carrier = classCarriers[0]!;
  const kName = /(?:^|\n)[ \t]*export\s+(?:default\s+)?class\s+([A-Za-z_$][\w$]*)/.exec(carrier.content)?.[1];
  if (!kName || !zeroArgCtor(carrier.content, kName)) {
    return { fix: null, status: 'refused', reason: `donor class constructor not provably zero-arg` };
  }
  return done(carrier,
    `export const ${m} = (...args: unknown[]) => (((new ${kName}()) as unknown as Record<string, (...a: unknown[]) => unknown>)["${m}"])(...args);`,
    `module ${m} → ${kName}.${m} (zero-arg ctor)`);
}

export function attemptMechanicalFix(
  failure: ClassifiedFailure,
  artifact: GeneratedFile[],
  suite: GeneratedFile,
  spec: FeatureSpec,
): MechanicalFixAttempt {
  // SET 37d — empty-draw: clade/template path; never a model attempt.
  if (failure.kind === 'empty-draw' || failure.classKey === 'empty-draw') {
    return {
      fix: null,
      status: 'cataloged',
      stratum: 'mechanical-first',
      reason: 'cataloged (stratum: mechanical-first) — empty-draw routes to clade template, not model',
    };
  }
  // SET 98 — missing-new catalog-drift family: mechanical before tripwire.
  if (
    failure.kind === 'catalog-drift'
    && /^catalog-drift\|missing-new\|/.test(failure.classKey)
  ) {
    const miss = attemptMissingNewFix(failure, artifact, suite);
    if (miss) return miss;
    return {
      fix: null,
      status: 'refused',
      reason: 'missing-new preconditions unmet',
    };
  }
  // SET 37c — mock-API: fall into mechanical handlers below; never catalog→model.
  if (refusesModelDraw(failure.classKey)) {
    // continue into jest/fn/spyOn handlers
  } else if (failure.kind === 'assertion') {
    // SET 43b(i) — literal toBe conformance before catalog→model.
    const lit = attemptSpecConstantConformance(failure, artifact);
    if (lit) return lit;
    const cat = classifyCataloged(failure.message);
    if (cat.templateId === 'catalog-drift' || failure.templateId === 'catalog-drift') {
      return {
        fix: null,
        status: 'catalog-drift',
        stratum: 'catalog-drift',
        reason: formatCatalogDriftTripwire(failure.message),
      };
    }
    return {
      fix: null,
      status: 'cataloged',
      stratum: cat.stratum === 'catalog-drift' ? 'semantic-procedure' : cat.stratum,
      reason: `cataloged (stratum: ${cat.stratum})`,
    };
  } else if (!MECHANICAL_KINDS.has(failure.kind) && failure.kind !== 'is-not-a-function') {
    const cat = classifyCataloged(failure.message);
    if (cat.templateId === 'catalog-drift' || failure.kind === 'catalog-drift'
      || failure.templateId === 'catalog-drift') {
      return {
        fix: null,
        status: 'catalog-drift',
        stratum: 'catalog-drift',
        reason: formatCatalogDriftTripwire(failure.message),
      };
    }
    return {
      fix: null,
      status: 'cataloged',
      stratum: cat.stratum === 'catalog-drift' ? 'semantic-procedure' : cat.stratum,
      reason: `cataloged (stratum: ${cat.stratum})`,
    };
  } else if (!MECHANICAL_KINDS.has(failure.kind) && failure.kind === 'is-not-a-function'
    && !refusesModelDraw(failure.classKey)) {
    // SET 61 — exact-name delegation handlers try FIRST; catalog only when
    // the call shape or donor is unprovable.
    const shim = attemptMemberConformance(failure, artifact, suite);
    if (shim) return shim;
    const cat = classifyCataloged(failure.message);
    return {
      fix: null,
      status: 'cataloged',
      stratum: cat.stratum === 'catalog-drift' ? 'playbook' : cat.stratum,
      reason: `cataloged (stratum: ${cat.stratum === 'catalog-drift' ? 'playbook' : cat.stratum})`,
    };
  }
  const everything = [...artifact, suite];

  // SET 20a — duplicate-decl|<Identifier>: strip non-owner redeclarations, rewire to import.
  if (failure.kind === 'duplicate-decl' && failure.detail.symbol) {
    const sym = failure.detail.symbol;
    const declModules = artifact.filter(f => scanTopLevelDeclarations(f.content).includes(sym));
    if (declModules.length < 2) {
      return { fix: null, status: 'refused', reason: 'identifier not declared in ≥2 modules' };
    }
    const ownerPath = ownerFileForSymbol(spec, sym);
    if (!ownerPath) {
      return { fix: null, status: 'refused', reason: 'no stub owner for identifier' };
    }
    const ownerKey = keyOf(ownerPath);
    const owner = artifact.find(f => keyOf(f.path) === ownerKey || f.path === ownerPath);
    if (!owner) {
      return { fix: null, status: 'refused', reason: 'owner module not in artifact' };
    }
    const fixes: MechanicalFix[] = [];
    for (const f of declModules) {
      if (keyOf(f.path) === ownerKey) continue;
      const exportRe = new RegExp(
        `export\\s+(?:default\\s+)?(?:async\\s+)?(?:function\\*?|class|const|let|var)\\s+${sym}\\b`,
      );
      const wasExport = exportRe.test(f.content);
      const stripped = stripTopLevelDecl(f.content, sym);
      if (stripped == null || stripped === f.content) {
        return { fix: null, status: 'refused', reason: 'could not strip non-owner redeclaration' };
      }
      const fromSpec = relativeSpec(keyOf(f.path), ownerKey);
      const next = rewireDeclToImport(stripped, sym, fromSpec, wasExport);
      fixes.push({
        path: f.path,
        content: next,
        note: `stripped duplicate '${sym}' in ${f.path}; rewired to import from '${fromSpec}'`,
        classKey: failure.classKey,
      });
    }
    if (!fixes.length) {
      return { fix: null, status: 'refused', reason: 'no non-owner redeclaration to strip' };
    }
    return { status: 'fixed', fix: fixes[0]!, fixes };
  }

  if (failure.kind === 'unresolved-import' && failure.detail.spec && failure.detail.importer) {
    const wanted = failure.detail.spec.replace(/^(\.\.?\/)+/, '').replace(/\.(ts|js)$/, '');
    const wantedBase = norm(wanted.split('/').pop() ?? '');
    const moduleKeys = [...new Set(
      artifact.map(f => keyOf(f.path)).filter(k => norm(k.split('/').pop() ?? '') === wantedBase),
    )].sort((a, b) => a.localeCompare(b));
    if (moduleKeys.length === 0) {
      return { fix: null, status: 'refused', reason: 'no matching export module' };
    }
    // SET 18d — suite importers often carry a trailing .ts; also match by basename.
    const importerFile = findImporterFile(everything, failure.detail.importer)
      ?? (/\.test\b|tests?\//i.test(failure.detail.importer)
        ? everything.find(f => /\.test\.(ts|js)$/.test(f.path) || f.path === suite.path)
        : undefined);
    if (!importerFile) {
      return { fix: null, status: 'refused', reason: 'importer file not in artifact' };
    }
    const importerKey = keyOf(importerFile.path);
    // SET 43a — K-select among ambiguous export modules (cap 4); scratch apply.
    type ModCand = { fix: MechanicalFix };
    const modCands: ModCand[] = [];
    for (const mod of moduleKeys) {
      const fixedSpec = relativeSpec(importerKey, mod);
      const next = replaceImportSpec(importerFile.content, failure.detail.spec, fixedSpec);
      if (next == null || next === importerFile.content) continue;
      modCands.push({
        fix: {
          path: importerFile.path,
          content: next,
          note: `rewired import '${failure.detail.spec}' → '${fixedSpec}' in ${importerFile.path}`,
          classKey: failure.classKey,
        },
      });
    }
    if (modCands.length === 0) {
      return { fix: null, status: 'refused', reason: 'import spec rewrite failed' };
    }
    if (modCands.length > MECHANICAL_PERM_CAP) {
      return {
        fix: null,
        status: 'refused',
        reason: `ambiguous export module (>4 candidates: ${modCands.length})`,
        candidateSites: moduleKeys,
      };
    }
    const picked = selectMechanicalCandidate(modCands, c => ({
      parseClean: suiteSyntaxError(c.fix.content) == null,
      filesTouched: 1,
      // Same importer path for all module rewires → parse-clean peers fitness-tie.
      pathOrderKey: keyOf(c.fix.path),
    }));
    if (picked.status === 'refused') {
      return {
        fix: null,
        status: 'refused',
        reason: picked.reason.includes('tie')
          ? `ambiguous export module (${picked.reason})`
          : picked.reason,
        candidateSites: moduleKeys,
      };
    }
    return { status: 'fixed', fix: picked.winner.fix };
  }

  if (failure.kind === 'undefined-symbol' && failure.detail.symbol) {
    const sym = failure.detail.symbol;
    // SET 35d/37c — jest under vitest: mechanical suite rewrite (0 tokens, ever).
    if (sym === 'jest') {
      const suiteNext = rewriteJestToVi(suite.content);
      if (suiteNext && suiteSyntaxError(suiteNext) == null && !dynamicEvalLint(suiteNext)) {
        // Accept only when the rewrite is jest→vi shaped (no other diffs beyond jest/vi).
        const stripped = (s: string) => s.replace(/\bjest\b/g, 'vi');
        if (stripped(suite.content) === suiteNext || suiteNext.includes('vi.')) {
          return {
            status: 'fixed',
            fix: {
              path: suite.path,
              content: suiteNext,
              note: `rewrote jest → vi in ${suite.path} (0 tok)`,
              classKey: failure.classKey,
            },
          };
        }
      }
      // Harness already aliases jest=vi at runtime; refuse only if rewrite unsafe.
      return { fix: null, status: 'refused', reason: 'jest rewrite unsafe or unnecessary (harness aliases jest)' };
    }
    // SET 37c — bare `fn` mock binding: harness provides vi.fn; never model.
    if (sym === 'fn') {
      return {
        fix: null,
        status: 'refused',
        reason: 'mock-API mechanical only — harness binds fn→vi.fn (Stream B); never model',
      };
    }
    // SET 47c — resolve exporter via ownership FIRST (unique by construction),
    // then unique content exporter, then 43a K-select for genuine ties.
    const ownerPath = ownerFileForSymbol(spec, sym);
    let exporters: GeneratedFile[] = [];
    if (ownerPath) {
      const owner = everything.find(f =>
        keyOf(f.path) === keyOf(ownerPath) || f.path === ownerPath)
        ?? artifact.find(f => keyOf(f.path) === keyOf(ownerPath) || f.path === ownerPath);
      if (owner) exporters = [owner];
    }
    if (exporters.length === 0) {
      exporters = artifact
        .filter(f =>
          new RegExp(`export (?:async )?(?:function|class|const|interface|type|enum)\\s+${sym}\\b`).test(f.content))
        .sort((a, b) => keyOf(a.path).localeCompare(keyOf(b.path)));
    }
    if (exporters.length === 0) {
      return { fix: null, status: 'refused', reason: 'symbol not exported uniquely' };
    }
    const exporterPaths = new Set(exporters.map(e => e.path));
    // SET 47b/47c — import sites include the suite (scaffold-owned free idents).
    const users = everything
      .filter(f =>
        !exporterPaths.has(f.path)
        && new RegExp(`\\b${sym}\\b`).test(f.content)
        && !new RegExp(`import\\s*(?:type\\s*)?\\{[^}]*\\b${sym}\\b[^}]*\\}`).test(f.content))
      .sort((a, b) => keyOf(a.path).localeCompare(keyOf(b.path)));
    if (users.length === 0) {
      return { fix: null, status: 'refused', reason: 'no unique import site' };
    }
    // SET 43a — all exporter × import-site pairs; K-select by parse fitness (cap 4).
    type ImpCand = { fix: MechanicalFix };
    const impCands: ImpCand[] = [];
    for (const ex of exporters) {
      const exporterKey = keyOf(ex.path);
      for (const user of users) {
        const rel = relativeSpec(keyOf(user.path), exporterKey);
        const content = `import { ${sym} } from '${rel}';\n${user.content}`;
        impCands.push({
          fix: {
            path: user.path,
            content,
            note: `added missing import { ${sym} } from '${rel}' to ${user.path}`,
            classKey: failure.classKey,
          },
        });
      }
    }
    // SET 47c — 43a-eligible refusals MUST surface candidateSites (tripwire).
    if (impCands.length > MECHANICAL_PERM_CAP) {
      const why = exporters.length > 1 && users.length > 1
        ? 'ambiguous exporter×import site'
        : exporters.length > 1
          ? 'ambiguous exporter'
          : 'ambiguous import site';
      return {
        fix: null,
        status: 'refused',
        reason: `${why} (>4 candidates: ${impCands.length})`,
        candidateSites: impCands.map(c => c.fix.path),
      };
    }
    const picked = selectMechanicalCandidate(impCands, c => ({
      parseClean: suiteSyntaxError(c.fix.content) == null,
      filesTouched: 1,
      pathOrderKey: keyOf(c.fix.path),
    }));
    if (picked.status === 'refused') {
      const prefix = exporters.length > 1
        ? 'ambiguous exporter'
        : users.length > 1
          ? 'ambiguous import site'
          : 'mechanical permutation';
      return {
        fix: null,
        status: 'refused',
        reason: picked.reason.includes('tie')
          ? `${prefix} (${picked.reason})`
          : picked.reason,
        candidateSites: impCands.map(c => c.fix.path),
      };
    }
    return { status: 'fixed', fix: picked.winner.fix };
  }

  // SET 37c — spyOn mock binding: harness provides vi.spyOn; never model.
  if (failure.kind === 'is-not-a-function' && failure.detail.symbol === 'spyOn') {
    return {
      fix: null,
      status: 'refused',
      reason: 'mock-API mechanical only — harness binds spyOn→vi.spyOn (Stream B); never model',
    };
  }

  // SET 6a — unterminated regex literal: odd unescaped `/` on a line → append `/`.
  // SET 25b — delimiter oracle covers char-class close and other regex demarcators
  // when healUnterminatedRegexLiteral alone can't close the literal (e.g. `/[a-z+;`);
  // also verify the quick heal actually produces parse-clean content before accepting.
  if (failure.kind === 'regex-literal') {
    // SET 86 — attribute by LITERAL SEARCH when the runtime gave no position:
    // the pattern text is in the message, and it appears verbatim in exactly
    // one artifact file. Deterministic, 0 tok, and it is the difference
    // between a repairable finding and a parked feature.
    let target = everything.find(f => f.path === failure.detail.file) ?? null;
    if (!target && failure.detail.pattern) {
      const needle = failure.detail.pattern;
      const hits = everything.filter(f => f.content.includes(needle));
      if (hits.length === 1) {
        target = hits[0]!;
        failure.detail.file = target.path;
      } else if (hits.length > 1) {
        // Prefer an implementation file over a test file when ambiguous.
        const impl = hits.find(f => !/\.test\.|\/tests?\//.test(f.path));
        if (impl) { target = impl; failure.detail.file = impl.path; }
      }
    }
    if (!target) {
      return { fix: null, status: 'refused', reason: 'regex offender file not attributed' };
    }
    const quick = healUnterminatedRegexLiteral(target.content);
    if (quick && suiteSyntaxError(quick) == null) {
      return {
        status: 'fixed',
        fix: {
          path: target.path,
          content: quick,
          note: `appended missing '/' to unterminated regex literal in ${target.path}`,
          classKey: failure.classKey,
        },
      };
    }
    // 25b — quick heal insufficient (char-class unclosed, etc.) → delimiter oracle.
    const delimHeal = applyDelimiterOraclePolish(target.content, failure.message);
    if (delimHeal) {
      return {
        status: 'fixed',
        fix: {
          path: target.path,
          content: delimHeal.content,
          note: `delimiter oracle heal (${delimHeal.classKey}) for regex literal in ${target.path}`,
          classKey: failure.classKey,
        },
      };
    }
    return { fix: null, status: 'refused', reason: 'no unterminated regex literal on a line' };
  }

  // SET 18a — parser-guided repair for IMPLEMENTATION (and attributed crash) files.
  if ((failure.kind === 'transpile' || failure.kind === 'crash') && failure.detail.file) {
    const target = everything.find(f => f.path === failure.detail.file) ?? null;
    if (!target) {
      return { fix: null, status: 'refused', reason: 'offender file not in artifact' };
    }
    const errPart = failure.kind === 'transpile'
      ? failure.message.replace(/^transpile [^:]+:\s*/, '')
      : failure.message;
    const isSuite = target.path === suite.path || /\.test\.(ts|js)$/.test(target.path);
    // SET 25b — try the delimiter oracle FIRST on suite-side crashes; a
    // byte-minimal spot-edit that keeps every assertion is strictly safer
    // than full IR-side regen. If it heals, license the mechanical fix.
    if (isSuite) {
      const delimHeal = applyDelimiterOraclePolish(target.content, errPart);
      if (delimHeal) {
        return {
          status: 'fixed',
          fix: {
            path: target.path,
            content: delimHeal.content,
            note: `delimiter oracle spot-heal (${delimHeal.classKey}) on frozen suite ${target.path}`,
            classKey: failure.classKey,
          },
        };
      }
      return { fix: null, status: 'refused', reason: 'suite-side crash — IR surgery' };
    }
    const guided = parserGuidedImplRepair(target.content, errPart);
    if (guided) {
      return {
        status: 'fixed',
        fix: {
          path: target.path,
          content: guided.content,
          note: `parser-guided impl repair (${guided.token}) in ${target.path}`,
          classKey: failure.classKey,
        },
      };
    }
    // 25b — delimiter oracle last-chance sweep on impl files too.
    const delimHeal = applyDelimiterOraclePolish(target.content, errPart);
    if (delimHeal) {
      return {
        status: 'fixed',
        fix: {
          path: target.path,
          content: delimHeal.content,
          note: `delimiter oracle heal (${delimHeal.classKey}) in ${target.path}`,
          classKey: failure.classKey,
        },
      };
    }
    return { fix: null, status: 'refused', reason: 'parser-guided impl repair exhausted' };
  }

  return { fix: null, status: 'refused', reason: 'handler preconditions unmet' };
}

/**
 * Attempt a deterministic repair for one classified failure against the
 * current artifact (implementation files + the feature's frozen suite).
 * Returns null unless the outcome is provable — ambiguity goes to the LLM.
 */
export function mechanicalFix(
  failure: ClassifiedFailure,
  artifact: GeneratedFile[],
  suite: GeneratedFile,
  spec: FeatureSpec,
): MechanicalFix | null {
  return attemptMechanicalFix(failure, artifact, suite, spec).fix;
}

/** Count unescaped `/` on a line (string-aware). Odd → likely unterminated regex. */
function oddUnescapedSlashes(line: string): boolean {
  let count = 0;
  let inStr: string | null = null;
  for (let i = 0; i < line.length; i++) {
    const c = line[i]!;
    if (inStr) {
      if (c === '\\') { i++; continue; }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
    if (c === '/' && line[i + 1] === '/') break; // line comment
    if (c === '/' && line[i + 1] === '*') break; // block comment start
    if (c === '\\') { i++; continue; }
    if (c === '/') count++;
  }
  return count > 0 && count % 2 === 1;
}

/**
 * SET 6a mechanical candidate: on a line with an odd count of unescaped `/`,
 * append `/` (closing the literal). Caller must verify via module probe.
 */
export function healUnterminatedRegexLiteral(content: string): string | null {
  const lines = content.split('\n');
  let changed = false;
  const next = lines.map(line => {
    if (!oddUnescapedSlashes(line)) return line;
    const trimmed = line.replace(/\s+$/, '');
    if (trimmed.endsWith('/')) return line;
    changed = true;
    return `${trimmed}/`;
  });
  return changed ? next.join('\n') : null;
}

// ---------------------------------------------------------------------------
// 2b. IR case-body syntax repair (SET 1) — zero tokens, parse-oracle only
// ---------------------------------------------------------------------------

/** Accept a repaired case body only when it parses, asserts, and passes 35b lint. */
export function acceptCaseBody(candidate: string): boolean {
  if (!candidate.includes('expect(')) return false;
  if (dynamicEvalLint(candidate)) return false;
  return caseSyntaxErrors([{ name: '_', body: candidate }]).length === 0;
}

function asciiQuotes(body: string): string {
  return body
    .replace(/[\u2018\u2019\u201A\u201B]/g, "'")
    .replace(/[\u201C\u201D\u201E\u201F]/g, '"');
}

/** Strip trailing commas before closers — `foo(1, 2,)` → `foo(1, 2)`. */
function stripTrailingCommas(body: string): string {
  return body.replace(/,(\s*[)\]}])/g, '$1');
}

/**
 * Append missing closers for unclosed `([{`. String-aware; does not delete
 * surplus closers (that belongs to the parser-guided rung).
 */
export function balanceClosers(body: string): string {
  const stack: string[] = [];
  const openOf: Record<string, string> = { ')': '(', ']': '[', '}': '{' };
  const closeOf: Record<string, string> = { '(': ')', '[': ']', '{': '}' };
  let inStr: string | null = null;
  for (let i = 0; i < body.length; i++) {
    const c = body[i]!;
    if (inStr) {
      if (c === '\\') { i++; continue; }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
    if (c === '(' || c === '[' || c === '{') stack.push(c);
    else if (c in openOf) {
      if (stack.length && stack[stack.length - 1] === openOf[c]) stack.pop();
    }
  }
  let out = body;
  while (stack.length) out += closeOf[stack.pop()!]!;
  return out;
}

/**
 * SET 1a — mechanical case repair. Returns a parse-clean body or null.
 * Lesson token (for distill) is returned via `token` when healing succeeds.
 */
export function mechanicalCaseRepair(body: string): { body: string; token: string } | null {
  // Pipeline (order matters): quotes → balance closers → strip trailing commas.
  // Always ASCII-fy first (sucrase may oddly accept some typographic quotes).
  let cur = asciiQuotes(body);
  let token = cur !== body ? 'quotes' : 'noop';
  if (acceptCaseBody(cur) && cur !== body) return { body: cur, token: 'quotes' };
  if (acceptCaseBody(cur)) return { body: cur, token: 'already-clean' };
  // Trailing commas are valid JS, so balance alone can "succeed" too early —
  // always strip after balancing before accepting.
  const balanced = balanceClosers(cur);
  if (balanced !== cur) { cur = balanced; token = 'closers'; }
  const stripped = stripTrailingCommas(cur);
  if (stripped !== cur) { cur = stripped; token = 'trailing-comma' as const; }
  // Re-strip after balance in case balance introduced `,)`.
  cur = stripTrailingCommas(balanceClosers(cur));
  if (cur !== body && acceptCaseBody(cur)) return { body: cur, token: token === 'noop' ? 'combined' : token };
  return null;
}

interface ParsePos {
  /** 0-based offset into the raw case body. */
  offset: number;
  expected?: string;
}

/** Map a sucrase `(line:col)` (1-indexed, wrapper-aware) onto the body. */
export function parseErrorPosition(error: string, body: string): ParsePos | null {
  const m = /(?:expected ["']([^"']+)["'][^\(]*)?\((\d+):(\d+)\)/i.exec(error);
  if (!m) return null;
  const expected = m[1];
  const line = Number(m[2]);
  const col = Number(m[3]);
  // caseSyntaxErrors wraps: `async function __t() {\n` + body + `\n}`
  // so body content starts at line 2, column 0 of the wrapper.
  const bodyLines = body.replace(/\r\n/g, '\n').split('\n');
  const bodyLineIdx = line - 2;
  if (bodyLineIdx < 0 || bodyLineIdx >= bodyLines.length) {
    // Fallback: treat col as absolute offset into body when line is 1.
    if (line === 1) return { offset: Math.min(Math.max(col, 0), body.length), expected };
    return null;
  }
  let offset = 0;
  for (let i = 0; i < bodyLineIdx; i++) offset += bodyLines[i]!.length + 1;
  offset += Math.min(col, bodyLines[bodyLineIdx]!.length);
  return { offset: Math.min(offset, body.length), expected };
}

function applyEdit(body: string, offset: number, kind: 'insert' | 'delete', token?: string): string | null {
  if (offset < 0 || offset > body.length) return null;
  if (kind === 'insert') {
    if (!token) return null;
    return body.slice(0, offset) + token + body.slice(offset);
  }
  if (offset >= body.length) return null;
  // Delete one code point; if it's the start of an identifier/operator run, delete one char.
  return body.slice(0, offset) + body.slice(offset + 1);
}

/**
 * Candidate offsets to insert `)` before `.prop` while a call is still open —
 * the classic `expect(foo(1, 2).toBe(3)` missing-paren shape (error columns
 * often point past the real hole).
 */
function missingCloserBeforeDotOffsets(body: string): number[] {
  const out: number[] = [];
  let depth = 0;
  let inStr: string | null = null;
  for (let i = 0; i < body.length; i++) {
    const c = body[i]!;
    if (inStr) {
      if (c === '\\') { i++; continue; }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
    if (c === '(' || c === '[' || c === '{') depth++;
    else if (c === ')' || c === ']' || c === '}') depth = Math.max(0, depth - 1);
    else if (c === '.' && depth > 0) out.push(i);
  }
  return out;
}

/** If body ends inside an unclosed quote/template, append the matching closer. */
function closeOpenStringLiteral(body: string): { next: string; token: string } | null {
  let inStr: string | null = null;
  for (let i = 0; i < body.length; i++) {
    const c = body[i]!;
    if (inStr) {
      if (c === '\\') { i++; continue; }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') inStr = c;
  }
  if (!inStr) return null;
  const label = inStr === '`' ? 'template' : 'string';
  return { next: body + inStr, token: `close-${label}` };
}

/** Statement-level brace balance (string-aware) — append missing `}`. */
function closeOpenBraces(body: string): { next: string; token: string } | null {
  let depth = 0;
  let inStr: string | null = null;
  for (let i = 0; i < body.length; i++) {
    const c = body[i]!;
    if (inStr) {
      if (c === '\\') { i++; continue; }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
    if (c === '{') depth++;
    else if (c === '}') depth = Math.max(0, depth - 1);
  }
  if (depth <= 0) return null;
  return { next: body + '}'.repeat(depth), token: `close-braces:${depth}` };
}

/** Delete a stray quote/backtick near the error column (one char). */
function strayQuoteDeletes(body: string, offset: number | null): { next: string; token: string }[] {
  const out: { next: string; token: string }[] = [];
  const tryAt = (i: number, label: string) => {
    if (i < 0 || i >= body.length) return;
    const c = body[i]!;
    if (c !== '"' && c !== "'" && c !== '`') return;
    out.push({ next: body.slice(0, i) + body.slice(i + 1), token: `delete-stray:${label}` });
  };
  if (offset != null) {
    tryAt(offset, 'at');
    tryAt(offset - 1, 'before');
    tryAt(offset + 1, 'after');
  }
  // Also try deleting the last quote/backtick in the body.
  for (let i = body.length - 1; i >= 0; i--) {
    const c = body[i]!;
    if (c === '"' || c === "'" || c === '`') {
      out.push({ next: body.slice(0, i) + body.slice(i + 1), token: 'delete-stray:last' });
      break;
    }
  }
  return out;
}

/**
 * SET 1b / 16b — parser-guided token repair. The parse error IS the oracle.
 * Up to `maxDepth` error-driven edits; accept only parse-clean + expect(.
 * Extends comma/paren family with unterminated strings/templates, braces,
 * and stray quote/backtick deletes.
 */
export function parserGuidedCaseRepair(
  body: string,
  error: string,
  maxDepth = 3,
): { body: string; token: string } | null {
  if (acceptCaseBody(body)) return { body, token: 'already-clean' };
  type Frame = { body: string; depth: number; lastErr: string };
  const queue: Frame[] = [{ body, depth: 0, lastErr: error }];
  const seen = new Set<string>([body]);
  while (queue.length) {
    const cur = queue.shift()!;
    if (cur.depth >= maxDepth) continue;
    const pos = parseErrorPosition(cur.lastErr, cur.body);
    const candidates: { next: string; token: string }[] = [];
    if (pos) {
      if (pos.expected) {
        const ins = applyEdit(cur.body, pos.offset, 'insert', pos.expected);
        if (ins) candidates.push({ next: ins, token: `insert:${pos.expected}` });
      }
      for (const closer of [')', ']', '}', '"', "'", '`']) {
        const ins = applyEdit(cur.body, pos.offset, 'insert', closer);
        if (ins) candidates.push({ next: ins, token: `insert:${closer}` });
      }
      const del = applyEdit(cur.body, pos.offset, 'delete');
      if (del) candidates.push({ next: del, token: 'delete' });
      candidates.push(...strayQuoteDeletes(cur.body, pos.offset));
    }
    // Error columns are often past the hole — try `)` before each open-depth `.`
    for (const off of missingCloserBeforeDotOffsets(cur.body)) {
      const ins = applyEdit(cur.body, off, 'insert', ')');
      if (ins) candidates.push({ next: ins, token: 'insert:)-before-dot' });
    }
    const closedStr = closeOpenStringLiteral(cur.body);
    if (closedStr) candidates.push(closedStr);
    const closedBrace = closeOpenBraces(cur.body);
    if (closedBrace) candidates.push(closedBrace);
    candidates.push(...strayQuoteDeletes(cur.body, null));
    const combo = balanceClosers(stripTrailingCommas(cur.body));
    if (combo !== cur.body) candidates.push({ next: combo, token: 'balance' });

    for (const c of candidates) {
      if (seen.has(c.next)) continue;
      seen.add(c.next);
      if (acceptCaseBody(c.next)) return { body: c.next, token: c.token };
      const err = caseSyntaxErrors([{ name: '_', body: c.next }])[0]?.error;
      if (err) queue.push({ body: c.next, depth: cur.depth + 1, lastErr: err });
    }
  }
  return null;
}

/**
 * SET 1a+1b pipeline for one case body. Prefers mechanical, then parser-guided.
 */
export function healCaseBody(
  body: string,
  error?: string,
): { body: string; token: string; via: 'mechanical' | 'parser-guided' } | null {
  const mech = mechanicalCaseRepair(body);
  if (mech && mech.token !== 'already-clean') {
    return { body: mech.body, token: mech.token, via: 'mechanical' };
  }
  if (mech?.token === 'already-clean') {
    return { body: mech.body, token: mech.token, via: 'mechanical' };
  }
  const err = error ?? caseSyntaxErrors([{ name: '_', body }])[0]?.error;
  if (!err) return null;
  const guided = parserGuidedCaseRepair(body, err);
  if (guided) return { body: guided.body, token: guided.token, via: 'parser-guided' };
  return null;
}

/** Accept impl content only when the whole file transpiles clean. */
export function acceptImplContent(content: string): boolean {
  return suiteSyntaxError(content) == null;
}

/** Map a sucrase `(line:col)` onto whole-file content (no case-body wrapper). */
export function parseErrorPositionFile(error: string, content: string): ParsePos | null {
  const m = /(?:expected ["']([^"']+)["'][^\(]*)?\((\d+):(\d+)\)/i.exec(error);
  if (!m) return null;
  const expected = m[1];
  const line = Number(m[2]);
  const col = Number(m[3]);
  const lines = content.replace(/\r\n/g, '\n').split('\n');
  const lineIdx = line - 1;
  if (lineIdx < 0 || lineIdx >= lines.length) {
    if (line === 1) return { offset: Math.min(Math.max(col, 0), content.length), expected };
    return null;
  }
  let offset = 0;
  for (let i = 0; i < lineIdx; i++) offset += lines[i]!.length + 1;
  offset += Math.min(col, lines[lineIdx]!.length);
  return { offset: Math.min(offset, content.length), expected };
}

/**
 * SET 18a — parser-guided repair for IMPLEMENTATION files.
 * Error-driven token edits (≤3), accept iff whole-file transpile is clean.
 */
export function parserGuidedImplRepair(
  content: string,
  error: string,
  maxDepth = 3,
): { content: string; token: string } | null {
  if (acceptImplContent(content)) return { content, token: 'already-clean' };
  // Cheap mechanical pre-pass (quotes / closers / trailing commas).
  let pre = asciiQuotes(content);
  pre = stripTrailingCommas(balanceClosers(pre));
  const closedStr0 = closeOpenStringLiteral(pre);
  if (closedStr0) pre = closedStr0.next;
  if (pre !== content && acceptImplContent(pre)) {
    return { content: pre, token: 'mechanical-prepass' };
  }

  type Frame = { content: string; depth: number; lastErr: string };
  const startErr = suiteSyntaxError(pre) ?? error;
  const queue: Frame[] = [{ content: pre, depth: 0, lastErr: startErr }];
  const seen = new Set<string>([content, pre]);
  while (queue.length) {
    const cur = queue.shift()!;
    if (cur.depth >= maxDepth) continue;
    const pos = parseErrorPositionFile(cur.lastErr, cur.content);
    const candidates: { next: string; token: string }[] = [];
    if (pos) {
      if (pos.expected) {
        const ins = applyEdit(cur.content, pos.offset, 'insert', pos.expected);
        if (ins) candidates.push({ next: ins, token: `insert:${pos.expected}` });
      }
      for (const closer of [')', ']', '}', '"', "'", '`', ';']) {
        const ins = applyEdit(cur.content, pos.offset, 'insert', closer);
        if (ins) candidates.push({ next: ins, token: `insert:${closer}` });
      }
      const del = applyEdit(cur.content, pos.offset, 'delete');
      if (del) candidates.push({ next: del, token: 'delete' });
      candidates.push(...strayQuoteDeletes(cur.content, pos.offset));
    }
    for (const off of missingCloserBeforeDotOffsets(cur.content)) {
      const ins = applyEdit(cur.content, off, 'insert', ')');
      if (ins) candidates.push({ next: ins, token: 'insert:)-before-dot' });
    }
    const closedStr = closeOpenStringLiteral(cur.content);
    if (closedStr) candidates.push(closedStr);
    const closedBrace = closeOpenBraces(cur.content);
    if (closedBrace) candidates.push(closedBrace);
    const combo = balanceClosers(stripTrailingCommas(cur.content));
    if (combo !== cur.content) candidates.push({ next: combo, token: 'balance' });
    const regexHeal = healUnterminatedRegexLiteral(cur.content);
    if (regexHeal) candidates.push({ next: regexHeal, token: 'regex-slash' });

    for (const c of candidates) {
      if (seen.has(c.next)) continue;
      seen.add(c.next);
      if (acceptImplContent(c.next)) return { content: c.next, token: c.token };
      const err = suiteSyntaxError(c.next);
      if (err) queue.push({ content: c.next, depth: cur.depth + 1, lastErr: err });
    }
  }
  return null;
}

/**
 * SET 18b — polish a candidate file that crashes on parse/transpile.
 * Tries impl repair for source files and case-body heal for suite-shaped content.
 *
 * SET 25b — sandwiches parser-guided with a delimiter-oracle sweep: cheaper
 * mechanical heals (regex char-class close, block-comment close, closer
 * balance) run before parser-guided, and once more after in case the
 * parser-guided step exposed a new demarcator defect.
 */
export function polishParseCrashContent(
  path: string,
  content: string,
  error: string,
): { content: string; token: string } | null {
  const isSuite = /\.test\.(ts|js)$/.test(path);
  const errPart = isSuite ? error : error.replace(/^transpile [^:]+:\s*/, '');

  // 25b: delimiter oracle sweep BEFORE parser-guided.
  const preDelim = applyDelimiterOraclePolish(content, errPart);
  const startContent = preDelim?.content ?? content;

  const guided = parserGuidedImplRepair(startContent, errPart);
  const guidedOk = guided && guided.token !== 'already-clean';
  let cur = guidedOk ? guided.content : startContent;
  let token = guidedOk
    ? (preDelim ? `delimiter+guided:${guided.token}` : guided.token)
    : (preDelim ? `delimiter:${preDelim.classKey}` : '');

  // 25b: delimiter oracle sweep AFTER parser-guided (mop up any residual issue).
  if (cur === content && !preDelim && !guidedOk) {
    return null;
  }
  const err2 = suiteSyntaxError(cur);
  if (err2 != null) {
    const postDelim = applyDelimiterOraclePolish(cur, err2);
    if (postDelim && postDelim.content !== cur) {
      cur = postDelim.content;
      token = token
        ? `${token}+delimiter:${postDelim.classKey}`
        : `delimiter:${postDelim.classKey}`;
    }
  }

  if (cur === content) return null;
  return { content: cur, token: token || 'delimiter' };
}

/** SET 25b — delimiter-oracle wrapper used only by polishParseCrashContent.
 *  Registered by delimiterOracle.ts on module init to break the import cycle
 *  (delimiterOracle depends on substrate for its own healer building blocks). */
let _applyDelimiterOracleImpl:
  | ((c: string, e?: string) => { content: string; classKey: string; edits: number } | null)
  | null = null;

export function _registerDelimiterOracle(
  impl: (c: string, e?: string) => { content: string; classKey: string; edits: number } | null,
): void {
  _applyDelimiterOracleImpl = impl;
}

function applyDelimiterOraclePolish(
  content: string,
  errorHint?: string,
): { content: string; classKey: string } | null {
  if (!_applyDelimiterOracleImpl) return null;
  const r = _applyDelimiterOracleImpl(content, errorHint);
  return r ? { content: r.content, classKey: r.classKey } : null;
}

// ---------------------------------------------------------------------------
// SET 4b / 15 — deterministic capability scanner (disclosure half; 0 tokens)
// ---------------------------------------------------------------------------

export type OutOfScopeCapability =
  | 'browser'
  | 'computed-style'
  | 'database'
  | 'redis'
  | 'network'
  | 'filesystem';

export interface CapabilityHit {
  item: string;
  capability: OutOfScopeCapability;
  /** 1-based prompt line; omitted when license text cannot be located. */
  line?: number;
  /** Mixed item: assertable structure kept; only the clause is deferred. */
  partial?: boolean;
  /** Capability-gated clause text when partial. */
  clause?: string;
  /** SET 69 — infra-noun item split: binding excised, behavior kept testable. */
  behavioralSplit?: BehavioralSplit;
  /** SET 100c — assertable residue collapsed to whole-line exclude. */
  residueCollapsed?: boolean;
}

/** SET 69 — behavior/binding split for infra-noun spec items. */
export interface BehavioralSplit {
  /** The testable behavioral invariant with the binding clause excised. */
  behavior: string;
  /** The excised infrastructure-binding clause. */
  binding: string;
  /** Simulated-dependency clade the kept case is tagged with. */
  simClade: 'sim-store' | 'sim-clock';
}

/** Numbered (`1.` / `1)`) or bulleted (`-` / `*` / `•`) spec items only. */
export const ITEM_LINE_RE = /^\s*(?:\d+[.)]\s+|[-*•]\s+)/;

/**
 * Assertion verbs/units that license capability keywords inside backticks /
 * quoted HTML. Bare nouns (e.g. viewport in `<meta name="viewport">`) do not.
 */
const ASSERTION_CONTEXT_RE =
  /\b(?:computed(?:\s*styles?)?|getComputedStyle|viewport\s*[≥≤<>]=?\s*\d*|axe-core|axe\.run|focus\s*ring|\d+px(?:\s*(?:at|@|wide|breakpoint))?)\b/i;

const CAPABILITY_PATTERNS: { capability: OutOfScopeCapability; re: RegExp }[] = [
  { capability: 'computed-style', re: /\b(computed\s*styles?|getComputedStyle|viewport|focus\s*ring|\d+px\b|axe-core|axe\.run)\b/i },
  { capability: 'browser', re: /\b(document\.|window\.|localStorage|sessionStorage|DOMContentLoaded|querySelector|playwright|puppeteer|jsdom|browsers?\b)\b/i },
  { capability: 'database', re: /\b(CREATE\s+TABLE|ALTER\s+TABLE|BIGSERIAL|tsvector|postgres|postgresql|mysql|sqlite|prisma|drizzle|SELECT\s+.+\s+FROM)\b/i },
  { capability: 'redis', re: /\b(redis|ioredis|sliding\s+window)\b/i },
  { capability: 'network', re: /\b(SMTP|sendmail|nodemailer|TCP\b|WebSocket|favicon|Content-Type:\s*(image|font|audio|video)\/|fetch\(|axios|http\.createServer|listen\(\s*\d+)\b/i },
  { capability: 'filesystem', re: /\b(fs\.promises|readFileSync|writeFileSync|createWriteStream|path\.join\([^)]*process\.cwd)\b/i },
];

/** Split backtick / simple quoted-HTML spans from plain prose. */
function splitCodeFragments(line: string): { plain: string; code: string } {
  let plain = '';
  let code = '';
  let i = 0;
  while (i < line.length) {
    const c = line[i]!;
    if (c === '`') {
      const end = line.indexOf('`', i + 1);
      if (end < 0) { plain += line.slice(i); break; }
      code += ` ${line.slice(i + 1, end)}`;
      plain += ' ';
      i = end + 1;
      continue;
    }
    // Quoted HTML attribute blobs: "...<meta name="viewport">..." is rare;
    // treat double-quoted runs that look like HTML tags as code fragments.
    if (c === '"' || c === "'") {
      const q = c;
      const end = line.indexOf(q, i + 1);
      if (end > i && /<\/?[A-Za-z]/.test(line.slice(i, end + 1))) {
        code += ` ${line.slice(i + 1, end)}`;
        plain += ' ';
        i = end + 1;
        continue;
      }
    }
    plain += c;
    i++;
  }
  return { plain, code };
}

/** Punctuation/whitespace tolerance after clause-excision — not a certification threshold. */
const ASSERTABLE_RESIDUE_MIN = 4;

/** Tier-1-assertable HTML/DOM structure (string harness can check). */
export function hasAssertableStructure(line: string): boolean {
  if (/`[^`]*<\/?[A-Za-z][^`]*>/.test(line)) return true;
  if (/\b(?:returns?\s+(?:an?\s+)?HTML|innerHTML|outerHTML|textContent|markup|DOM\s+structure|className|data-[\w-]+)\b/i.test(line)) {
    return true;
  }
  // Verb + object — not bare nouns like "page render".
  if (/\b(?:renders?|contains?|includes?)\s+[`'"<\w]/i.test(line)) return true;
  return false;
}

/** Pull a readable capability clause from a mixed item line. */
function extractCapabilityClause(line: string, capability: OutOfScopeCapability): string {
  const body = line.replace(ITEM_LINE_RE, '').trim();
  const patterns: RegExp[] = [];
  if (capability === 'computed-style') {
    patterns.push(
      /(?:when\s+)?viewport\s*[≥≤<>]=?\s*\d+\s*px[^.;]*/i,
      /computed\s*styles?[^.;]*/i,
      /getComputedStyle[^.;]*/i,
      /focus\s*ring[^.;]*/i,
      /axe-core[^.;]*/i,
      /\d+px(?:\s*(?:at|@|wide|breakpoint)[^.;]*)?/i,
    );
  } else {
    const row = CAPABILITY_PATTERNS.find(p => p.capability === capability);
    if (row) patterns.push(row.re);
  }
  for (const re of patterns) {
    const m = re.exec(body);
    if (m) return m[0]!.trim().slice(0, 120);
  }
  return body.slice(0, 120);
}

/** SET 69 — infra nouns whose binding clause is excisable. */
const INFRA_PHRASE_RE =
  /\s*(?:using|with|via|backed\s+by|persisted\s+(?:in|to)|stored\s+in|in)\s+(?:an?\s+)?(?:redis|ioredis|postgres(?:ql)?|mysql|sqlite|(?:the\s+)?database|prisma|drizzle)(?:\s+(?:sorted\s+sets?|sliding\s+window|tables?|schema))?/gi;
const INFRA_NOUN_RE = /\b(?:redis|ioredis|postgres(?:ql)?|mysql|sqlite|database|prisma|drizzle)\b/gi;
/** Behavioral cores that survive a binding excision (deterministic logic). */
const BEHAVIOR_CORE_RE =
  /\b(?:rate.?limit|sliding\s+window|throttl\w*|quota|at\s+most\s+\d+|per\s+(?:second|minute|hour|day)|dedup\w*|cache|session|counter|token|expir\w*|returns?|rejects?|allows?|blocks?|enforces?|limits?|tracks?|computes?)\b/i;
/** Time-boundary semantics → injected-clock clade; else in-memory store. */
const TIME_SEMANTICS_RE =
  /\b(?:sliding\s+window|per\s+(?:second|minute|hour|day)|window|expir\w*|ttl|timeout|time-?based|seconds?|minutes?)\b/i;

/**
 * SET 69 — behavior/binding split (Andy: "does this really require Redis").
 * An infrastructure noun in a spec item excludes only the BINDING clause;
 * the behavioral invariant (a sliding-window rate limit is a deterministic
 * counter + clock — maximally testable) is kept as a testable line tagged
 * with the simulated-dependency clade. Returns null when no behavioral core
 * survives the excision — those items remain honestly whole-line excluded.
 */
export function splitBehaviorBinding(
  line: string,
  capability: OutOfScopeCapability,
): BehavioralSplit | null {
  if (capability !== 'redis' && capability !== 'database') return null;
  const body = line.replace(ITEM_LINE_RE, '').trim();
  const binding = extractCapabilityClause(line, capability);
  let behavior = body.replace(INFRA_PHRASE_RE, '').replace(INFRA_NOUN_RE, '').trim()
    .replace(/\s{2,}/g, ' ')
    .replace(/\s+([,;.])/g, '$1')
    .replace(/^[,;.\s]+|[,;.\s]+$/g, '');
  if (behavior.length < 15 || !BEHAVIOR_CORE_RE.test(behavior)) return null;
  const simClade = TIME_SEMANTICS_RE.test(body) ? 'sim-clock' : 'sim-store';
  behavior += simClade === 'sim-clock'
    ? ' [binding excised — simulate with an injected clock; no real timers]'
    : ' [binding excised — simulate with an in-memory store]';
  return { behavior, binding, simClade };
}

/**
 * Context-aware match: keywords inside backticks / quoted HTML do not fire
 * unless assertion verbs + units are present on the line.
 */
function lineMatchesCapability(line: string, re: RegExp): boolean {
  const { plain, code } = splitCodeFragments(line);
  if (re.test(plain)) return true;
  if (!code.trim()) return false;
  // Code-fragment hits require assertion context (computed / viewport≥ / px-at-…).
  if (!ASSERTION_CONTEXT_RE.test(line)) return false;
  // Still require the capability pattern itself somewhere (usually in plain
  // assertion clause, or assertion-qualified code).
  return re.test(plain + ' ' + code) && ASSERTION_CONTEXT_RE.test(plain + ' ' + code);
}

/**
 * Locate a license / edge string in the raw prompt (SET 18g.i).
 * Returns 1-based line number, or undefined when not found (never invent line 1).
 */
export function findPromptLineNumber(rawPrompt: string, license: string): number | undefined {
  const needle = license.trim();
  if (!needle) return undefined;
  const lines = rawPrompt.split(/\r?\n/);
  for (let i = 0; i < lines.length; i++) {
    const raw = lines[i]!;
    const stripped = raw.replace(ITEM_LINE_RE, '').trim();
    if (raw.includes(needle) || stripped === needle || stripped.includes(needle) || needle.includes(stripped) && stripped.length >= 8) {
      return i + 1;
    }
  }
  return undefined;
}

/**
 * Scan a raw prompt (or licensing text) for capabilities outside Tier 1's
 * in-memory library harness. Zero LLM — pattern table only.
 *
 * SET 15: multi-line prompts only scan numbered/bulleted items (prose skipped).
 * Single-line text (plan-gate licenses) is always scanned.
 * Mixed assertable+capability lines yield `partial: true` hits.
 */
export function scanCapabilities(text: string): CapabilityHit[] {
  const hits: CapabilityHit[] = [];
  const lines = text.split(/\r?\n/);
  const multi = lines.length > 1;
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]!;
    if (!line.trim()) continue;
    if (multi && !ITEM_LINE_RE.test(line)) continue;
    for (const { capability, re } of CAPABILITY_PATTERNS) {
      if (!lineMatchesCapability(line, re)) continue;
      const item = line.trim().slice(0, 80);
      const assertable = hasAssertableStructure(line);
      if (assertable) {
        const body = line.replace(ITEM_LINE_RE, '').trim();
        const clause = extractCapabilityClause(line, capability);
        const residue = body.replace(clause, '').replace(/[\s.,;:()\[\]{}]+/g, '').trim();
        if (residue.length < ASSERTABLE_RESIDUE_MIN) {
          hits.push({ item, capability, line: i + 1, residueCollapsed: true });
          break;
        }
        hits.push({
          item,
          capability,
          line: i + 1,
          partial: true,
          clause,
        });
      } else {
        // SET 69 — infra-noun items: excise the binding, keep the behavior.
        // Browser-truth capabilities (computed-style, real network I/O, …)
        // never split — those exclusions are honest whole-line.
        const split = splitBehaviorBinding(line, capability);
        if (split) {
          hits.push({
            item,
            capability,
            line: i + 1,
            partial: true,
            clause: split.binding,
            behavioralSplit: split,
          });
        } else {
          hits.push({ item, capability, line: i + 1 });
        }
      }
      break; // one capability tag per line
    }
  }
  return hits;
}

/** SET 100d — pre-encode gate: any numbered/bulleted item with assertable residue? */
export function promptHasVerifiableResidue(rawPrompt: string): {
  ok: boolean;
  itemsScanned: number;
  fenced: number;
  residuePresent: number;
} {
  const lines = rawPrompt.split(/\r?\n/);
  let scanned = 0;
  let fenced = 0;
  let residuePresent = 0;
  for (const raw of lines) {
    if (!ITEM_LINE_RE.test(raw)) continue;
    scanned++;
    const single = scanCapabilities(raw);
    if (single.length === 0) {
      residuePresent++;
      continue;
    }
    fenced++;
    if (single.some(h => h.partial || h.behavioralSplit)) residuePresent++;
  }
  return { ok: scanned === 0 || residuePresent > 0, itemsScanned: scanned, fenced, residuePresent };
}

// ---------------------------------------------------------------------------
// SET 5 — index-bound fill helpers (name echo / body wrap from small models)
// ---------------------------------------------------------------------------

/**
 * Strip listing-format echoes the model copies into the `name` field:
 * `[N]`, `it('…')` / `test("…")` wrappers, `kind=behavior|edge|integration`.
 */
export function normalizeCaseName(raw: string): string {
  let s = raw.trim();
  s = s.replace(/^\[\d+\]\s*/, '');
  s = s.replace(/\s*kind=(behavior|edge|integration)\s*$/i, '');
  s = s.trim();
  const wrapped = s.match(/^(?:it|test)\s*\(\s*(['"`])([\s\S]*?)\1\s*\)\s*$/);
  if (wrapped) s = wrapped[2]!;
  else {
    const prefix = s.match(/^(?:it|test)\s*\(\s*(['"`])([\s\S]*?)\1\s*\)\s*/);
    if (prefix) s = prefix[2]!;
  }
  s = s.trim();
  if (s.length >= 2) {
    const a = s[0]!;
    const b = s[s.length - 1]!;
    if ((a === "'" && b === "'") || (a === '"' && b === '"')) s = s.slice(1, -1);
  }
  return s.trim();
}

/** True when two case names refer to the same planned case after normalization. */
export function caseNamesMatch(a: string, b: string): boolean {
  if (a === b) return true;
  return normalizeCaseName(a) === normalizeCaseName(b);
}

/**
 * If the model returned a full `it('name', () => { … })` / `test(…)` call
 * instead of the bare callback body, unwrap the inner body. Accept only when
 * the inner text parses.
 */
export function unwrapCaseBody(body: string): { body: string; unwrapped: boolean } {
  const trimmed = body.trim().replace(/;?\s*$/, '');
  const head = /^(?:it|test)\s*\(\s*(['"`])/.exec(trimmed);
  if (!head) return { body, unwrapped: false };
  let i = head.index + head[0].length;
  const q = head[1]!;
  while (i < trimmed.length) {
    const c = trimmed[i]!;
    if (c === '\\') { i += 2; continue; }
    if (c === q) { i++; break; }
    i++;
  }
  const rest = trimmed.slice(i);
  const arrow = rest.match(/^\s*,\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*\{/);
  const fn = rest.match(/^\s*,\s*(?:async\s+)?function\s*(?:[A-Za-z_$][\w$]*)?\s*\([^)]*\)\s*\{/);
  const match = arrow || fn;
  if (!match) return { body, unwrapped: false };
  const openIdx = i + match[0].length - 1;
  let depth = 0;
  let inStr: string | null = null;
  for (let j = openIdx; j < trimmed.length; j++) {
    const c = trimmed[j]!;
    if (inStr) {
      if (c === '\\') { j++; continue; }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
    if (c === '{') depth++;
    else if (c === '}') {
      depth--;
      if (depth === 0) {
        const after = trimmed.slice(j + 1).trim();
        if (after !== '' && !/^\)\s*;?\s*$/.test(after)) return { body, unwrapped: false };
        const inner = trimmed.slice(openIdx + 1, j).trim();
        if (caseSyntaxErrors([{ name: '_', body: inner }]).length) return { body, unwrapped: false };
        return { body: inner, unwrapped: true };
      }
    }
  }
  return { body, unwrapped: false };
}

/**
 * SET 5a — bind fixTestCases results by INDEX. Returned `name` is advisory;
 * overwrite with the requested/planned name. Applies body unwrap. Prefix-only
 * when the model returns fewer cases than requested.
 */
export function bindFixTestCases<T extends { name: string; kind: string; body: string }>(
  requested: { name: string; kind: string }[],
  returned: T[],
): { cases: T[]; unwrapFixes: number } {
  const n = Math.min(requested.length, returned.length);
  const cases: T[] = [];
  let unwrapFixes = 0;
  for (let i = 0; i < n; i++) {
    const req = requested[i]!;
    const fc = returned[i]!;
    const uw = unwrapCaseBody(fc.body);
    if (uw.unwrapped) unwrapFixes++;
    cases.push({ ...fc, name: req.name, kind: req.kind as T['kind'], body: uw.body });
  }
  return { cases, unwrapFixes };
}

// ---------------------------------------------------------------------------
// 3. Lessons — classKey-matched idioms in, distilled lessons out
// ---------------------------------------------------------------------------

/**
 * SET 31d — merge corpus-published lessons with locally distilled ones into
 * a single ordered, deduplicated list. Corpus lessons come first (broader
 * verified base); local lessons follow and are the ones this install just
 * earned. Without this, callers were free to just concatenate — duplicates
 * (same classKey+idiom re-published in both) silently ate the superbuffer's
 * lesson slots and starved retrieval.
 */
export function mergeLessons(
  corpus: readonly Tier1Lesson[],
  local: readonly Tier1Lesson[],
  cap = 200,
): Tier1Lesson[] {
  const seen = new Set<string>();
  const out: Tier1Lesson[] = [];
  for (const l of [...corpus, ...local]) {
    if (!l?.classKey || !l?.idiom) continue;
    const id = `${l.classKey}|${l.idiom}`;
    if (seen.has(id)) continue;
    seen.add(id);
    out.push(l);
  }
  return out.slice(0, cap);
}

/** Lessons whose classKey matches (exact, or same class prefix). */
export function matchLessons(lessons: Tier1Lesson[], classKeys: string[]): Tier1Lesson[] {
  const keys = new Set(classKeys);
  const prefixes = new Set(classKeys.map(k => k.split('|')[0]));
  const seen = new Set<string>();
  const out: Tier1Lesson[] = [];
  for (const l of lessons) {
    const hit = keys.has(l.classKey) || prefixes.has(l.classKey.split('|')[0]);
    const id = `${l.classKey}|${l.idiom}`;
    if (hit && !seen.has(id)) {
      seen.add(id);
      out.push(l);
    }
  }
  return out.slice(0, 12);
}

/** Prompt block for matched lessons — apply mechanically, do not rediscover. */
export function checkedIdiomsText(matched: Tier1Lesson[]): string {
  if (!matched.length) return '';
  return [
    'Checked idioms from prior runs (apply mechanically when they match — do not rediscover):',
    ...matched.map(l => `- ${l.idiom} → ${l.resolution}`),
  ].join('\n');
}

/** Distill a lesson from a healed failure class. */
export function distillLesson(classKey: string, exampleMessage: string, resolution: string): Tier1Lesson {
  return {
    classKey,
    idiom: exampleMessage.split('\n')[0].slice(0, 160),
    resolution: resolution.slice(0, 200),
    ts: new Date().toISOString(),
  };
}

// ---------------------------------------------------------------------------
// 4. Serial resolution — one failure class per draw
// ---------------------------------------------------------------------------

const KIND_PRIORITY: FailureKind[] = [
  'transpile', 'duplicate-decl', 'regex-literal', 'crash', 'unresolved-import', 'undefined-symbol',
  'is-not-a-function', 'empty-draw', 'catalog-drift', 'runtime', 'throw-mismatch', 'assertion',
];

/** Re-export catalog TOTAL classifier + tripwire for Integration. */
export { classifyCataloged, formatCatalogDriftTripwire } from './errorCatalog.generated';
export type { CatalogClassification, ErrorStratum } from './errorCatalog.generated';
export { strategyOrderForClass, solutionStratumForClass, SOLUTION_STRATA } from './solutionStrata';
export type { Strategy, SolutionStratumRow } from './solutionStrata';

/** Pick the single class to resolve this pass: structural before behavioral,
 *  then largest group. Returns its members. */
export function dominantClass(failures: ClassifiedFailure[]): ClassifiedFailure[] {
  if (!failures.length) return [];
  const groups = new Map<string, ClassifiedFailure[]>();
  for (const f of failures) {
    groups.set(f.classKey, [...(groups.get(f.classKey) ?? []), f]);
  }
  const ranked = [...groups.values()].sort((a, b) => {
    const pa = KIND_PRIORITY.indexOf(a[0].kind);
    const pb = KIND_PRIORITY.indexOf(b[0].kind);
    if (pa !== pb) return pa - pb;
    return b.length - a.length;
  });
  return ranked[0];
}
