#!/usr/bin/env bash
#
# Claude Code Kit — Cross-tool exporter
#
# Exports the kit's CLAUDE.md DISCIPLINE (Plan → Confirm → Implement → Verify,
# Protected Changes, conventions) plus the review agents to other tools' native
# rule formats, written to the locations each tool actually reads:
#
#   cursor    → .cursor/rules/claude-code-kit.mdc  (alwaysApply: true — the discipline)
#               .cursor/rules/<agent>.mdc          (agent-requested — description-driven)
#   windsurf  → .windsurf/rules/claude-code-kit.md (trigger: always_on — the discipline)
#               .windsurf/rules/<agent>.md         (trigger: model_decision)
#   aider     → CONVENTIONS.md                      (the discipline + agents)
#               .aider.conf.yml                     (read: [CONVENTIONS.md] — so Aider loads it)
#   agents-md → AGENTS.md                           (universal standard, via gen-agents-md.sh)
#   skills    → .agents/skills/<name>/SKILL.md       (the cross-tool Agent Skills location
#                                                     that Codex, Zed, and Amp all read)
#   codex     → AGENTS.md (discipline as rules) + .agents/skills (skills)
#
# And one INBOUND, one-time migration:
#
#   import    → tasks/imported-rules.md  (collect rules a project already has from
#               Cursor/Windsurf/Copilot/Aider/GEMINI/AGENTS.md into one staging
#               file to review and curate into CLAUDE.project.md — never auto-edits
#               the overlay; skips the kit's own generated exports)
#
# The always-on rule carries the CORE kit discipline (compact, under per-tool rule
# size caps); richer project context lives in the generated AGENTS.md, which these
# tools also read natively. Single source of truth: gen-agents-md.sh output.
# All outputs are generated; regenerate, don't hand-edit.
#
# NOTE on Codex hooks: the kit's deterministic hook layer does NOT port to Codex.
# Codex repo-local hooks are unreliable (openai/codex#17532) and the kit's hooks
# parse Claude Code's hook-JSON schema, which Codex does not emit. The discipline
# those hooks enforce still reaches Codex as AGENTS.md rules — only the automatic
# enforcement is Claude-Code-only.
#
# Usage:
#   ./scripts/convert.sh            # all targets (cursor, windsurf, aider, skills, AGENTS.md)
#   ./scripts/convert.sh cursor     # one export target: cursor|windsurf|aider|agents-md|skills|codex
#   ./scripts/convert.sh import     # one-time: import other tools' rules → tasks/imported-rules.md
#

set -euo pipefail

AGENTS_DIR=".claude/agents"
SKILLS_DIR=".claude/skills"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
GEN_NOTE="<!-- Generated by claude-code-kit (scripts/convert.sh) from CLAUDE.md — do not edit; regenerate. -->"
IMPORT_NOTE="<!-- Generated by claude-code-kit (scripts/convert.sh import) — review, then curate into CLAUDE.project.md and delete this file. -->"

# --- Helpers ---

# Extract a YAML frontmatter field value
get_field() {
  local file="$1" field="$2"
  awk -v field="$field" '
    /^---$/ { fm++; next }
    fm == 1 && $0 ~ "^" field ":" { sub("^" field ":[ ]*", ""); print; exit }
    fm >= 2 { exit }
  ' "$file"
}

# Extract body content (everything after frontmatter)
get_body() {
  awk '/^---$/ { fm++; next } fm >= 2 { print }' "$1"
}

# Strip a leading YAML frontmatter block if present; otherwise emit the file as-is.
# (get_body assumes frontmatter exists — many foreign rule files have none.)
#
# A leading '---' is treated as frontmatter ONLY when a closing '---' exists below
# it AND the block contains a 'key:' line. A plain-markdown file that merely opens
# with a '---' thematic break has neither a closing fence nor a key, so it is
# emitted verbatim — never silently dropped or truncated.
strip_frontmatter() {
  local f="$1"
  if [ "$(sed -n '1p' "$f")" = "---" ] && awk '
        NR==1 { next }
        /^---$/ { found=1; exit }
        /^[[:space:]]*[A-Za-z0-9_.-]+[[:space:]]*:/ { haskey=1 }
        END { exit (found && haskey) ? 0 : 1 }
      ' "$f"; then
    awk 'NR==1 && $0=="---" {infm=1; next}
         infm==1 && $0=="---" {infm=0; next}
         infm==1 {next}
         {print}' "$f"
  else
    cat "$f"
  fi
}

# Remove our previously-generated rules in a dir (by marker) so removed/renamed
# agents don't linger as ghosts. User-authored files (without the marker) are kept.
sweep_generated() {
  local dir="$1" ext="$2" f
  for f in "$dir"/*."$ext"; do
    [ -f "$f" ] || continue
    if grep -q -- "$GEN_NOTE" "$f" 2>/dev/null; then rm -f "$f"; fi
  done
}

# The CORE kit discipline = the stable Workflow / Protected Changes / Code
# Conventions sections of the generated AGENTS.md. Compact (under per-tool rule
# caps) and single-source (no drift). build_discipline also (re)freshes AGENTS.md
# itself — the universal cross-tool file.
DISCIPLINE=""
build_discipline() {
  "$SCRIPT_DIR/gen-agents-md.sh" . >/dev/null
  DISCIPLINE=$(awk '
    /^## (Workflow|Protected Changes|Code Conventions)/ { p=1; print; next }
    /^## / { p=0 }
    p { print }
  ' AGENTS.md)
}

# --- Converters ---

convert_cursor() {
  local outdir=".cursor/rules"
  mkdir -p "$outdir"
  sweep_generated "$outdir" mdc

  # 1. Always-on discipline rule (fixes the old never-firing globs:+alwaysApply:false)
  {
    printf '%s\n' '---'
    printf '%s\n' 'description: Claude Code Kit — core engineering discipline (Plan → Confirm → Implement → Verify)'
    printf '%s\n' 'alwaysApply: true'
    printf '%s\n\n' '---'
    printf '%s\n\n' "$GEN_NOTE"
    printf '%s\n' "$DISCIPLINE"
  } > "$outdir/claude-code-kit.mdc"
  echo "  Cursor: $outdir/claude-code-kit.mdc (alwaysApply)"

  # 2. Per-agent rules — Agent-Requested (alwaysApply:false + description, no globs)
  for agent in "$AGENTS_DIR"/*.md; do
    [ -f "$agent" ] || continue
    local name desc body
    name=$(get_field "$agent" "name"); [ -z "$name" ] && name=$(basename "$agent" .md)
    desc=$(get_field "$agent" "description")
    body=$(get_body "$agent")
    {
      printf '%s\n' '---'
      printf 'description: %s\n' "${desc:-Claude Code Kit agent: $name}"
      printf '%s\n' 'alwaysApply: false'
      printf '%s\n\n' '---'
      printf '%s\n\n' "$GEN_NOTE"
      printf '%s\n' "$body"
    } > "$outdir/${name}.mdc"
    echo "  Cursor: $outdir/${name}.mdc (agent-requested)"
  done
}

convert_windsurf() {
  local outdir=".windsurf/rules"
  mkdir -p "$outdir"
  sweep_generated "$outdir" md

  # 1. Always-on discipline rule (replaces the deprecated single .windsurfrules)
  {
    printf '%s\n' '---' 'trigger: always_on' '---'
    printf '\n%s\n\n' "$GEN_NOTE"
    printf '%s\n' "$DISCIPLINE"
  } > "$outdir/claude-code-kit.md"
  echo "  Windsurf: $outdir/claude-code-kit.md (always_on)"

  # 2. Per-agent rules — model_decision (Windsurf pulls when relevant)
  for agent in "$AGENTS_DIR"/*.md; do
    [ -f "$agent" ] || continue
    local name desc body
    name=$(get_field "$agent" "name"); [ -z "$name" ] && name=$(basename "$agent" .md)
    desc=$(get_field "$agent" "description")
    body=$(get_body "$agent")
    {
      printf '%s\n' '---' 'trigger: model_decision'
      printf 'description: %s\n' "${desc:-Claude Code Kit agent: $name}"
      printf '%s\n\n' '---'
      printf '%s\n\n' "$GEN_NOTE"
      printf '%s\n' "$body"
    } > "$outdir/${name}.md"
    echo "  Windsurf: $outdir/${name}.md (model_decision)"
  done
}

convert_aider() {
  # 1. CONVENTIONS.md — carries the discipline (+ agents), the thing Aider reads.
  #    Single wholesale-regenerated file (self-heals on agent removal).
  {
    printf '# Conventions — Claude Code Kit\n\n'
    printf '%s\n\n' "$GEN_NOTE"
    printf '%s\n' "$DISCIPLINE"
    printf '\n\n## Review Agents\n'
    for agent in "$AGENTS_DIR"/*.md; do
      [ -f "$agent" ] || continue
      local name body
      name=$(get_field "$agent" "name"); [ -z "$name" ] && name=$(basename "$agent" .md)
      body=$(get_body "$agent")
      printf '\n### %s\n\n%s\n' "$name" "$body"
    done
  } > CONVENTIONS.md
  echo "  Aider: CONVENTIONS.md (discipline + agents)"

  # 2. .aider.conf.yml so Aider actually loads CONVENTIONS.md (never did before).
  #    Don't clobber an existing user config — only create it, else advise.
  if [ ! -f .aider.conf.yml ]; then
    {
      printf '# Generated by claude-code-kit (scripts/convert.sh aider)\n'
      printf '# Loads the kit conventions/discipline into every Aider session.\n'
      printf 'read: [CONVENTIONS.md]\n'
    } > .aider.conf.yml
    echo "  Aider: .aider.conf.yml (read: [CONVENTIONS.md])"
  else
    echo "  Aider: .aider.conf.yml exists — ensure it contains: read: [CONVENTIONS.md]"
  fi
}

# Export the kit's skills to the cross-tool Agent Skills location, .agents/skills/.
# Codex (.agents/skills or ~/.codex/skills), Zed (.agents/skills), and Amp all read
# this layout. We drop the Claude-specific `user-invocable:` frontmatter key and
# keep name/description (the cross-tool spec). Previously-generated skill dirs are
# swept by marker (.cck-generated) so removed/renamed skills don't linger;
# user-authored skills in .agents/skills (without the marker) are left untouched.
convert_skills() {
  local outdir=".agents/skills"
  mkdir -p "$outdir"

  local d
  for d in "$outdir"/*/; do
    [ -d "$d" ] || continue
    [ -f "${d}.cck-generated" ] && rm -rf "$d"
  done

  local count=0 skill name dest
  for skill in "$SKILLS_DIR"/*/; do
    [ -f "${skill}SKILL.md" ] || continue
    name=$(basename "$skill")
    case "$name" in _*) continue ;; esac   # skip _shared / _templates meta dirs
    dest="$outdir/$name"
    mkdir -p "$dest"
    cp -R "$skill". "$dest/"                # mirror SKILL.md + any resource files/subdirs
    rm -f "$dest/.cck-generated"            # drop a stale marker if the source ever had one
    # Strip the Claude-specific user-invocable key from the exported SKILL.md.
    grep -v '^user-invocable:' "$dest/SKILL.md" > "$dest/SKILL.md.tmp" && mv "$dest/SKILL.md.tmp" "$dest/SKILL.md"
    : > "$dest/.cck-generated"
    count=$((count + 1))
  done
  echo "  Skills: $outdir/ ($count skills — read by Codex, Zed, Amp)"
}

# Codex target: export the pieces that actually reach Codex — the discipline as
# AGENTS.md rules and the skills under .agents/skills. The hook layer does not
# port (see the header NOTE), so we say so plainly rather than ship a no-op.
convert_codex() {
  build_discipline   # refreshes AGENTS.md (Codex reads it natively)
  convert_skills
  echo "  Codex: AGENTS.md refreshed (discipline as rules)"
  echo "  Codex: hooks NOT ported — different input schema + repo-local bug openai/codex#17532."
  echo "         The discipline survives as AGENTS.md rules; deterministic enforcement is Claude-Code-only."
}

# One-time inbound import: collect rules a project already has from OTHER tools and
# stage them for review. The kit's own exports (carrying a generated marker) are
# skipped so import never re-ingests what `convert` wrote. Output is a staging
# file the user curates into CLAUDE.project.md — we never auto-edit the overlay.
convert_import() {
  local out="tasks/imported-rules.md"
  local tmp; tmp=$(mktemp)
  trap 'rm -f "$tmp"' RETURN   # never orphan the temp, even on an unexpected abort
  local found=0 f body
  {
    printf '%s\n\n' "$IMPORT_NOTE"
    printf '# Imported rules — review, then curate into CLAUDE.project.md\n\n'
    printf 'These were imported from other tools by `scripts/convert.sh import`. They are **not active yet** — Claude Code does not read this file. Move the rules worth keeping into `CLAUDE.project.md` (the kit reads it after CLAUDE.md), then delete the original tool files and this one. Provenance is noted per block.\n'
  } > "$tmp"

  # Candidate sources: fixed files + globbed rule dirs from other tools.
  local candidates=()
  local fixed=(.cursorrules .windsurfrules .github/copilot-instructions.md CONVENTIONS.md GEMINI.md AGENTS.md)
  for f in "${fixed[@]}"; do [ -f "$f" ] && candidates+=("$f"); done
  for f in .cursor/rules/*.mdc; do [ -f "$f" ] && candidates+=("$f"); done
  for f in .windsurf/rules/*.md; do [ -f "$f" ] && candidates+=("$f"); done

  if [ "${#candidates[@]}" -gt 0 ]; then
    for f in "${candidates[@]}"; do
      [ -s "$f" ] || continue
      if grep -qE 'Generated by claude-code-kit|GENERATED FILE — do not edit directly' "$f"; then
        echo "  skip   $f (kit-generated export)"; continue
      fi
      body=$(strip_frontmatter "$f")
      if [ -z "$(printf '%s' "$body" | tr -d '[:space:]')" ]; then
        echo "  skip   $f (empty after frontmatter)"; continue
      fi
      printf '\n---\n\n## From `%s`\n\n%s\n' "$f" "$body" >> "$tmp"
      echo "  import $f"
      found=$((found + 1))
    done
  fi

  if [ "$found" -eq 0 ]; then
    echo "  No foreign rule files found (looked for Cursor, Windsurf, Copilot, Aider/CONVENTIONS, GEMINI, AGENTS.md)."
    rm -f "$tmp"
    return 0
  fi

  mkdir -p tasks
  mv "$tmp" "$out"
  echo ""
  echo "  Imported $found source(s) → $out"
  echo "  Next: review it, move the rules you want into CLAUDE.project.md, then delete the originals + $out."
}

# --- Main ---

echo ""
echo "  Claude Code Kit — Cross-tool exporter"
echo "  ====================================="
echo ""

if [ ! -d "$AGENTS_DIR" ] || [ -z "$(ls -A "$AGENTS_DIR"/*.md 2>/dev/null)" ]; then
  echo "  No agent files found in $AGENTS_DIR/"
  exit 1
fi
if [ ! -f CLAUDE.md ]; then
  echo "  CLAUDE.md not found — run convert from the project root (the kit must be installed)."
  exit 1
fi

TARGET="${1:-all}"

case "$TARGET" in
  agents-md)
    "$SCRIPT_DIR/gen-agents-md.sh" .
    ;;
  skills)
    convert_skills
    ;;
  codex)
    convert_codex
    ;;
  import)
    convert_import
    ;;
  cursor|windsurf|aider|all)
    build_discipline   # generates/refreshes AGENTS.md + caches the core discipline
    case "$TARGET" in
      cursor)   convert_cursor ;;
      windsurf) convert_windsurf ;;
      aider)    convert_aider ;;
      all)      convert_cursor; convert_windsurf; convert_aider; convert_skills
                echo "  AGENTS.md: refreshed (universal standard)" ;;
    esac
    ;;
  *)
    echo "  Unknown target: $TARGET"
    echo "  Usage: $0 [cursor|windsurf|aider|agents-md|skills|codex|all] | import"
    exit 1
    ;;
esac

echo ""
echo "  Done! Rules written to each tool's native location."
echo ""
