#!/usr/bin/env node

import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { execFileSync, spawnSync } from "node:child_process";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const here = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(here, "..");
const fixturesRoot = join(here, "fixtures");
const skillBundleEntries = [
  "SKILL.md",
  "README.md",
  "INTENT.md",
  "docs",
  "phases",
  "evals",
  "templates",
  "sub-skills",
  "tools",
];

function usage() {
  console.error([
    "Usage:",
    "  node evals/fixture-runner.mjs list",
    "  node evals/fixture-runner.mjs prepare <fixture-id> <output-dir> [--force]",
    "  node evals/fixture-runner.mjs verify <fixture-id> <project-dir>",
    "  node evals/fixture-runner.mjs run <fixture-id> <output-dir> [--force] [--timeout-ms <ms>]",
    "  node evals/fixture-runner.mjs repeat <fixture-id> <output-dir-prefix> [--runs <n>] [--timeout-ms <ms>]",
    "  node evals/fixture-runner.mjs ablate <fixture-id> <output-dir> [--force] [--timeout-ms <ms>]",
    "  node evals/fixture-runner.mjs compare <baseline.json> <current.json>",
  ].join("\n"));
}

function fixtureDir(id) {
  return join(fixturesRoot, id);
}

function loadFixture(id) {
  const metaPath = join(fixtureDir(id), "fixture.json");
  if (!existsSync(metaPath)) {
    throw new Error(`Unknown fixture '${id}'. Expected ${metaPath}`);
  }
  return JSON.parse(readFileSync(metaPath, "utf8"));
}

function listFixtures() {
  const ids = readdirSync(fixturesRoot, { withFileTypes: true })
    .filter((entry) => entry.isDirectory())
    .map((entry) => entry.name)
    .sort();

  const fixtures = ids.map((id) => loadFixture(id));
  console.log(JSON.stringify({ fixtures }, null, 2));
}

function copySkillBundle(destination) {
  mkdirSync(destination, { recursive: true });
  for (const entry of skillBundleEntries) {
    cpSync(join(repoRoot, entry), join(destination, entry), { recursive: true });
  }
}

function initializeGitWorkspace(projectDir) {
  if (existsSync(join(projectDir, ".git"))) return;

  try {
    execFileSync("git", ["init", "-b", "main"], { cwd: projectDir, stdio: "ignore" });
  } catch {
    execFileSync("git", ["init"], { cwd: projectDir, stdio: "ignore" });
  }

  execFileSync("git", ["config", "user.name", "Fixture Runner"], { cwd: projectDir, stdio: "ignore" });
  execFileSync("git", ["config", "user.email", "fixture-runner@example.invalid"], { cwd: projectDir, stdio: "ignore" });
  execFileSync("git", ["add", "."], { cwd: projectDir, stdio: "ignore" });

  try {
    execFileSync("git", ["commit", "-m", "Fixture seed"], { cwd: projectDir, stdio: "ignore" });
  } catch {
    // Leave the workspace initialized even if commit creation is unavailable.
  }
}

function currentDateString() {
  return new Date().toISOString().slice(0, 10);
}

function escapeRegex(value) {
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function writeJsonArtifact(outputPath, payload) {
  const absolutePath = resolve(outputPath);
  mkdirSync(dirname(absolutePath), { recursive: true });
  writeFileSync(absolutePath, JSON.stringify(payload, null, 2) + "\n");
  return absolutePath;
}

function readJsonArtifact(inputPath) {
  return JSON.parse(readFileSync(resolve(inputPath), "utf8"));
}

function readDirectoryFiles(projectDir, relativeDir) {
  const root = join(projectDir, relativeDir);
  if (!existsSync(root)) return [];

  const files = [];
  const visit = (currentRelativeDir) => {
    const currentFullDir = join(projectDir, currentRelativeDir);
    for (const entry of readdirSync(currentFullDir, { withFileTypes: true })) {
      const entryRelativePath = join(currentRelativeDir, entry.name);
      if (entry.isDirectory()) {
        visit(entryRelativePath);
        continue;
      }
      files.push({
        relative_path: entryRelativePath,
        content: readFileSync(join(projectDir, entryRelativePath), "utf8"),
      });
    }
  };

  visit(relativeDir);
  return files;
}

function buildFixtureSpecificChecks(meta, projectDir, canonicalContents) {
  const checks = [];
  const expectations = meta.expectations ?? {};

  if (Array.isArray(expectations.archive_contains) && expectations.archive_contains.length > 0) {
    const archiveFiles = readDirectoryFiles(projectDir, "docs/archive");
    const missingArchiveMarkers = expectations.archive_contains.filter((marker) =>
      !archiveFiles.some((file) => file.content.includes(marker))
    );

    checks.push({
      id: "archive-preservation",
      pass: missingArchiveMarkers.length === 0,
      notes: missingArchiveMarkers.length === 0
        ? `Archived stale content markers found in docs/archive (${expectations.archive_contains.length} expected)`
        : `Missing archived stale markers: ${missingArchiveMarkers.join(", ")}`,
    });
  }

  if (
    Array.isArray(expectations.forbidden_strings_in_canonical_docs) &&
    expectations.forbidden_strings_in_canonical_docs.length > 0
  ) {
    const leakedStrings = expectations.forbidden_strings_in_canonical_docs.filter((value) =>
      canonicalContents.includes(value)
    );

    checks.push({
      id: "stale-content-removed",
      pass: leakedStrings.length === 0,
      notes: leakedStrings.length === 0
        ? "Canonical docs no longer contain stale fixture-specific content"
        : `Stale strings still present in canonical docs: ${leakedStrings.join(", ")}`,
    });
  }

  if (Array.isArray(expectations.preserve_strings) && expectations.preserve_strings.length > 0) {
    const missingPreservedStrings = [];
    for (const item of expectations.preserve_strings) {
      const content = readOptional(projectDir, item.path);
      const includes = Array.isArray(item.includes) ? item.includes : [];
      for (const snippet of includes) {
        if (!content.includes(snippet)) {
          missingPreservedStrings.push(`${item.path}: ${snippet}`);
        }
      }
    }

    checks.push({
      id: "preserved-healthy-docs",
      pass: missingPreservedStrings.length === 0,
      notes: missingPreservedStrings.length === 0
        ? "Expected healthy doc content was preserved"
        : `Missing preserved doc content: ${missingPreservedStrings.join(", ")}`,
    });
  }

  return checks;
}

function buildMilestonePlan(meta, commands) {
  const milestoneMap = {
    "empty-repo": [
      {
        id: "M1",
        title: "Repository Foundation",
        goal: "Create the first runnable project foundation without inventing product behavior.",
        scope: "README.md, placeholder source layout, environment examples, repo hygiene",
        deliverables: [
          "A minimal source layout is chosen and documented",
          "Environment/dependency placeholders are recorded without inventing external systems",
        ],
        checkpoint: "M1 establishes the first concrete repo shape while preserving the empty starter constraints.",
        done: [
          "A future source layout is documented and reflected in the repo",
          "Unknown runtime/deployment details remain explicitly tracked instead of assumed",
        ],
      },
      {
        id: "M2",
        title: "First Runnable Surface",
        goal: "Add the first real user-facing or developer-facing runnable surface when the product shape becomes clear.",
        scope: "Future application entry point, smoke verification, setup docs",
        deliverables: [
          "A concrete runnable entry point exists",
          "A smoke path is mechanically verifiable",
        ],
        checkpoint: "M2 begins once the repo stops being purely scaffold and gains a real executable surface.",
        done: [
          "The chosen runnable surface starts successfully",
          "Verification proves the first runnable path works end-to-end",
        ],
      },
      {
        id: "M3",
        title: "First Feature Delivery",
        goal: "Deliver the first user-visible feature on top of the validated project foundation.",
        scope: "First feature modules, tests, docs, release notes",
        deliverables: [
          "The first scoped feature is implemented",
          "Feature behavior is documented and verified",
        ],
        checkpoint: "M3 is the first milestone where feature delivery becomes the focus instead of repo setup.",
        done: [
          "A real feature exists with evidence of correct behavior",
          "Quality gates remain green after the feature lands",
        ],
      },
    ],
    "pure-library": [
      {
        id: "M1",
        title: "Library Quality Baseline",
        goal: "Harden the existing library package structure and baseline tests.",
        scope: "Library packaging, unit tests, linting, docs",
        deliverables: [
          "Library package boundaries are documented",
          "Existing tests run cleanly under the quality gates",
        ],
        checkpoint: "M1 establishes the library as a stable baseline for future extension.",
        done: [
          "The public API surface is documented",
          "Quality gates prove the library baseline is stable",
        ],
      },
      {
        id: "M2",
        title: "API Refinement",
        goal: "Improve the public library API without breaking its contract.",
        scope: "Public API modules, helpers, usage docs",
        deliverables: [
          "Public API naming and examples are refined",
          "Regression coverage protects the import surface",
        ],
        checkpoint: "M2 keeps the library focused on API clarity rather than service-style growth.",
        done: [
          "API improvements are reflected in docs and tests",
          "The public import surface remains stable",
        ],
      },
      {
        id: "M3",
        title: "First Library Extension",
        goal: "Ship the next coherent library capability on top of the hardened baseline.",
        scope: "New library feature, tests, examples",
        deliverables: [
          "A new library capability is implemented",
          "Examples and tests cover the new capability",
        ],
        checkpoint: "M3 is the first library feature milestone after the repo operating system is in place.",
        done: [
          "The new capability is shipped with tests",
          "Quality gates remain green after the extension",
        ],
      },
    ],
    "user-facing-cli": [
      {
        id: "M1",
        title: "CLI Quality Baseline",
        goal: "Wrap the existing CLI and smoke test in a stable operating system.",
        scope: "CLI scripts, smoke verification, docs, quality infra",
        deliverables: [
          "CLI commands are documented in the scaffold docs",
          "The existing smoke path is integrated into the quality gates",
        ],
        checkpoint: "M1 preserves the CLI as the primary user-facing surface and makes it mechanically verifiable.",
        done: [
          "CLI verification is part of the routine gate flow",
          "The CLI contract is documented for future work",
        ],
      },
      {
        id: "M2",
        title: "CLI Experience Hardening",
        goal: "Improve CLI robustness and feedback without changing its core contract.",
        scope: "CLI UX, error handling, smoke scenarios",
        deliverables: [
          "CLI errors include remediation hints",
          "Smoke coverage is broadened to the most important CLI paths",
        ],
        checkpoint: "M2 makes the CLI safer to evolve and easier for agents to debug.",
        done: [
          "User-facing CLI failure modes are better handled",
          "Smoke verification covers the key CLI journey",
        ],
      },
      {
        id: "M3",
        title: "First CLI Feature Expansion",
        goal: "Deliver the next meaningful CLI feature on top of the hardened baseline.",
        scope: "New CLI feature, tests, docs",
        deliverables: [
          "A new CLI feature is implemented",
          "The new feature is covered by smoke or end-to-end checks",
        ],
        checkpoint: "M3 is the first feature milestone after the CLI operating system is in place.",
        done: [
          "The new CLI feature is documented and verified",
          "Existing CLI behavior remains stable",
        ],
      },
    ],
  };

  const milestones = milestoneMap[meta.category] ?? milestoneMap["empty-repo"];
  const overviewRows = milestones
    .map((milestone, index) => `| ${milestone.id} | ${milestone.title} | pending | M${index} | — |`)
    .join("\n");

  const verificationBlock = `${commands.quality}\n${commands.docs}`;

  const blocks = milestones
    .map((milestone) => [
      `## ${milestone.id}: ${milestone.title}`,
      "",
      `**Goal**: ${milestone.goal}`,
      "",
      `**Scope**: ${milestone.scope}`,
      "",
      "**Deliverables**:",
      ...milestone.deliverables.map((item) => `- [ ] ${item}`),
      "",
      "**Verification**:",
      "```text",
      verificationBlock,
      "```",
      "",
      `**Compaction Checkpoint**: ${milestone.checkpoint}`,
      "",
      "**Done When**:",
      ...milestone.done.map((item) => `- ${item}`),
      "- All verification commands pass",
      `- \`${commands.docs}\` passes`,
      "",
      "---",
      "",
    ].join("\n"))
    .join("");

  return {
    overviewRows,
    blocks,
    m1Title: milestones[0]?.title ?? "",
  };
}

function buildFeedbackProfile(meta, commands, harnessManifest) {
  if (meta.category === "user-facing-cli") {
    return {
      version: 1,
      project: {
        name: meta.id,
        state: meta.id === "stale-docs-cli" ? "reorganizing-existing" : "existing-repo",
        kind: "cli",
        languages: ["JavaScript"],
        user_facing_surfaces: ["cli"],
      },
      execution_contract: harnessManifest.execution_contract,
      manifest_path: "docs/harness-manifest.json",
      feedback_surfaces: {
        static_constraint: {
          status: "partial",
          mechanisms: [
            "Primary quality gate wrapper",
          ],
          commands: [
            commands.quality,
          ],
          evidence_paths: [
            "scripts/quality-gates.*",
          ],
          gaps: [
            "No dedicated architecture-boundary enforcement is recorded yet.",
          ],
        },
        contract: {
          status: "partial",
          mechanisms: [
            "Repo tests and smoke verification",
          ],
          commands: [
            commands.quality,
          ],
          evidence_paths: [
            "package.json",
            "tests/",
          ],
          gaps: [
            "CLI argument or output shape is not captured in a separate machine-readable contract yet.",
          ],
        },
        runtime: {
          status: "partial",
          mechanisms: [
            "CLI smoke command output",
          ],
          commands: [
            commands.quality,
          ],
          evidence_paths: [
            "tests/",
            "README.md",
          ],
          gaps: [
            "Runtime evidence is mostly limited to smoke-test output and shell-visible failures.",
          ],
        },
        business_flow: {
          status: "strong",
          mechanisms: [
            "CLI smoke verification",
          ],
          commands: [
            commands.quality,
          ],
          evidence_paths: [
            "tests/",
          ],
          gaps: [],
        },
      },
      business_flows: [
        {
          name: "Primary CLI command",
          classification: "repo-confirmed",
          current_coverage: ["runtime", "business_flow"],
          verification_target: "The primary CLI smoke path passes end-to-end from the repo's primary shell.",
          evidence_paths: ["tests/"],
        },
      ],
      observability: {
        logs: "CLI stdout/stderr and smoke-test output",
        metrics_traces: "Not available in this tiny CLI fixture",
        runtime_debug: "Shell-visible CLI output plus smoke-test failures",
      },
      priority_gaps: [
        "Strengthen static boundary checks around the CLI entry point and internal application logic.",
        "Capture runtime evidence in a more structured form than smoke-test output alone.",
      ],
      human_inputs: [
        {
          type: "human_decision",
          name: "Highest-priority CLI scenarios",
          reason: "Business criticality of CLI scenarios cannot be inferred from the tiny fixture repo alone.",
          blocking: false,
        },
      ],
    };
  }

  if (meta.category === "pure-library") {
    return {
      version: 1,
      project: {
        name: meta.id,
        state: "existing-repo",
        kind: "library",
        languages: ["Python"],
        user_facing_surfaces: ["library-api"],
      },
      execution_contract: harnessManifest.execution_contract,
      manifest_path: "docs/harness-manifest.json",
      feedback_surfaces: {
        static_constraint: {
          status: "partial",
          mechanisms: [
            "Primary quality gate wrapper",
          ],
          commands: [
            commands.quality,
          ],
          evidence_paths: [
            "scripts/quality-gates.*",
          ],
          gaps: [
            "No explicit import-boundary enforcement is recorded for the library package structure.",
          ],
        },
        contract: {
          status: "strong",
          mechanisms: [
            "Existing library tests",
          ],
          commands: [
            commands.quality,
          ],
          evidence_paths: [
            "tests/",
          ],
          gaps: [],
        },
        runtime: {
          status: "limited-by-project-shape",
          mechanisms: [
            "Test-run failure output",
          ],
          commands: [
            commands.quality,
          ],
          evidence_paths: [
            "tests/",
          ],
          gaps: [
            "No standalone runtime surface exists outside the library test harness.",
          ],
        },
        business_flow: {
          status: "partial",
          mechanisms: [
            "Public API regression tests",
          ],
          commands: [
            commands.quality,
          ],
          evidence_paths: [
            "tests/",
          ],
          gaps: [
            "There is no separate acceptance-style contract for public API scenarios yet.",
          ],
        },
      },
      business_flows: [
        {
          name: "Public import surface",
          classification: "repo-confirmed",
          current_coverage: ["contract", "business_flow"],
          verification_target: "Existing library tests preserve the intended public API behavior.",
          evidence_paths: ["tests/"],
        },
      ],
      observability: {
        logs: "Library tests and assertion output only",
        metrics_traces: "Not available in this library fixture",
        runtime_debug: "Python test failures and stack traces",
      },
      priority_gaps: [
        "Add stronger static package-boundary enforcement for the public API versus internals.",
        "Document which public API paths are compatibility-critical before expansion.",
      ],
      human_inputs: [
        {
          type: "human_decision",
          name: "Compatibility-critical public API paths",
          reason: "The repo shows the current test shape but not which API paths matter most to users.",
          blocking: false,
        },
      ],
    };
  }

  return {
    version: 1,
    project: {
      name: meta.id,
      state: "greenfield-empty",
      kind: "unknown",
      languages: [],
      user_facing_surfaces: [],
    },
    execution_contract: harnessManifest.execution_contract,
    manifest_path: "docs/harness-manifest.json",
    feedback_surfaces: {
      static_constraint: {
        status: "bootstrap-required",
        mechanisms: [
          "Primary doc validation script",
          "Primary quality gate wrapper",
        ],
        commands: [
          commands.docs,
          commands.quality,
        ],
        evidence_paths: [
          "scripts/validate-docs.*",
          "scripts/quality-gates.*",
        ],
        gaps: [
          "No code-level lint, formatter, or architecture-boundary enforcement exists yet.",
        ],
      },
      contract: {
        status: "bootstrap-required",
        mechanisms: [],
        commands: [],
        evidence_paths: [],
        gaps: [
          "No runtime contracts exist yet because no application surface has been implemented.",
        ],
      },
      runtime: {
        status: "not-applicable-yet",
        mechanisms: [],
        commands: [],
        evidence_paths: [],
        gaps: [
          "No runnable application surface exists yet.",
        ],
      },
      business_flow: {
        status: "human-input-needed",
        mechanisms: [],
        commands: [],
        evidence_paths: [],
        gaps: [
          "Top critical user or operator flows have not been confirmed yet.",
        ],
      },
    },
    business_flows: [],
    observability: {
      logs: "Not available yet in this empty scaffold fixture",
      metrics_traces: "Not available yet in this empty scaffold fixture",
      runtime_debug: "Not available yet in this empty scaffold fixture",
    },
    priority_gaps: [
      "Confirm the first real user or operator flow before inventing business-flow tests.",
      "Establish the first code-level static and contract checks once a runtime surface exists.",
    ],
    human_inputs: [
      {
        type: "human_decision",
        name: "Top 1-3 critical user or operator flows",
        reason: "Business-flow verification cannot be designed safely from an empty repo alone.",
        blocking: true,
      },
      {
        type: "human_decision",
        name: "Unacceptable failure modes",
        reason: "The harness needs to know which failures are too expensive to miss.",
        blocking: true,
      },
      {
        type: "human_decision",
        name: "Environment and deployment assumptions",
        reason: "An empty repo does not reveal runtime targets or external-system expectations.",
        blocking: false,
      },
    ],
  };
}

function buildScaffoldValues(meta) {
  const primaryShell = process.platform === "win32" ? "windows-powershell" : "unix-shell";
  const commands = process.platform === "win32"
    ? {
        quality: "powershell -File scripts/quality-gates.ps1",
        docs: "powershell -File scripts/validate-docs.ps1",
        lintPs: "Write-Output 'No linter configured yet; establish one in M0'",
        formatPs: "Write-Output 'No formatter configured yet; establish one in M0'",
        testPs: "Write-Output 'No tests configured yet; establish baseline in M0'",
        lint: "",
        format: "",
        test: "",
      }
    : {
        quality: "bash scripts/quality-gates.sh",
        docs: "bash scripts/validate-docs.sh",
        lintPs: "",
        formatPs: "",
        testPs: "",
        lint: "echo 'No linter configured yet; establish one in M0'",
        format: "echo 'No formatter configured yet; establish one in M0'",
        test: "echo 'No tests configured yet; establish baseline in M0'",
      };

  const scriptCommands = {
    lint: process.platform === "win32" ? "Write-Output 'No linter configured yet; establish one in M0'" : "echo 'No linter configured yet; establish one in M0'",
    format: process.platform === "win32" ? "Write-Output 'No formatter configured yet; establish one in M0'" : "echo 'No formatter configured yet; establish one in M0'",
    test: process.platform === "win32" ? "Write-Output 'No tests configured yet; establish baseline in M0'" : "echo 'No tests configured yet; establish baseline in M0'",
  };

  const plan = buildMilestonePlan(meta, commands);
  const metrics = meta.phase1_answers.success_metrics;
  const date = currentDateString();
  const e2eRequired = meta.expectations.e2e_gate === "required";
  const e2eFramework = e2eRequired ? "fixture-smoke" : "none";
  const e2eCommand = e2eRequired
    ? (process.platform === "win32" ? "npm run smoke" : "npm run smoke")
    : "Not applicable";
  const primaryScriptExtension = primaryShell === "windows-powershell" ? ".ps1" : ".sh";
  const harnessManifest = {
    version: 1,
    documents: [
      "AGENTS.md",
      "docs/project_spec.md",
      "docs/prompt.md",
      "docs/plan.md",
      "docs/implement.md",
      "docs/documentation.md",
      "docs/failures/taxonomy.md",
      "docs/compaction-strategy.md",
      "docs/feedback-profile.json",
      "docs/harness-manifest.json",
    ],
    scripts: [
      `scripts/quality-gates${primaryScriptExtension}`,
      `scripts/validate-docs${primaryScriptExtension}`,
    ],
    sub_skills: [
      "iterate",
      "quality-audit",
      "milestone-promote",
    ],
    execution_contract: {
      primary_shell: primaryShell,
      primary_quality_gate_command: commands.quality,
      primary_doc_validation_command: commands.docs,
      portable_fallbacks: {
        quality_gates: {
          "windows-powershell": "powershell -File scripts/quality-gates.ps1",
          "unix-shell": "bash scripts/quality-gates.sh",
        },
        doc_validation: {
          "windows-powershell": "powershell -File scripts/validate-docs.ps1",
          "unix-shell": "bash scripts/validate-docs.sh",
        },
      },
    },
    feedback_profile_path: "docs/feedback-profile.json",
  };
  const feedbackProfile = buildFeedbackProfile(meta, commands, harnessManifest);

  return {
    PROJECT_NAME: meta.id,
    PROJECT_STATE: feedbackProfile.project.state,
    PROJECT_KIND: feedbackProfile.project.kind,
    PROJECT_LANGUAGES: feedbackProfile.project.languages,
    USER_FACING_SURFACES: feedbackProfile.project.user_facing_surfaces,
    PRIORITY_GAPS: feedbackProfile.priority_gaps,
    HUMAN_INPUTS: feedbackProfile.human_inputs,
    BUSINESS_FLOWS: feedbackProfile.business_flows,
    NORTH_STAR: meta.phase1_answers.north_star,
    PRIORITY_ORDERING: "Correctness > legibility > future extensibility > speed",
    HARD_CONSTRAINTS: meta.phase1_answers.hard_constraints,
    HARD_CONSTRAINTS_DETAILED: `- ${meta.phase1_answers.hard_constraints}\n- Keep the scaffold aligned with the fixture contract and avoid inventing unsupported product details.`,
    FORBIDDEN_PATTERNS: meta.phase1_answers.hard_constraints,
    REQUIRED_READING_LIST: [
      "4. `docs/prompt.md` — current milestone scope and constraints",
      "5. `docs/plan.md` — roadmap, status, and operations cadence",
      "6. `docs/implement.md` — iteration runbook and recovery rules",
      "7. `docs/project_spec.md` — stable program-level rules and quality model",
      "8. `docs/documentation.md` — iteration log, decisions, and known issues",
      "9. `docs/failures/taxonomy.md` — recurring failure patterns and prevention actions",
      "10. `docs/compaction-strategy.md` — long-session recovery protocol",
    ].join("\n"),
    PRIMARY_SHELL: primaryShell,
    QUALITY_GATES_PRIMARY_COMMAND: commands.quality,
    DOC_VALIDATION_PRIMARY_COMMAND: commands.docs,
    FEEDBACK_PROFILE: feedbackProfile,
    FEEDBACK_PROFILE_JSON: JSON.stringify(feedbackProfile, null, 2),
    HARNESS_MANIFEST: harnessManifest,
    HARNESS_MANIFEST_JSON: JSON.stringify(harnessManifest, null, 2),
    EXECUTION_COMMANDS: `- Run quality gates: \`${commands.quality}\`\n- Run doc validation: \`${commands.docs}\`\n- Start work by reading \`AGENTS.md\` -> \`docs/prompt.md\` -> \`docs/plan.md\``,
    LOG_ACCESS: feedbackProfile.observability.logs,
    METRICS_ACCESS: feedbackProfile.observability.metrics_traces,
    UI_DEBUG_ACCESS: feedbackProfile.observability.runtime_debug,
    CODE_MAP: "- Repo currently contains only the root README, fixture metadata, and the installed scaffold skill bundle.\n- No source tree exists yet; M0 establishes docs, scripts, and operating rules first.",
    ARCHITECTURE_LAYERS: "- Current state: no application layers yet.\n- M0 introduces scaffold docs/scripts; future milestones define actual runtime layers once code exists.",
    DEPENDENCY_DIRECTION: "AGENTS/docs -> scripts -> future source modules (no reverse dependency from docs into runtime code).",
    ENFORCEMENT_MECHANISM: "Primary quality gate and doc validation scripts; stronger code-layer linting is deferred to the first real runtime milestone.",
    MAX_FILES_PER_CHANGE: "3",
    RUN_COMMANDS: "No runnable application surface exists yet. Current runnable commands are the scaffold validation scripts only.",
    KEY_ARTIFACTS: "AGENTS.md, docs/, scripts/, .agents/skills/, docs/harness-manifest.json, docs/feedback-profile.json",
    METRIC_1_NAME: "Scaffold completeness",
    CURRENT_1: "0%",
    METHOD_1: "Presence of required scaffold docs, scripts, and sub-skills",
    TARGET_1: "100%",
    METRIC_2_NAME: "Mechanical validation readiness",
    CURRENT_2: "0%",
    METHOD_2: "Primary quality gate and doc validation commands execute successfully",
    TARGET_2: "100%",
    METRIC_DEPENDENCY_GRAPH: "Scaffold completeness -> Mechanical validation readiness -> Safe autonomous iteration",
    LAYER_1: "Scaffold Docs",
    PURPOSE_1: "Store repo memory, plans, constraints, and recovery rules",
    MODULES_1: "AGENTS.md, docs/project_spec.md, docs/plan.md, docs/prompt.md",
    ALLOWED_DEPS_1: "Fixture inputs, repo discovery findings",
    FAILURES_1: "Stale docs, missing cross-references, placeholder leakage",
    LAYER_2: "Validation Scripts",
    PURPOSE_2: "Mechanically enforce the scaffold contract and future quality gates",
    MODULES_2: "scripts/validate-docs.*, scripts/quality-gates.*",
    ALLOWED_DEPS_2: "Docs, future project tooling",
    FAILURES_2: "Broken commands, inconsistent execution contract, missing files",
    CATEGORY_1: "Shell / Tooling",
    TECH_1: primaryShell,
    RATIONALE_1: "Matches the current fixture host environment for the primary execution contract",
    CATEGORY_2: "Scaffold Strategy",
    TECH_2: "Template-driven repo operating system",
    RATIONALE_2: "Keeps the scaffold legible, reusable, and mechanically verifiable",
    LINTER: "none-yet",
    LINT_CMD: scriptCommands.lint || "Write-Output 'No linter configured yet; establish one in M0'",
    LINT_CONFIG: "—",
    FORMATTER: "none-yet",
    FORMAT_CMD: scriptCommands.format || "Write-Output 'No formatter configured yet; establish one in M0'",
    FORMAT_CONFIG: "—",
    TEST_RUNNER: "none-yet",
    TEST_CMD: scriptCommands.test || "Write-Output 'No tests configured yet; establish baseline in M0'",
    TEST_CONFIG: "—",
    E2E_FRAMEWORK: e2eFramework,
    E2E_CMD: e2eCommand,
    E2E_CONFIG: e2eRequired ? "package.json#scripts.smoke" : "—",
    QUALITY_SCORE: "0",
    LAST_AUDIT_DATE: "not run yet",
    MILESTONE_OVERVIEW_ROWS: plan.overviewRows,
    MILESTONE_BLOCKS: plan.blocks,
    MILESTONE_ID: "M0",
    MILESTONE_TITLE: "Quality Infrastructure",
    CURRENT_MILESTONE_TITLE: "Quality Infrastructure",
    ACTIVE_DELIVERABLE: "Generate the scaffold operating system and validate it mechanically",
    LAST_VALIDATION_RESULT: "Not run yet",
    CURRENT_RUN_SURFACE: "No runnable application surface exists yet",
    KNOWN_ISSUE_1: "No application code or runtime surface exists yet; future milestones will define one when the product shape is known",
    KNOWN_ISSUE_2: "Environment variables and deployment details remain intentionally deferred to the Needs Manifest",
    DECISION_1: "Use a platform-native execution contract, encode artifact inventory in docs/harness-manifest.json, and encode feedback surfaces in docs/feedback-profile.json",
    DECISION_2: "Keep M0 focused on scaffold infrastructure rather than inventing product features",
    MILESTONE_SCOPE: "Create the repo operating system, the manifest-driven validation scripts, and installed sub-skills without inventing application code.",
    IN_SCOPE_1: "Generate AGENTS.md plus the core docs and the primary-shell validation scripts",
    IN_SCOPE_2: "Install the shipped sub-skills and validate the scaffold mechanically",
    ALLOWED_LAYER_1: "docs/ and AGENTS.md",
    ALLOWED_LAYER_2: "scripts/ and .agents/skills/",
    OFF_LIMITS_1: "Do not modify or invent future application source directories unless the active milestone explicitly creates them",
    NON_GOAL_1: "Do not invent application features or runtime surfaces",
    NON_GOAL_2: "Do not enter Phase 5 continuous work during this fixture self-test",
    CONSTRAINT_1: meta.phase1_answers.hard_constraints,
    CONSTRAINT_2: "PowerShell is the primary execution contract on this Windows fixture",
    VERIFICATION_COMMANDS: `${commands.quality}\n${commands.docs}`,
    EVIDENCE_1: "Primary quality gate command executes and reports the expected scaffold checks",
    EVIDENCE_2: "Primary doc validation command passes with no missing-file or placeholder errors",
    COMPACTION_CHECKPOINT: "M0 creates the scaffold operating system and validation contract. Future sessions should be able to resume from AGENTS.md, docs/prompt.md, and docs/plan.md without re-discovering the repo.",
    DONE_CONDITION_1: "All required scaffold docs, scripts, and sub-skills exist",
    DONE_CONDITION_2: "The execution contract is consistent across docs and scripts",
    ITERATION_NUMBER: "0",
    DATE: date,
    ONE_SENTENCE_DESCRIPTION: "Initial scaffold generation baseline",
    FILE_LIST: "None yet — replace with concrete file list after the first iteration",
    ROLLBACK_SCOPE: "No rollback yet — initial scaffold baseline",
    PASS_OR_FAIL_WITH_EVIDENCE: "Not run yet",
    NEXT_ACTION: "Run the primary quality gate command after rendering the scaffold",
    FOCUSED_VERIFICATION_COMMANDS: commands.docs,
    REGRESSION_CHECK_COMMANDS: commands.quality,
    FAILURE_CATEGORY_1: "Placeholder leakage",
    FAILURE_CATEGORY_2: "Execution contract drift",
    METRIC: "Scaffold completeness",
    LAYER: "Scaffold docs or validation scripts",
    SYMPTOMS: "Missing required files, unresolved placeholders, or mismatched commands",
    VARIABLE_NAME: "VARIABLE_NAME",
    COMPACTION_ITERATION_THRESHOLD: "10",
    LINT_COMMAND: scriptCommands.lint || "echo 'No linter configured yet; establish one in M0'",
    FORMAT_CHECK_COMMAND: scriptCommands.format || "echo 'No formatter configured yet; establish one in M0'",
    TEST_COMMAND: scriptCommands.test || "echo 'No tests configured yet; establish baseline in M0'",
    OPTIONAL_TYPE_CHECK_GATE_SH: "",
    OPTIONAL_ARCHITECTURE_GATE_SH: "",
    OPTIONAL_E2E_GATE_SH: e2eRequired ? `# E2E gate required for this fixture\nrun_gate "business-flow" "E2E Tests" "${meta.category === "user-facing-cli" ? "npm run smoke" : "echo 'Add E2E command'"}"` : "",
    LINT_COMMAND_PS: "Write-Output 'No linter configured yet; establish one in M0'",
    FORMAT_CHECK_COMMAND_PS: "Write-Output 'No formatter configured yet; establish one in M0'",
    TEST_COMMAND_PS: meta.category === "user-facing-cli" ? "npm test" : "Write-Output 'No tests configured yet; establish baseline in M0'",
    OPTIONAL_TYPE_CHECK_GATE_PS: "",
    OPTIONAL_ARCHITECTURE_GATE_PS: "",
    OPTIONAL_E2E_GATE_PS: e2eRequired ? `# E2E gate required for this fixture\nRun-Gate "business-flow" "E2E Tests" { npm run smoke }` : "",
  };
}

function prepareFixture(id, outputDir, force, verbose = true, includeSkill = true) {
  const meta = loadFixture(id);
  const sourceRepo = join(fixtureDir(id), "repo");
  const absoluteOutput = resolve(outputDir);

  if (existsSync(absoluteOutput)) {
    if (!force) {
      throw new Error(`Output directory already exists: ${absoluteOutput}. Pass --force to replace it.`);
    }
    rmSync(absoluteOutput, { recursive: true, force: true });
  }

  cpSync(sourceRepo, absoluteOutput, { recursive: true });
  if (includeSkill) {
    copySkillBundle(join(absoluteOutput, ".agents", "skills", "project-scaffold"));
  }
  writeFileSync(join(absoluteOutput, ".fixture-meta.json"), JSON.stringify(meta, null, 2) + "\n");
  writeFileSync(join(absoluteOutput, ".fixture-scaffold-values.json"), JSON.stringify(buildScaffoldValues(meta), null, 2) + "\n");
  writeFileSync(join(absoluteOutput, ".fixture-seed-prompt.txt"), buildSeedPrompt(meta));
  initializeGitWorkspace(absoluteOutput);

  if (verbose) {
    console.log(JSON.stringify({
      prepared: true,
      fixture: id,
      output_dir: absoluteOutput,
      next_steps: [
        `cd ${absoluteOutput}`,
        `Get-Content -Raw .fixture-seed-prompt.txt | codex exec --full-auto -`,
        `node ${join(here, "fixture-runner.mjs")} verify ${id} ${absoluteOutput}`,
      ],
    }, null, 2));
  }
}

function readOptional(projectDir, relativePath) {
  const fullPath = join(projectDir, relativePath);
  return existsSync(fullPath) ? readFileSync(fullPath, "utf8") : "";
}

function extractExecutionContract(content) {
  if (!content) return null;
  const shell = content.match(/^- Primary shell:\s*(.+)$/m)?.[1]?.trim();
  const quality = content.match(/^- Primary quality gate command:\s*`([^`]+)`/m)?.[1];
  const docs = content.match(/^- Primary doc validation command:\s*`([^`]+)`/m)?.[1];
  if (!shell || !quality || !docs) return null;
  return { shell, quality, docs };
}

function splitCommandText(commandText) {
  return (commandText.match(/"[^"]*"|'[^']*'|\S+/g) || []).map((token) => {
    if (
      (token.startsWith('"') && token.endsWith('"')) ||
      (token.startsWith("'") && token.endsWith("'"))
    ) {
      return token.slice(1, -1);
    }
    return token;
  });
}

function runProjectCommand(projectDir, commandText, timeout = 120000) {
  const tokens = splitCommandText(commandText);
  if (tokens.length === 0) {
    return {
      command: commandText,
      exit_code: null,
      signal: null,
      timed_out: false,
      stdout: "",
      stderr: "Command text was empty",
      error: { code: "EMPTY_COMMAND", message: "Command text was empty" },
    };
  }

  const [command, ...args] = tokens;
  const result = invokeCommand(command, args, {
    cwd: projectDir,
    encoding: "utf8",
    timeout,
  });

  return {
    command: commandText,
    ...result,
  };
}

function runBaseGrader(projectDir) {
  const graderPath = join(here, "grader.mjs");
  try {
    const stdout = execFileSync(process.execPath, [graderPath, projectDir], {
      encoding: "utf8",
    });
    return JSON.parse(stdout);
  } catch (error) {
    const stdout = error.stdout?.toString() || "";
    if (!stdout) throw error;
    return JSON.parse(stdout);
  }
}

function containsInstructionLeak(content) {
  return /remove the E2E row|Optional gate placeholder|Expand to a full `?(run_gate|Run-Gate)`? block/i.test(content);
}

function buildSeedPrompt(meta) {
  const requiredExistingDocReads = Array.isArray(meta.expectations?.trace_reads_before_generation)
    ? meta.expectations.trace_reads_before_generation
    : [];
  const lines = [
    meta.seed_prompt,
    "",
    "This run is a fixture-based scaffold self-test.",
    "Complete Phase 0 through Phase 4 only, then stop immediately after a short handoff summary.",
    "Do not enter Phase 5 continuous operation for this fixture run.",
    "Once the primary doc validation command and primary quality gate command both pass, do not do extra repo exploration.",
    "Do not gather line-number references, broad file inventories, `git status`, or archive listings for the final summary unless a failing check requires that evidence.",
    "Keep the final handoff compact: generated artifact groups, validation outcome, and remaining feedback gaps only.",
    "",
    "Brainstorming and design approval are already complete for this fixture run.",
    "If a brainstorming-style workflow is available, treat the approved design summary below as already accepted by the user.",
    "Do not open a new design-approval loop unless a safety-critical ambiguity remains unresolved.",
    "Keep context tight: read only the templates and sub-skill files needed for the next concrete generation step.",
    "Do not exhaustively dump every template file before writing if the fixture constraints already determine the scaffold shape.",
    "",
  ];

  if (requiredExistingDocReads.length > 0) {
    lines.push(
      "This fixture contains existing repo docs that must be audited before generation.",
      "Read these repo files before running the scaffold renderer or opening templates:",
      ...requiredExistingDocReads.map((path) => `- ${path}`),
      "If any of them are stale, archive the old version into `docs/archive/` before generating replacements.",
      ""
    );
  }

  lines.push(
    "Renderer-first rule for this fixture run:",
    "- if `.fixture-scaffold-values.json` and `.agents/skills/project-scaffold/tools/render-scaffold.mjs` both exist, invoke the renderer before opening any `.tmpl` files",
    "- treat fixture `template_read_plan` entries as a fallback-only recovery path after a concrete render or validation failure, not as a mandatory pre-render checklist",
    "",
    "Approved design summary:",
    ...meta.approved_design.map((item) => `- ${item}`),
    "Implementation is explicitly approved for this fixture self-test.",
    "",
    "A starting scaffold values file is available at `.fixture-scaffold-values.json`.",
    "Prefer updating that values file and running the renderer over drafting every output file from scratch.",
    "",
    "If `.agents/skills/project-scaffold/tools/render-scaffold.mjs` exists, prefer this flow:",
    "- create one scaffold values JSON file for the agreed placeholders",
    "- run the renderer once to materialize the manifest-declared docs/scripts",
    "- validate and patch only the concrete failures that remain",
    "",
    "Fallback template read plan (use only if the renderer is missing or the first render/validation pass exposes a concrete issue that requires inspecting source templates):",
    ...meta.template_read_plan.map((path) => `- ${path}`),
    "",
    "Generation order for this fixture run:",
    "- actual generated artifacts must match `docs/harness-manifest.json` and the repo's primary shell",
    "- do not materialize both `.ps1` and `.sh` script families unless the manifest explicitly requires both",
    ...meta.generation_order.map((path) => `- ${path}`),
    "",
    "Treat the following as already-provided Phase 1 human alignment answers.",
    "Do not stop to ask these questions again unless a safety-critical ambiguity remains unresolved.",
    "",
    `North Star goal: ${meta.phase1_answers.north_star}`,
    "Success metrics:",
    ...meta.phase1_answers.success_metrics.map((metric) => `- ${metric}`),
    `Hard constraints: ${meta.phase1_answers.hard_constraints}`,
    `Biggest pain point: ${meta.phase1_answers.biggest_pain_point}`,
    `Target architecture style: ${meta.phase1_answers.target_architecture_style}`,
  );

  if (Array.isArray(meta.phase1_answers.additional_notes) && meta.phase1_answers.additional_notes.length > 0) {
    lines.push("", "Additional fixture notes:");
    lines.push(...meta.phase1_answers.additional_notes.map((note) => `- ${note}`));
  }

  lines.push(
    "",
    "If anything is still unknown after using these answers and repo discovery, record the gap in the Needs Manifest instead of blocking the fixture run."
  );

  return lines.join("\n") + "\n";
}

function buildVerificationResult(id, projectDir) {
  const meta = loadFixture(id);
  const absoluteProjectDir = resolve(projectDir);
  const base = runBaseGrader(absoluteProjectDir);
  const agentsMd = readOptional(absoluteProjectDir, "AGENTS.md");
  const projectSpec = readOptional(absoluteProjectDir, "docs/project_spec.md");
  const plan = readOptional(absoluteProjectDir, "docs/plan.md");
  const prompt = readOptional(absoluteProjectDir, "docs/prompt.md");
  const implement = readOptional(absoluteProjectDir, "docs/implement.md");
  const documentation = readOptional(absoluteProjectDir, "docs/documentation.md");
  const feedbackProfile = readOptional(absoluteProjectDir, "docs/feedback-profile.json");
  const qualityGatesSh = readOptional(absoluteProjectDir, "scripts/quality-gates.sh");
  const qualityGatesPs1 = readOptional(absoluteProjectDir, "scripts/quality-gates.ps1");
  const generatedFiles = [projectSpec, plan, feedbackProfile, qualityGatesSh, qualityGatesPs1];
  const executionContract = extractExecutionContract(agentsMd);
  const mechanicalChecks = executionContract
    ? {
        primary_doc_validation: runProjectCommand(absoluteProjectDir, executionContract.docs),
        primary_quality_gates: runProjectCommand(absoluteProjectDir, executionContract.quality),
      }
    : null;

  const checks = [
    {
      id: "base-grader",
      pass: base.overall_pass,
      notes: base.overall_pass ? `Base grader passed with score ${base.score}` : `Base grader failed with score ${base.score}`,
    },
    {
      id: "instruction-leakage",
      pass: generatedFiles.every((content) => !containsInstructionLeak(content)),
      notes: generatedFiles.every((content) => !containsInstructionLeak(content))
        ? "No template-author instruction text leaked into scaffold output"
        : "Template-author instruction text leaked into scaffold output",
    },
    {
      id: "execution-contract-present",
      pass: !!executionContract,
      notes: executionContract
        ? `Execution contract found for ${executionContract.shell}`
        : "Could not extract the primary shell and command pair from AGENTS.md",
    },
  ];

  if (mechanicalChecks) {
    const docValidationPassed = mechanicalChecks.primary_doc_validation.exit_code === 0;
    const qualityGatesPassed = mechanicalChecks.primary_quality_gates.exit_code === 0;

    checks.push({
      id: "primary-doc-validation",
      pass: docValidationPassed,
      notes: docValidationPassed
        ? `Primary doc validation passed: ${mechanicalChecks.primary_doc_validation.command}`
        : `Primary doc validation failed (exit ${mechanicalChecks.primary_doc_validation.exit_code ?? "null"}): ${mechanicalChecks.primary_doc_validation.command}`,
    });

    checks.push({
      id: "primary-quality-gates",
      pass: qualityGatesPassed,
      notes: qualityGatesPassed
        ? `Primary quality gates passed: ${mechanicalChecks.primary_quality_gates.command}`
        : `Primary quality gates failed (exit ${mechanicalChecks.primary_quality_gates.exit_code ?? "null"}): ${mechanicalChecks.primary_quality_gates.command}`,
    });
  }

  const hasBusinessFlowGate = /run_gate\s+"business-flow"\s+"/.test(qualityGatesSh) ||
    /Run-Gate\s+"business-flow"\s+"/.test(qualityGatesPs1);

  if (meta.expectations.e2e_gate === "required") {
    checks.push({
      id: "fixture-e2e-gate",
      pass: hasBusinessFlowGate,
      notes: hasBusinessFlowGate
        ? "User-facing fixture includes a business-flow gate"
        : "User-facing fixture should include a business-flow gate",
    });
  }

  if (meta.expectations.e2e_gate === "forbidden") {
    checks.push({
      id: "fixture-e2e-gate",
      pass: !hasBusinessFlowGate,
      notes: !hasBusinessFlowGate
        ? "Non-user-facing fixture did not invent a business-flow gate"
        : "Non-user-facing fixture should not invent a business-flow gate",
    });
  }

  const canonicalGuidanceContents = [
    agentsMd,
    projectSpec,
    plan,
    prompt,
    implement,
    documentation,
  ].join("\n");
  checks.push(...buildFixtureSpecificChecks(meta, absoluteProjectDir, canonicalGuidanceContents));

  const overallPass = checks.every((check) => check.pass);
  const result = {
    fixture: meta.id,
    category: meta.category,
    overall_pass: overallPass,
    checks,
    base_grader: {
      overall_pass: base.overall_pass,
      score: base.score,
    },
    mechanical_checks: mechanicalChecks,
  };

  return result;
}

function verifyFixture(id, projectDir) {
  const result = buildVerificationResult(id, projectDir);
  const artifactPath = join(resolve(projectDir), ".fixture-verify-result.json");
  result.artifact_path = artifactPath;
  writeJsonArtifact(artifactPath, result);

  console.log(JSON.stringify(result, null, 2));
  process.exit(result.overall_pass ? 0 : 1);
}

function parseRunOptions(args) {
  const options = {
    force: false,
    timeoutMs: 600000,
    runs: 3,
  };

  for (let i = 0; i < args.length; i++) {
    if (args[i] === "--force") {
      options.force = true;
      continue;
    }
    if (args[i] === "--timeout-ms") {
      options.timeoutMs = Number(args[i + 1]);
      i += 1;
    }
    if (args[i] === "--runs") {
      options.runs = Number(args[i + 1]);
      i += 1;
    }
  }

  return options;
}

function quoteForPowerShell(arg) {
  return `'${String(arg).replace(/'/g, "''")}'`;
}

function quoteForCmd(arg) {
  return `"${String(arg).replace(/"/g, '""')}"`;
}

function resolveCodexCommand() {
  if (process.platform !== "win32") return "codex";

  const appData = process.env.APPDATA;
  if (appData) {
    const shimPath = join(appData, "npm", "codex.cmd");
    if (existsSync(shimPath)) return shimPath;
    const ps1ShimPath = join(appData, "npm", "codex.ps1");
    if (existsSync(ps1ShimPath)) return ps1ShimPath;
  }

  return "codex.cmd";
}

function invokeCommand(command, args, options) {
  const result = spawnSync(command, args, {
    cwd: options.cwd,
    input: options.input,
    encoding: options.encoding ?? "utf8",
    timeout: options.timeout,
    maxBuffer: options.maxBuffer ?? 16 * 1024 * 1024,
    windowsHide: true,
    windowsVerbatimArguments: options.windowsVerbatimArguments ?? false,
  });

  const stdout = typeof result.stdout === "string"
    ? result.stdout.trim()
    : result.stdout?.toString().trim() || "";
  const stderr = typeof result.stderr === "string"
    ? result.stderr.trim()
    : result.stderr?.toString().trim() || "";

  return {
    exit_code: result.status ?? null,
    signal: result.signal ?? null,
    timed_out: result.error?.code === "ETIMEDOUT",
    stdout,
    stderr: stderr || result.error?.message || "",
    error: result.error
      ? {
          code: result.error.code,
          message: result.error.message,
        }
      : null,
  };
}

function executeFixtureRun(id, outputDir, options) {
  const meta = loadFixture(id);
  prepareFixture(id, outputDir, options.force, false, options.includeSkill !== false);

  const absoluteOutput = resolve(outputDir);
  const lastMessagePath = join(absoluteOutput, ".codex-last-message.txt");
  const tracePath = join(absoluteOutput, ".codex-trace.jsonl");
  const stderrPath = join(absoluteOutput, ".codex-stderr.log");
  const seedPrompt = buildSeedPrompt(meta);
  const seedPromptPath = join(absoluteOutput, ".fixture-seed-prompt.txt");
  const codexArgs = [
    "exec",
    "--full-auto",
    "--json",
    "--skip-git-repo-check",
    "-C",
    absoluteOutput,
    "--output-last-message",
    lastMessagePath,
    "-",
  ];
  const codexCommand = resolveCodexCommand();
  const spawnCommand = process.platform === "win32" ? "cmd.exe" : codexCommand;
  const spawnArgs = process.platform === "win32"
    ? [
        "/d",
        "/s",
        "/c",
        `"${quoteForCmd(codexCommand)} exec --full-auto --json --skip-git-repo-check -C ${quoteForCmd(absoluteOutput)} --output-last-message ${quoteForCmd(lastMessagePath)} - < ${quoteForCmd(seedPromptPath)} > ${quoteForCmd(tracePath)} 2> ${quoteForCmd(stderrPath)}"`,
      ]
    : codexArgs;

  const codex = invokeCommand(spawnCommand, spawnArgs, {
    cwd: repoRoot,
    encoding: "utf8",
    input: process.platform === "win32" ? undefined : seedPrompt,
    timeout: options.timeoutMs,
    windowsVerbatimArguments: process.platform === "win32",
  });
  if (process.platform !== "win32") {
    const traceContent = codex.stdout || codex.stderr || "";
    if (traceContent) {
      writeFileSync(tracePath, traceContent + (traceContent.endsWith("\n") ? "" : "\n"));
    }
  }
  const codexResult = {
    exit_code: codex.exit_code,
    signal: codex.signal,
    timed_out: codex.timed_out,
    stdout: codex.stdout,
    stderr: existsSync(stderrPath) ? readFileSync(stderrPath, "utf8").trim() : codex.stderr,
    last_message_path: existsSync(lastMessagePath) ? lastMessagePath : null,
    trace_path: existsSync(tracePath) ? tracePath : null,
    stderr_path: existsSync(stderrPath) ? stderrPath : null,
    command: process.platform === "win32"
      ? `${spawnCommand} ${spawnArgs.join(" ")}`
      : `${codexCommand} ${codexArgs.join(" ")}`,
    error: codex.error,
  };

  const verification = buildVerificationResult(id, absoluteOutput);
  const result = {
    fixture: id,
    output_dir: absoluteOutput,
    codex: codexResult,
    verification,
    overall_pass: verification.overall_pass && codex.exit_code === 0,
  };
  const artifactPath = join(absoluteOutput, ".fixture-run-result.json");
  result.artifact_path = artifactPath;
  writeJsonArtifact(artifactPath, result);

  return result;
}

function runFixture(id, outputDir, options) {
  const result = executeFixtureRun(id, outputDir, options);

  console.log(JSON.stringify(result, null, 2));
  process.exit(result.overall_pass ? 0 : 1);
}

function repeatFixture(id, outputDirPrefix, options) {
  const results = [];
  for (let runIndex = 1; runIndex <= options.runs; runIndex++) {
    const runOutputDir = `${outputDirPrefix}-${runIndex}`;
    const result = executeFixtureRun(id, runOutputDir, { ...options, force: true });
    results.push(result);
  }

  const baseScores = results
    .map((result) => result.verification.base_grader.score)
    .filter((score) => typeof score === "number");
  const passCount = results.filter((result) => result.overall_pass).length;
  const summary = {
    fixture: id,
    runs: options.runs,
    pass_count: passCount,
    pass_rate: options.runs === 0 ? 0 : passCount / options.runs,
    base_grader_scores: baseScores,
    min_score: baseScores.length ? Math.min(...baseScores) : null,
    max_score: baseScores.length ? Math.max(...baseScores) : null,
    results,
  };
  const summaryPath = `${outputDirPrefix}-summary.json`;
  summary.summary_path = resolve(summaryPath);
  writeJsonArtifact(summaryPath, summary);

  console.log(JSON.stringify(summary, null, 2));
  process.exit(passCount === options.runs ? 0 : 1);
}

function ablateFixture(id, outputDir, options) {
  const result = executeFixtureRun(id, outputDir, { ...options, force: true, includeSkill: false });
  const summary = {
    fixture: id,
    output_dir: resolve(outputDir),
    skill_installed: false,
    verification: result.verification,
    codex: result.codex,
    still_passes_without_skill: result.overall_pass,
    skill_still_needed: !result.overall_pass,
  };
  const artifactPath = join(resolve(outputDir), ".fixture-ablation-result.json");
  summary.artifact_path = artifactPath;
  writeJsonArtifact(artifactPath, summary);

  console.log(JSON.stringify(summary, null, 2));
  process.exit(summary.skill_still_needed ? 0 : 1);
}

function extractComparableResult(payload) {
  const candidate = payload?.verification?.checks ? payload.verification : payload;
  const checks = Array.isArray(candidate?.checks) ? candidate.checks : [];
  const normalizedChecks = checks
    .filter((check) => check && typeof check.id === "string")
    .map((check) => ({
      id: check.id,
      pass: Boolean(check.pass),
      notes: typeof check.notes === "string" ? check.notes : "",
      weight: typeof check.weight === "number" ? check.weight : null,
    }));

  const score = typeof candidate?.score === "number"
    ? candidate.score
    : typeof payload?.verification?.base_grader?.score === "number"
      ? payload.verification.base_grader.score
      : null;

  const overallPass = typeof candidate?.overall_pass === "boolean"
    ? candidate.overall_pass
    : typeof payload?.overall_pass === "boolean"
      ? payload.overall_pass
      : null;

  return {
    overall_pass: overallPass,
    score,
    checks: normalizedChecks,
  };
}

function compareFixtureResults(baselinePath, currentPath) {
  const baseline = extractComparableResult(readJsonArtifact(baselinePath));
  const current = extractComparableResult(readJsonArtifact(currentPath));

  const baselineChecks = new Map(baseline.checks.map((check) => [check.id, check]));
  const currentChecks = new Map(current.checks.map((check) => [check.id, check]));
  const sharedIds = [...baselineChecks.keys()].filter((id) => currentChecks.has(id)).sort();

  const regressions = [];
  const improvements = [];
  for (const id of sharedIds) {
    const before = baselineChecks.get(id);
    const after = currentChecks.get(id);
    if (before.pass && !after.pass) {
      regressions.push({
        id,
        baseline_pass: before.pass,
        current_pass: after.pass,
        baseline_notes: before.notes,
        current_notes: after.notes,
      });
    }
    if (!before.pass && after.pass) {
      improvements.push({
        id,
        baseline_pass: before.pass,
        current_pass: after.pass,
        baseline_notes: before.notes,
        current_notes: after.notes,
      });
    }
  }

  const added_checks = [...currentChecks.keys()].filter((id) => !baselineChecks.has(id)).sort();
  const removed_checks = [...baselineChecks.keys()].filter((id) => !currentChecks.has(id)).sort();
  const score_delta = typeof baseline.score === "number" && typeof current.score === "number"
    ? current.score - baseline.score
    : null;
  const overall_regression = baseline.overall_pass === true && current.overall_pass === false;

  const result = {
    baseline: {
      path: resolve(baselinePath),
      overall_pass: baseline.overall_pass,
      score: baseline.score,
      check_count: baseline.checks.length,
    },
    current: {
      path: resolve(currentPath),
      overall_pass: current.overall_pass,
      score: current.score,
      check_count: current.checks.length,
    },
    overall_regression,
    score_delta,
    regressions,
    improvements,
    added_checks,
    removed_checks,
  };

  console.log(JSON.stringify(result, null, 2));
  process.exit(overall_regression || regressions.length > 0 ? 1 : 0);
}

const [, , command, arg1, arg2, ...rest] = process.argv;

try {
  if (command === "list") {
    listFixtures();
  } else if (command === "prepare") {
    if (!arg1 || !arg2) {
      usage();
      process.exit(1);
    }
    prepareFixture(arg1, arg2, rest.includes("--force"));
  } else if (command === "verify") {
    if (!arg1 || !arg2) {
      usage();
      process.exit(1);
    }
    verifyFixture(arg1, arg2);
  } else if (command === "run") {
    if (!arg1 || !arg2) {
      usage();
      process.exit(1);
    }
    runFixture(arg1, arg2, parseRunOptions(rest));
  } else if (command === "repeat") {
    if (!arg1 || !arg2) {
      usage();
      process.exit(1);
    }
    repeatFixture(arg1, arg2, parseRunOptions(rest));
  } else if (command === "ablate") {
    if (!arg1 || !arg2) {
      usage();
      process.exit(1);
    }
    ablateFixture(arg1, arg2, parseRunOptions(rest));
  } else if (command === "compare") {
    if (!arg1 || !arg2) {
      usage();
      process.exit(1);
    }
    compareFixtureResults(arg1, arg2);
  } else {
    usage();
    process.exit(1);
  }
} catch (error) {
  console.error(error.message);
  process.exit(1);
}
