#!/usr/bin/env tsx
/**
 * U8 — per-slug migration status for the bundled skill catalog.
 *
 * Complements U1's pre-migration census with a narrower question: "for
 * each catalog slug, what's the state of its U8 migration, and what's
 * the next step?"
 *
 * Post-U6 the legacy composition + declarative shapes are gone — this
 * script is kept as a drift tripwire: if a slug ever regresses to a
 * removed execution type the census surfaces it under `regressed`
 * rather than silently shipping a broken catalog entry.
 *
 * Source of truth (post plan 2026-04-24-009 §U2): SKILL.md frontmatter.
 * The `skill.yaml` files were retired; this script now reads through
 * U1's `parseSkillMdInternal` for the `execution` / `mode` / `steps`
 * signals.
 *
 * Migration states:
 *   - done              — execution is `script` or `context`, OR the
 *                         skill declares `contract: system` (system
 *                         contracts are platform-resident behavioral
 *                         rules loaded by the system_contract_loader,
 *                         not invocable skills — they don't go through
 *                         the script/context dispatch path so they have
 *                         no `execution` field to migrate).
 *   - regressed         — frontmatter declares an execution value the
 *                         runtime no longer handles (anything outside
 *                         `script` | `context`). Post-U6 this must
 *                         stay at zero.
 *   - unknown           — SKILL.md missing or frontmatter malformed.
 *
 * Usage:
 *   tsx packages/skill-catalog/scripts/u8-status.ts
 *
 * Write to a file:
 *   tsx packages/skill-catalog/scripts/u8-status.ts > docs/plans/u8-status.md
 */

import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { join, resolve } from "node:path";

import { parseSkillMdInternal } from "../../api/src/lib/skill-md-parser.js";

const SUPPORTED_EXECUTIONS = new Set<string>(["script", "context"]);

type MigrationState = "done" | "regressed" | "unknown";

interface SlugStatus {
  slug: string;
  state: MigrationState;
  execution: string;
  mode: string;
  hasScriptsDir: boolean;
  hasSkillMd: boolean;
  stepCount: number;
  note?: string;
}

function catalogRoot(): string {
  const hereUrl = new URL(import.meta.url).pathname;
  // scripts/u8-status.ts → up two to packages/skill-catalog
  return resolve(hereUrl, "..", "..");
}

function listSlugDirs(catalog: string): string[] {
  return readdirSync(catalog, { withFileTypes: true })
    .filter((e) => e.isDirectory())
    .map((e) => e.name)
    .filter((name) => !name.startsWith("_") && !name.startsWith("."))
    .filter((name) => name !== "node_modules" && name !== "dist")
    .filter((name) => name !== "scripts" && name !== "tests")
    .filter((name) => name !== "__tests__" && name !== "characterization")
    .sort();
}

function inspect(slug: string, catalog: string): SlugStatus {
  const dir = join(catalog, slug);
  const skillMdPath = join(dir, "SKILL.md");
  const scriptsDir = join(dir, "scripts");
  const hasScriptsDir =
    existsSync(scriptsDir) && statSync(scriptsDir).isDirectory();
  const hasSkillMd = existsSync(skillMdPath);

  if (!hasSkillMd) {
    return {
      slug,
      state: "unknown",
      execution: "",
      mode: "",
      hasScriptsDir,
      hasSkillMd,
      stepCount: 0,
      note: "no SKILL.md",
    };
  }

  const result = parseSkillMdInternal(readFileSync(skillMdPath, "utf8"), skillMdPath);
  if (!result.valid) {
    return {
      slug,
      state: "unknown",
      execution: "",
      mode: "",
      hasScriptsDir,
      hasSkillMd,
      stepCount: 0,
      note: `parse failed: ${result.errors.map((e) => e.message).join("; ")}`,
    };
  }

  const fm = result.parsed.data;
  const execution = typeof fm.execution === "string" ? fm.execution : "";
  const mode = typeof fm.mode === "string" ? fm.mode : "";
  const contract = typeof fm.contract === "string" ? fm.contract : "";
  const steps = Array.isArray(fm.steps) ? (fm.steps as unknown[]) : [];

  let state: MigrationState;
  let note: string | undefined;
  if (contract === "system") {
    // System contracts (loaded by packages/agentcore-strands/.../
    // system_contract_loader.py per plan 2026-05-21-004) live in the
    // catalog but don't go through script/context dispatch. No
    // execution field to migrate.
    state = "done";
    note = "system contract";
  } else if (!execution) {
    state = "unknown";
  } else if (SUPPORTED_EXECUTIONS.has(execution)) {
    state = "done";
  } else {
    state = "regressed";
  }

  return {
    slug,
    state,
    execution: execution || (contract === "system" ? "(contract)" : ""),
    mode,
    hasScriptsDir,
    hasSkillMd,
    stepCount: steps.length,
    note,
  };
}

function render(statuses: SlugStatus[]): string {
  const byState: Record<MigrationState, SlugStatus[]> = {
    done: [],
    regressed: [],
    unknown: [],
  };
  for (const s of statuses) byState[s.state].push(s);

  const lines: string[] = [];
  lines.push("# U8 — Skill catalog migration status\n");
  lines.push(
    "Generated by `tsx packages/skill-catalog/scripts/u8-status.ts`.\n",
  );
  lines.push("## Summary\n");
  lines.push("| State | Count |");
  lines.push("| --- | ---: |");
  lines.push(`| done | ${byState.done.length} |`);
  lines.push(`| regressed | ${byState.regressed.length} |`);
  lines.push(`| unknown | ${byState.unknown.length} |`);
  lines.push("");
  lines.push("## Per-slug detail\n");
  lines.push(
    "| Slug | State | execution | mode | scripts/ | SKILL.md | steps |",
  );
  lines.push("| --- | --- | --- | --- | ---: | ---: | ---: |");
  for (const s of statuses) {
    lines.push(
      `| ${s.slug} | ${s.state} | ${s.execution || "—"} | ${s.mode || "—"} | ${s.hasScriptsDir ? "yes" : "no"} | ${s.hasSkillMd ? "yes" : "no"} | ${s.stepCount} |`,
    );
  }
  lines.push("");
  lines.push("## Migration guidance\n");
  lines.push(
    "- **regressed** slugs → a catalog entry declared an execution type " +
      "the runtime no longer supports (anything outside `script` | " +
      "`context`). Post-U6 this count must stay at zero — CI should " +
      "treat a non-empty bucket as a failure.",
  );
  lines.push(
    "- **unknown** → SKILL.md missing or frontmatter malformed; fix the file.",
  );
  lines.push(
    "- **done** → no action needed; the runtime knows how to dispatch.",
  );
  lines.push("");
  return lines.join("\n");
}

function main(): void {
  const catalog = catalogRoot();
  const slugs = listSlugDirs(catalog);
  const statuses = slugs.map((slug) => inspect(slug, catalog));
  process.stdout.write(render(statuses));
}

main();
