#!/bin/bash
set -e

REPO="FreePeak/LeanKG"
BINARY_NAME="leankg"
INSTALL_DIR="$HOME/.local/bin"
GITHUB_RAW="https://raw.githubusercontent.com/$REPO/main"
GITHUB_API="https://api.github.com/repos/$REPO/releases/latest"

INSTRUCTIONS_DIR="${GITHUB_RAW}/instructions"

CLAUDE_TEMPLATE_URL="${INSTRUCTIONS_DIR}/claude-template.md"
AGENTS_TEMPLATE_URL="${INSTRUCTIONS_DIR}/agents-template.md"

usage() {
    cat <<EOF
LeanKG Installer/Updater

Usage: curl -fsSL $GITHUB_RAW/scripts/install.sh | bash -s -- <command>

Commands:
  opencode      Install and configure LeanKG for OpenCode AI
  cursor        Install and configure LeanKG for Cursor AI
  claude        Install and configure LeanKG for Claude Code/Desktop
  gemini        Install and configure LeanKG for Gemini CLI
  kilo          Install and configure LeanKG for Kilo Code
  antigravity   Install and configure LeanKG for Anti Gravity
  update        Update LeanKG to the latest version
  version       Show installed and latest available version

Examples:
  curl -fsSL $GITHUB_RAW/scripts/install.sh | bash -s -- opencode
  curl -fsSL $GITHUB_RAW/scripts/install.sh | bash -s -- update
  curl -fsSL $GITHUB_RAW/scripts/install.sh | bash -s -- version
EOF
}

detect_platform() {
    local platform
    local arch

    case "$(uname -s)" in
        Darwin*)
            platform="macos"
            ;;
        Linux*)
            platform="linux"
            ;;
        *)
            echo "Unsupported platform: $(uname -s)" >&2
            exit 1
            ;;
    esac

    case "$(uname -m)" in
        x86_64)
            arch="x64"
            ;;
        arm64|aarch64)
            arch="arm64"
            ;;
        *)
            echo "Unsupported architecture: $(uname -m)" >&2
            exit 1
            ;;
    esac

    echo "${platform}-${arch}"
}

get_download_url() {
    local platform="$1"
    local version="$2"
    echo "https://github.com/$REPO/releases/download/v${version}/${BINARY_NAME}-${platform}.tar.gz"
}

get_installed_version() {
    local binary_path="${INSTALL_DIR}/${BINARY_NAME}"
    if [ -x "$binary_path" ]; then
        "$binary_path" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown"
    else
        echo "not installed"
    fi
}

get_latest_version() {
    curl -fsSL "$GITHUB_API" | grep -o '"tag_name": "[^"]*' | cut -d'"' -f4 | sed 's/v//'
}

check_for_updates() {
    local installed="$1"
    local latest="$2"

    if [ "$installed" = "not installed" ]; then
        echo "not installed"
        return 1
    fi

    if [ "$installed" != "$latest" ]; then
        echo "update available: $installed -> $latest"
        return 1
    else
        echo "up to date ($installed)"
        return 0
    fi
}

show_version() {
    local installed latest
    installed=$(get_installed_version)
    latest=$(get_latest_version)

    echo "LeanKG Version Check"
    echo "-------------------"
    echo "Installed: $installed"
    echo "Latest:    $latest"

    if [ "$installed" != "$latest" ] && [ "$installed" != "not installed" ]; then
        echo ""
        echo "A new version is available!"
        echo "Run 'curl -fsSL $GITHUB_RAW/scripts/install.sh | bash -s -- update' to upgrade."
    fi
}

update_binary() {
    local platform="$1"
    local installed latest

    installed=$(get_installed_version)
    latest=$(get_latest_version)

    echo "Checking for updates..."
    echo "Current: $installed"
    echo "Latest:  $latest"

    if [ "$installed" = "$latest" ]; then
        echo ""
        echo "You already have the latest version ($latest)."
        return 0
    fi

    echo ""
    echo "Stopping any running LeanKG processes..."
    pkill -f "leankg" 2>/dev/null || true
    sleep 1

    echo "Updating LeanKG for ${platform}..."

    local url
    url=$(get_download_url "$platform" "$latest")

    echo "Downloading from $url..."

    local tmp_dir
    tmp_dir=$(mktemp -d)
    local tar_path="$tmp_dir/binary.tar.gz"

    cleanup() {
        rm -rf "$tmp_dir"
    }
    trap cleanup EXIT

    curl -fsSL -o "$tar_path" "$url"

    mkdir -p "$INSTALL_DIR"

    # Remove existing binary first to avoid APFS metadata corruption issues
    # (overwriting in place can leave corrupted metadata, causing SIGKILL on exec)
    rm -f "${INSTALL_DIR}/${BINARY_NAME}"

    tar -xzf "$tar_path" -C "$INSTALL_DIR"
    chmod +x "${INSTALL_DIR}/${BINARY_NAME}"

    echo ""
    echo "Updated to v$latest"
    echo "Installed to ${INSTALL_DIR}/${BINARY_NAME}"
}

install_binary() {
    local platform="$1"
    local install_type="$2"

    local installed latest
    installed=$(get_installed_version)
    latest=$(get_latest_version)

    if [ "$installed" = "$latest" ]; then
        echo "LeanKG v$latest is already installed."
        return 0
    fi

    echo "Installing LeanKG for ${platform}..."

    local url
    url=$(get_download_url "$platform" "$latest")

    echo "Downloading v$latest from $url..."

    local tmp_dir
    tmp_dir=$(mktemp -d)
    local tar_path="$tmp_dir/binary.tar.gz"

    cleanup() {
        rm -rf "$tmp_dir"
    }
    trap cleanup EXIT

    curl -fsSL -o "$tar_path" "$url"

    mkdir -p "$INSTALL_DIR"

    # Remove existing binary first to avoid APFS metadata corruption issues
    rm -f "${INSTALL_DIR}/${BINARY_NAME}"

    tar -xzf "$tar_path" -C "$INSTALL_DIR"
    chmod +x "${INSTALL_DIR}/${BINARY_NAME}"

    echo "Installed v$latest to ${INSTALL_DIR}/${BINARY_NAME}"

    if [ "$install_type" = "full" ]; then
        echo "Adding ${INSTALL_DIR} to PATH..."
        if [ -d "$INSTALL_DIR" ] && [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
            echo "Add this to your shell profile if needed:"
            echo "  export PATH=\"\$HOME/.local/bin:\$PATH\""
        fi
    fi
}

configure_opencode() {
    local config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
    local config_file="$config_dir/opencode.json"
    local leankg_path="${INSTALL_DIR}/${BINARY_NAME}"

    mkdir -p "$config_dir"

    local has_mcp=false
    local has_plugin=false

    if [ -f "$config_file" ]; then
        if jq -e '.mcp.leankg' "$config_file" > /dev/null 2>&1; then
            echo "LeanKG MCP already configured in OpenCode"
            has_mcp=true
        fi
        if jq -e '.plugin | contains(["leankg@git"])' "$config_file" > /dev/null 2>&1; then
            echo "LeanKG plugin already in OpenCode"
            has_plugin=true
        fi
    else
        echo '{"$schema":"https://opencode.ai/config.json","plugin":[],"mcp":{}}' > "$config_file"
    fi

    local tmp_file
    tmp_file=$(mktemp)

    if [ "$has_mcp" = false ]; then
        jq --arg leankg "$leankg_path" '.mcp.leankg = {"type": "local", "command": [$leankg, "mcp-stdio", "--watch"], "enabled": true}' "$config_file" > "$tmp_file" && mv "$tmp_file" "$config_file"
    fi

    if [ "$has_plugin" = false ]; then
        jq '.plugin += ["leankg@git+https://github.com/FreePeak/LeanKG.git"]' "$config_file" > "$tmp_file" && mv "$tmp_file" "$config_file"
    fi

    echo "Configured LeanKG plugin and MCP for OpenCode at $config_file"
}

configure_cursor() {
    local config_dir="$HOME/.cursor"
    local config_file="$config_dir/mcp.json"
    local leankg_path="${INSTALL_DIR}/${BINARY_NAME}"
    local mcp_port="${MCP_HTTP_PORT:-9699}"

    mkdir -p "$config_dir"

    # SSE/HTTP mode: single server, project routed via ?project= query parameter.
    # Each project's .cursor/mcp.json includes its project path in the URL.
    # The server reads the query param and routes to the correct .leankg database.

    # Remove leankg from global config (per-project config is used instead)
    if [ -f "$config_file" ]; then
        local has_leankg
        has_leankg=$(jq -r '.mcpServers.leankg // empty' "$config_file" 2>/dev/null)
        if [ -n "$has_leankg" ]; then
            echo "Removing LeanKG from global Cursor config (per-project config preferred)"
            local tmp_file
            tmp_file=$(mktemp)
            cat "$config_file" | jq 'del(.mcpServers.leankg)' > "$tmp_file"
            mv "$tmp_file" "$config_file"
        fi
    fi

    # Create per-project .cursor/mcp.json in current directory
    local project_mcp_dir=".cursor"
    local project_mcp_file="$project_mcp_dir/mcp.json"
    local project_root
    project_root="$(pwd)"
    local mcp_url="http://localhost:${mcp_port}/mcp?project=${project_root}"

    if [ -d ".cursor" ] || [ -f ".cursor/mcp.json" ]; then
        mkdir -p "$project_mcp_dir"
        if [ -f "$project_mcp_file" ]; then
            local current_url
            current_url=$(jq -r '.mcpServers.leankg.url // empty' "$project_mcp_file" 2>/dev/null)
            if [ "$current_url" = "$mcp_url" ]; then
                echo "LeanKG already configured in project .cursor/mcp.json"
                return
            fi
            local tmp_file
            tmp_file=$(mktemp)
            cat "$project_mcp_file" | jq --arg url "$mcp_url" \
                '.mcpServers.leankg = {"url": $url}' > "$tmp_file"
            mv "$tmp_file" "$project_mcp_file"
        else
            cat > "$project_mcp_file" <<EOF
{
  "mcpServers": {
    "leankg": {
      "url": "$mcp_url"
    }
  }
}
EOF
        fi
        echo "Configured LeanKG for Cursor in project .cursor/mcp.json"
        echo "  URL: $mcp_url"
        echo "  Project database: ${project_root}/.leankg"
    else
        echo "No .cursor directory in project. Run this from a Cursor project root."
        echo "Or add to .cursor/mcp.json manually:"
        echo "{\"mcpServers\": {\"leankg\": {\"url\": \"$mcp_url\"}}}"
    fi
}

configure_claude() {
    local config_file="$HOME/.claude.json"
    local leankg_path="${INSTALL_DIR}/${BINARY_NAME}"
    local needs_update=false

    if [ -f "$config_file" ] && [ -s "$config_file" ]; then
        local current_path
        current_path=$(jq -r '.mcpServers.leankg.command // empty' "$config_file" 2>/dev/null)
        local current_args
        current_args=$(jq -r '.mcpServers.leankg.args // [] | join(" ")' "$config_file" 2>/dev/null)
        
        if [ -z "$current_path" ]; then
            needs_update=true
        else
            if [ "$current_path" != "$leankg_path" ]; then
                echo "Updating LeanKG binary path for Claude Code: $current_path -> $leankg_path"
                needs_update=true
            fi
            if ! echo "$current_args" | grep -q "\-\-watch"; then
                echo "Adding --watch flag to LeanKG for Claude Code"
                needs_update=true
            fi
        fi
        
        if [ "$needs_update" = false ]; then
            echo "LeanKG already properly configured in Claude Code"
            return
        fi
    else
        needs_update=true
    fi

    local tmp_file
    tmp_file=$(mktemp)
    if [ -f "$config_file" ] && [ "$needs_update" = true ]; then
        cat "$config_file" | jq --arg leankg "$leankg_path" '.mcpServers.leankg = {"type": "stdio", "command": $leankg, "args": ["mcp-stdio", "--watch"]}' > "$tmp_file" && mv "$tmp_file" "$config_file"
    else
        cat > "$tmp_file" <<EOF
{
  "mcpServers": {
    "leankg": {
      "type": "stdio",
      "command": "$leankg_path",
      "args": ["mcp-stdio", "--watch"]
    }
  }
}
EOF
        mv "$tmp_file" "$config_file"
    fi
    echo "Configured LeanKG for Claude Code at $config_file"
}

remove_old_skill() {
    local skill_dir="$HOME/.claude/skills/using-leankg"
    if [ -d "$skill_dir" ]; then
        rm -rf "$skill_dir"
        echo "Removed old LeanKG skill from $skill_dir"
    fi
}

setup_claude_hooks() {
    local plugin_dir="$HOME/.claude/plugins/leankg"
    local hooks_installed=false

    mkdir -p "$plugin_dir/hooks"

    # Write hooks.json with all Claude-Mem-equivalent hooks
    # Setup, SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop
    cat > "$plugin_dir/hooks/hooks.json" <<'EOF'
{
  "description": "LeanKG hooks - enforces LeanKG usage and provides Claude-Mem-like session management",
  "hooks": {
    "Setup": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/version-check.mjs\""
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart.mjs\""
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-init.mjs\""
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/leankg-pretooluse.mjs\""
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "mcp__leankg__*",
        "hooks": [
          {
            "type": "command",
            "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/leankg-posttooluse.mjs\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/summarize.mjs\""
          }
        ]
      }
    ]
  }
}
EOF
    hooks_installed=true

    # Write hook scripts
    cat > "$plugin_dir/hooks/sessionstart.mjs" <<'HOOKEOF'
#!/usr/bin/env node
/**
 * SessionStart hook for LeanKG
 * Injects <tool_selection_hierarchy> at session start.
 */
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";

async function readStdin() {
  return new Promise((resolve) => {
    let data = "";
    process.stdin.on("data", (chunk) => (data += chunk));
    process.stdin.on("end", () => resolve(data));
  });
}

const raw = await readStdin();
const input = JSON.parse(raw);

const ROUTING_BLOCK = `
<tool_selection_hierarchy>
  1. ORCHESTRATE: mcp__leankg__orchestrate(intent)
     - Natural language: "show me impact of changing function X"

  2. CODE DISCOVERY: mcp__leankg__search_code(query, element_type)
     - Primary search. ONE call replaces many Grep/Bash commands.

  3. IMPACT ANALYSIS: mcp__leankg__get_impact_radius(file, depth)
     - Calculate blast radius BEFORE making changes.

  4. CONTEXT: mcp__leankg__get_context(file)
     - Get minimal token-optimized context for a file.

  5. DEPENDENCIES: mcp__leankg__get_dependencies(file) | mcp__leankg__get_dependents(file)

  6. CALLERS: mcp__leankg__get_callers(function) | mcp__leankg__find_function(name)

  7. DOCUMENTATION: mcp__leankg__get_doc_for_file(file) | mcp__leankg__get_traceability(element)

  8. TESTING: mcp__leankg__get_tested_by(file) | mcp__leankg__detect_changes(scope)
</tool_selection_hierarchy>

<forbidden_actions>
  - DO NOT use Grep for code search (use mcp__leankg__search_code instead)
  - DO NOT use Bash find/grep for file search (use mcp__leankg__query_file instead)
</forbidden_actions>
`;

console.log(JSON.stringify({
  hookSpecificOutput: {
    hookEventName: "SessionStart",
    additionalContext: ROUTING_BLOCK,
  },
}));
HOOKEOF

    cat > "$plugin_dir/hooks/pretooluse.mjs" <<'HOOKEOF'
#!/usr/bin/env node
/**
 * LeanKG PreToolUse Hook
 * Provides LeanKG context when code search is detected.
 * Only blocks Bash commands that use raw grep/find.
 */
import { spawnSync } from "node:child_process";

// ─── LeanKG Tools Mapping ───
const LEANKG_TOOLS = {
  search_code: "Search code by name/type",
  find_function: "Locate function definitions",
  query_file: "Find files by name/pattern",
  get_impact_radius: "Calculate blast radius",
  get_dependencies: "Get direct imports",
  get_dependents: "Get files depending on target",
  get_context: "Get AI-optimized file context",
  get_tested_by: "Get test coverage",
  get_call_graph: "Get function call graph",
  get_callers: "Get who calls a function",
};

// ─── Read stdin ───
function readStdin() {
  return new Promise((resolve, reject) => {
    let data = "";
    process.stdin.on("readable", () => {
      let chunk;
      while ((chunk = process.stdin.read()) !== null) {
        data += chunk;
      }
    });
    process.stdin.on("end", () => resolve(data));
    process.stdin.on("error", reject);
  });
}

// ─── Check if LeanKG is available ───
function isLeanKGMCPReady() {
  try {
    const result = spawnSync("cargo", ["run", "--release", "--", "status"], {
      cwd: process.cwd(),
      timeout: 5000,
    });
    return result.status === 0;
  } catch {
    return false;
  }
}

// ─── Only block Bash with grep/find - allow other tools ───
function shouldBlockTool(toolName, toolInput) {
  if (toolName !== "Bash") return false;

  const cmd = (toolInput.command || "").toLowerCase();

  // Build commands always allowed
  const isBuildCmd = /^(cargo|npm|pnpm|yarn|go|make|cmake|rustc)/.test(cmd);
  if (isBuildCmd) return false;

  // Only block if using raw grep/find in bash
  const hasRawSearch = /\b(grep|rg|ag|ack|find|fd|fzf)\b/.test(cmd);
  const isLeankgCmd = cmd.includes("leankg");

  return hasRawSearch && !isLeankgCmd;
}

function buildGuidance(toolInput) {
  const cmd = toolInput.command || "";
  const match = cmd.match(/['"]([^'"]+)['"]/);
  const query = match ? match[1] : "";

  const toolsList = Object.entries(LEANKG_TOOLS)
    .map(([name, desc]) => `  - mcp__leankg__${name}: ${desc}`)
    .join("\n");

  return `LEANKG ENFORCEMENT: Raw search via Bash is blocked.

Use LeanKG MCP tools instead:
${toolsList}

REQUIRED WORKFLOW:
1. mcp__leankg__mcp_status → confirm LeanKG is ready
2. For code search: mcp__leankg__search_code("${query}") or mcp__leankg__find_function("${query}")

The original tool call: Bash(${JSON.stringify(toolInput)})`;
}

async function main() {
  try {
    const raw = await readStdin();
    if (!raw.trim()) process.exit(0);

    const input = JSON.parse(raw);
    const toolName = input.tool_name || "";
    const toolInput = input.tool_input || {};

    if (!shouldBlockTool(toolName, toolInput)) {
      process.exit(0);
    }

    const leanKGReady = isLeanKGMCPReady();
    if (!leanKGReady) process.exit(0);

    // LeanKG ready - block Bash search commands
    console.log(JSON.stringify({
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "deny",
        permissionDecisionReason: buildGuidance(toolInput),
      },
    }) + "\n");
    process.exit(0);
  } catch {
    process.exit(0);
  }
}

main();
HOOKEOF

    cat > "$plugin_dir/hooks/posttooluse.mjs" <<'HOOKEOF'
#!/usr/bin/env node
/**
 * PostToolUse hook for LeanKG
 * Tracks LeanKG MCP tool usage and logs for analytics.
 */
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";

const LEANKG_TOOLS = [
  "mcp__leankg__orchestrate",
  "mcp__leankg__search_code",
  "mcp__leankg__find_function",
  "mcp__leankg__query_file",
  "mcp__leankg__get_impact_radius",
  "mcp__leankg__get_dependencies",
  "mcp__leankg__get_dependents",
  "mcp__leankg__get_context",
  "mcp__leankg__get_callers",
  "mcp__leankg__get_call_graph",
  "mcp__leankg__get_clusters",
  "mcp__leankg__get_doc_for_file",
  "mcp__leankg__get_traceability",
  "mcp__leankg__get_tested_by",
  "mcp__leankg__detect_changes",
  "mcp__leankg__mcp_status",
  "mcp__leankg__mcp_index",
];

const SESSION_LOG_DIR = join(homedir(), ".leankg", "sessions");
const SESSION_LOG_FILE = join(SESSION_LOG_DIR, "tool-usage.log");

async function readStdin() {
  return new Promise((resolve) => {
    let data = "";
    process.stdin.on("data", (chunk) => (data += chunk));
    process.stdin.on("end", () => resolve(data));
  });
}

async function main() {
  try {
    const raw = await readStdin();
    if (!raw.trim()) {
      process.exit(0);
    }

    const input = JSON.parse(raw);
    const toolName = input.tool_name ?? "";
    const toolInput = input.tool_input ?? {};

    const isLeankgTool = LEANKG_TOOLS.some(t => toolName.includes(t));

    if (isLeankgTool) {
      if (!existsSync(SESSION_LOG_DIR)) {
        mkdirSync(SESSION_LOG_DIR, { recursive: true });
      }
      const sessionId = process.env.CLAUDE_SESSION_ID || "unknown";
      const timestamp = new Date().toISOString();
      const logEntry = JSON.stringify({
        timestamp,
        sessionId,
        tool: toolName,
        input: toolInput,
      }) + "\n";
      appendFileSync(SESSION_LOG_FILE, logEntry);
    }
  } catch { /* silent */ }
}

main();
HOOKEOF

    cat > "$plugin_dir/hooks/version-check.mjs" <<'HOOKEOF'
#!/usr/bin/env node
/**
 * Setup hook for LeanKG - version check
 * Runs at plugin startup to verify version requirements
 */
import { readFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { spawnSync } from "node:child_process";

const __dirname = dirname(fileURLToPath(import.meta.url));
const PLUGIN_ROOT = process.env.CLAUDE_PLUGIN_ROOT || __dirname;
const MIN_VERSION = "0.16.0";

function readStdin() {
  return new Promise((resolve, reject) => {
    let data = "";
    process.stdin.on("readable", () => {
      let chunk;
      while ((chunk = process.stdin.read()) !== null) {
        data += chunk;
      }
    });
    process.stdin.on("end", () => resolve(data));
    process.stdin.on("error", reject);
  });
}

function getInstalledVersion() {
  try {
    const cargoPath = join(PLUGIN_ROOT, "..", "..", "Cargo.toml");
    const content = readFileSync(cargoPath, "utf-8");
    const match = content.match(/version\s*=\s*"([^"]+)"/);
    return match ? match[1] : null;
  } catch {
    return null;
  }
}

function compareVersions(a, b) {
  const partsA = a.split(".").map(Number);
  const partsB = b.split(".").map(Number);
  for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
    const pA = partsA[i] || 0;
    const pB = partsB[i] || 0;
    if (pA > pB) return 1;
    if (pA < pB) return -1;
  }
  return 0;
}

async function main() {
  try {
    const raw = await readStdin();
    if (!raw.trim()) process.exit(0);

    const input = JSON.parse(raw);
    const hookName = input.hookName || input.hook_event_name || "Setup";
    if (hookName !== "Setup") process.exit(0);

    const installedVersion = getInstalledVersion();
    if (!installedVersion) {
      console.log(JSON.stringify({
        hookSpecificOutput: {
          hookEventName: "Setup",
          versionCheck: "warning",
          message: "Could not determine LeanKG version. Version gating skipped.",
        },
      }));
      process.exit(0);
    }

    const versionOk = compareVersions(installedVersion, MIN_VERSION) >= 0;
    console.log(JSON.stringify({
      hookSpecificOutput: {
        hookEventName: "Setup",
        versionCheck: versionOk ? "pass" : "fail",
        installedVersion,
        minimumVersion: MIN_VERSION,
        message: versionOk ? `LeanKG v${installedVersion} ready` : `LeanKG v${installedVersion} does not meet minimum v${MIN_VERSION}`,
      },
    }) + "\n");
  } catch (err) {
    console.error("Version check error:", err.message);
    process.exit(0);
  }
}

main();
HOOKEOF

    cat > "$plugin_dir/hooks/session-init.mjs" <<'HOOKEOF'
#!/usr/bin/env node
/**
 * UserPromptSubmit hook for LeanKG (session-init)
 * Initializes session context and injects LeanKG usage patterns
 */
import { existsSync } from "node:fs";
import { join } from "node:path";

function readStdin() {
  return new Promise((resolve, reject) => {
    let data = "";
    process.stdin.on("readable", () => {
      let chunk;
      while ((chunk = process.stdin.read()) !== null) {
        data += chunk;
      }
    });
    process.stdin.on("end", () => resolve(data));
    process.stdin.on("error", reject);
  });
}

function detectProjectType(cwd) {
  const indicators = [
    { pattern: "Cargo.toml", type: "rust", weight: 10 },
    { pattern: "package.json", type: "node", weight: 8 },
    { pattern: "go.mod", type: "go", weight: 9 },
    { pattern: "pyproject.toml", type: "python", weight: 7 },
    { pattern: "pom.xml", type: "java", weight: 7 },
  ];
  let bestType = "unknown";
  let bestScore = 0;
  for (const { pattern, type, weight } of indicators) {
    if (existsSync(join(cwd, pattern))) {
      if (weight > bestScore) {
        bestScore = weight;
        bestType = type;
      }
    }
  }
  return bestType;
}

function buildSessionContext(input) {
  const cwd = input.cwd || process.cwd();
  const projectType = detectProjectType(cwd);

  const context = `<tool_selection_hierarchy>
  1. ORCHESTRATE: mcp__leankg__orchestrate(intent)
  2. CODE DISCOVERY: mcp__leankg__search_code(query, element_type)
  3. IMPACT ANALYSIS: mcp__leankg__get_impact_radius(file, depth)
  4. CONTEXT: mcp__leankg__get_context(file)
  5. DEPENDENCIES: mcp__leankg__get_dependencies(file) | mcp__leankg__get_dependents(file)
  6. CALLERS: mcp__leankg__get_callers(function) | mcp__leankg__find_function(name)
  7. DOCUMENTATION: mcp__leankg__get_doc_for_file(file) | mcp__leankg__get_traceability(element)
  8. TESTING: mcp__leankg__get_tested_by(file) | mcp__leankg__detect_changes(scope)
</tool_selection_hierarchy>

<forbidden_actions>
  - DO NOT use Grep for code search (use mcp__leankg__search_code instead)
  - DO NOT use Bash find/grep for file search (use mcp__leankg__query_file instead)
</forbidden_actions>

<project_context>
Project type detected: ${projectType}
</project_context>

<leankg_reminder>
- Run: mcp__leankg__mcp_status to verify LeanKG is ready
- Run: mcp__leankg__mcp_index to index code if needed
</leankg_reminder>`;

  return context;
}

async function main() {
  try {
    const raw = await readStdin();
    if (!raw.trim()) process.exit(0);

    const input = JSON.parse(raw);
    if (!input.prompt && !input.user_prompt) process.exit(0);

    const sessionContext = buildSessionContext(input);
    console.log(JSON.stringify({
      hookSpecificOutput: {
        hookEventName: "UserPromptSubmit",
        additionalContext: sessionContext,
      },
    }) + "\n");
  } catch (err) {
    console.error("SessionInit error:", err.message);
    process.exit(0);
  }
}

main();
HOOKEOF

    cat > "$plugin_dir/hooks/summarize.mjs" <<'HOOKEOF'
#!/usr/bin/env node
/**
 * Stop hook for LeanKG (summarize)
 * Captures session summary and stores for future context injection
 */
import { writeFileSync, existsSync, mkdirSync } from "node:fs";
import { join } from "node:path";

function readStdin() {
  return new Promise((resolve, reject) => {
    let data = "";
    process.stdin.on("readable", () => {
      let chunk;
      while ((chunk = process.stdin.read()) !== null) {
        data += chunk;
      }
    });
    process.stdin.on("end", () => resolve(data));
    process.stdin.on("error", reject);
  });
}

function extractToolUsage(input) {
  const tools = [];
  if (input.tool_calls) {
    for (const call of input.tool_calls) {
      tools.push({ name: call.name || call.tool_name || "unknown" });
    }
  }
  if (input.tools_used) tools.push(...input.tools_used);
  if (input.mcp_tools) tools.push(...input.mcp_tools.map(t => ({ name: t })));
  return tools;
}

function generateSummary(input) {
  const timestamp = new Date().toISOString();
  const cwd = input.cwd || process.cwd() || "unknown";
  const tools = extractToolUsage(input);
  const mcpTools = tools.filter(t => t.name.startsWith("mcp__leankg__")).map(t => t.name.replace("mcp__leankg__", ""));
  const bashCommands = tools.filter(t => t.name === "Bash").length;

  return {
    timestamp,
    cwd,
    tools_used: { total: tools.length, mcp_leankg: mcpTools.length, bash_commands: bashCommands },
    leankg_tools: [...new Set(mcpTools)],
    session_duration_ms: input.duration_ms || 0,
  };
}

function saveSessionSummary(summary) {
  try {
    const SESSION_DIR = join(process.env.HOME || "~", ".cache", "leankg-hooks", "sessions");
    if (!existsSync(SESSION_DIR)) mkdirSync(SESSION_DIR, { recursive: true });
    const ts = new Date().getTime();
    const filepath = join(SESSION_DIR, `session-${ts}.json`);
    writeFileSync(filepath, JSON.stringify(summary, null, 2));
    const latestPath = join(SESSION_DIR, "latest.json");
    writeFileSync(latestPath, JSON.stringify(summary, null, 2));
    return filepath;
  } catch (err) {
    console.error("Failed to save session summary:", err.message);
    return null;
  }
}

async function main() {
  try {
    const raw = await readStdin();
    if (!raw.trim()) process.exit(0);

    const input = JSON.parse(raw);
    const hookName = input.hook_name || input.hook_event_name || "";
    if (hookName !== "Stop") process.exit(0);

    const summary = generateSummary(input);
    const savedPath = saveSessionSummary(summary);

    const toolList = summary.leankg_tools.length > 0
      ? summary.leankg_tools.map(t => `  - ${t}`).join("\n")
      : "none";

    const summaryText = `Session Summary:
- Working directory: ${summary.cwd}
- Total tools used: ${summary.tools_used.total}
- LeanKG MCP calls: ${summary.tools_used.mcp_leankg}
- Bash commands: ${summary.tools_used.bash_commands}
- LeanKG tools used: ${summary.leankg_tools.join(", ") || "none"}
- Saved to: ${savedPath}`;

    console.log(JSON.stringify({
      hookSpecificOutput: {
        hookEventName: "Stop",
        sessionSummary: summary,
        summaryText,
        savedTo: savedPath,
      },
    }) + "\n");
  } catch (err) {
    console.error("Summarize error:", err.message);
    process.exit(0);
  }
}

main();
HOOKEOF

    chmod +x "$plugin_dir/hooks/sessionstart.mjs" "$plugin_dir/hooks/pretooluse.mjs" "$plugin_dir/hooks/posttooluse.mjs" "$plugin_dir/hooks/version-check.mjs" "$plugin_dir/hooks/session-init.mjs" "$plugin_dir/hooks/summarize.mjs"
    
    if [ ! -f "$plugin_dir/leankg-bootstrap.md" ]; then
        cat > "$plugin_dir/leankg-bootstrap.md" <<'BOOTSTRAPEOF'
# LeanKG Bootstrap

LeanKG is a lightweight knowledge graph for codebase understanding.

**Multi-Project Support:**
LeanKG uses a single HTTP server that supports multiple projects. Each tool accepts a `project` parameter to specify the target project directory. The server routes queries to the correct `.leankg` database automatically.

**IMPORTANT:** Always pass `project="/path/to/project/root"` when calling LeanKG tools. This ensures the server queries the correct project database.

Example: `search_code(query="main", project="/Users/you/work/my-project")`

**Auto-Activated Tools (all accept `project` parameter):**
- `mcp_status` - Check if LeanKG is initialized
- `mcp_init` - Initialize LeanKG for a project
- `mcp_index` - Index codebase
- `search_code` - Search code elements by name/type
- `find_function` - Locate function definitions
- `get_impact_radius` - Calculate blast radius of changes
- `get_dependencies` - Get direct imports of a file
- `get_dependents` - Get files depending on target
- `get_context` - Get AI-optimized context for a file
- `get_tested_by` - Get test coverage info
- `query_file` - Find files by name/pattern
- `get_call_graph` - Get function call chains
- `find_large_functions` - Find oversized functions
- `get_doc_for_file` - Get documentation for a file
- `get_traceability` - Get full traceability chain
- `get_code_tree` - Get codebase structure
- `get_clusters` - Get functional clusters
- `detect_changes` - Pre-commit risk analysis

**MANDATORY WORKFLOW: LeanKG First, Grep Fallback**

Before ANY codebase search/navigation, you MUST:

1. Check if LeanKG is available via `mcp_status(project="/project/root")`
2. If LeanKG is not initialized, run `mcp_init(path="/project/root/.leankg")` first
3. Use LeanKG tools with `project="/project/root"`: `search_code`, `find_function`, `query_file`, etc.
4. **ONLY after LeanKG is exhausted (returns empty) may you fall back to grep/ripgrep**

| Instead of | Use LeanKG |
|------------|------------|
| grep/ripgrep for "where is X?" | `search_code(query="X", project="/path")` or `find_function(name="X", project="/path")` |
| glob + content search for tests | `get_tested_by(file="X", project="/path")` |
| Manual dependency tracing | `get_impact_radius(file="X", project="/path")` or `get_dependencies(file="X", project="/path")` |
| Reading entire files | `get_context(file="X", project="/path")` (token-optimized) |
BOOTSTRAPEOF
        echo "Created leankg-bootstrap.md for Claude Code"
    fi
    
    if [ "$hooks_installed" = true ]; then
        echo "Configured LeanKG hooks for Claude Code"
    else
        echo "LeanKG hooks already configured for Claude Code"
    fi
}

setup_cursor_hooks() {
    local plugin_dir="$HOME/.cursor/plugins/leankg"
    local hooks_installed=false
    
    if [ ! -d "$plugin_dir/hooks" ]; then
        mkdir -p "$plugin_dir/hooks"
        
        cat > "$plugin_dir/hooks/hooks.json" <<'EOF'
{
  "version": 1,
  "hooks": {
    "sessionStart": [
      {
        "command": "./hooks/session-start"
      }
    ]
  }
}
EOF

        cat > "$plugin_dir/hooks/session-start" <<'HOOKEOF'
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
leankg_bootstrap_content=$(cat "${PLUGIN_ROOT}/leankg-bootstrap.md" 2>&1 || echo "")
escape_for_json() {
    local s="$1"
    s="${s//\\/\\\\}"
    s="${s//\"/\\\"}"
    s="${s//$'\n'/\\n}"
    s="${s//$'\r'/\\r}"
    s="${s//$'\t'/\\t}"
    printf '%s' "$s"
}
leankg_bootstrap_escaped=$(escape_for_json "$leankg_bootstrap_content")
session_context="<LEANKG_BOOTSTRAP>\n${leankg_bootstrap_escaped}\n</LEANKG_BOOTSTRAP>"
printf '{\n  "additional_context": "%s"\n}\n' "$session_context"
exit 0
HOOKEOF

        chmod +x "$plugin_dir/hooks/session-start"
        hooks_installed=true
    fi
    
    if [ ! -f "$plugin_dir/leankg-bootstrap.md" ]; then
        cat > "$plugin_dir/leankg-bootstrap.md" <<'BOOTSTRAPEOF'
# LeanKG Bootstrap

LeanKG is a lightweight knowledge graph for codebase understanding.

**Multi-Project Support:**
LeanKG uses a single HTTP server that supports multiple projects. Each tool accepts a `project` parameter to specify the target project directory. The server routes queries to the correct `.leankg` database automatically.

**IMPORTANT:** Always pass `project="/path/to/project/root"` when calling LeanKG tools. This ensures the server queries the correct project database.

Example: `search_code(query="main", project="/Users/you/work/my-project")`

**Auto-Activated Tools (all accept `project` parameter):**
- `mcp_status` - Check if LeanKG is initialized
- `mcp_init` - Initialize LeanKG for a project
- `mcp_index` - Index codebase
- `search_code` - Search code elements by name/type
- `find_function` - Locate function definitions
- `get_impact_radius` - Calculate blast radius of changes
- `get_dependencies` - Get direct imports of a file
- `get_dependents` - Get files depending on target
- `get_context` - Get AI-optimized context for a file
- `get_tested_by` - Get test coverage info
- `query_file` - Find files by name/pattern
- `get_call_graph` - Get function call chains
- `find_large_functions` - Find oversized functions
- `get_doc_for_file` - Get documentation for a file
- `get_traceability` - Get full traceability chain
- `get_code_tree` - Get codebase structure
- `get_clusters` - Get functional clusters
- `detect_changes` - Pre-commit risk analysis

**MANDATORY WORKFLOW: LeanKG First, Grep Fallback**

Before ANY codebase search/navigation, you MUST:

1. Check if LeanKG is available via `mcp_status(project="/project/root")`
2. If LeanKG is not initialized, run `mcp_init(path="/project/root/.leankg")` first
3. Use LeanKG tools with `project="/project/root"`: `search_code`, `find_function`, `query_file`, etc.
4. **ONLY after LeanKG is exhausted (returns empty) may you fall back to grep/ripgrep**

| Instead of | Use LeanKG |
|------------|------------|
| grep/ripgrep for "where is X?" | `search_code(query="X", project="/path")` or `find_function(name="X", project="/path")` |
| glob + content search for tests | `get_tested_by(file="X", project="/path")` |
| Manual dependency tracing | `get_impact_radius(file="X", project="/path")` or `get_dependencies(file="X", project="/path")` |
| Reading entire files | `get_context(file="X", project="/path")` (token-optimized) |
BOOTSTRAPEOF
        echo "Created leankg-bootstrap.md for Cursor"
    fi
    
    if [ "$hooks_installed" = true ]; then
        echo "Configured LeanKG hooks for Cursor"
    else
        echo "LeanKG hooks already configured for Cursor"
    fi
}

configure_kilo() {
    local config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/kilo"
    local config_file="$config_dir/kilo.json"
    local leankg_path="${INSTALL_DIR}/${BINARY_NAME}"
    local needs_update=false

    mkdir -p "$config_dir"

    if [ -f "$config_file" ]; then
        local current_path
        current_path=$(jq -r '.mcp.leankg.command[0] // empty' "$config_file" 2>/dev/null)
        local current_args
        current_args=$(jq -r '.mcp.leankg.command[1:] | join(" ")' "$config_file" 2>/dev/null)
        
        if [ -n "$current_path" ]; then
            if [ "$current_path" != "$leankg_path" ]; then
                echo "Updating LeanKG binary path for Kilo: $current_path -> $leankg_path"
                needs_update=true
            fi
            if ! echo "$current_args" | grep -q "\-\-watch"; then
                echo "Adding --watch flag to LeanKG for Kilo"
                needs_update=true
            fi
        fi
        
        if [ "$needs_update" = false ]; then
            echo "LeanKG already properly configured in Kilo"
            return
        fi
    else
        needs_update=true
    fi

    local tmp_file
    tmp_file=$(mktemp)
    if [ -f "$config_file" ] && [ "$needs_update" = true ]; then
        cat "$config_file" | jq --arg leankg "$leankg_path" '.mcp.leankg = {"type": "local", "command": [$leankg, "mcp-stdio", "--watch"], "enabled": true}' > "$tmp_file"
    else
        cat > "$tmp_file" <<EOF
{
  "\$schema": "https://kilo.ai/config.json",
  "mcp": {
    "leankg": {
      "type": "local",
      "command": ["$leankg_path", "mcp-stdio", "--watch"],
      "enabled": true
    }
  }
}
EOF
    fi
    mv "$tmp_file" "$config_file"
    echo "Configured LeanKG for Kilo at $config_file"
}

configure_gemini() {
    local leankg_path="${INSTALL_DIR}/${BINARY_NAME}"
    local needs_update=false
    
    if command -v gemini >/dev/null 2>&1; then
        echo "Configuring LeanKG for Gemini CLI using 'gemini mcp add'..."
        gemini mcp add leankg "$leankg_path" mcp-stdio --watch --scope user || true
        echo "Configured LeanKG for Gemini CLI"
    else
        local config_file="$HOME/.gemini/settings.json"
        mkdir -p "$HOME/.gemini"

        if [ -f "$config_file" ] && [ -s "$config_file" ]; then
            local current_path
            current_path=$(jq -r '.mcpServers.leankg.command // empty' "$config_file" 2>/dev/null)
            local current_args
            current_args=$(jq -r '.mcpServers.leankg.args // [] | join(" ")' "$config_file" 2>/dev/null)
            
            if [ -n "$current_path" ]; then
                if [ "$current_path" != "$leankg_path" ]; then
                    echo "Updating LeanKG binary path for Gemini CLI: $current_path -> $leankg_path"
                    needs_update=true
                fi
                if ! echo "$current_args" | grep -q "\-\-watch"; then
                    echo "Adding --watch flag to LeanKG for Gemini CLI"
                    needs_update=true
                fi
            fi
            
            if [ "$needs_update" = false ]; then
                echo "LeanKG already properly configured in Gemini CLI"
                return
            fi
        else
            needs_update=true
        fi

        local tmp_file
        tmp_file=$(mktemp)
        if [ -f "$config_file" ] && [ "$needs_update" = true ]; then
            cat "$config_file" | jq --arg leankg "$leankg_path" '.mcpServers.leankg = {"command": $leankg, "args": ["mcp-stdio", "--watch"]}' > "$tmp_file" && mv "$tmp_file" "$config_file"
        else
            cat > "$tmp_file" <<EOF
{
  "mcpServers": {
    "leankg": {
      "command": "$leankg_path",
      "args": ["mcp-stdio", "--watch"]
    }
  }
}
EOF
            mv "$tmp_file" "$config_file"
        fi
        echo "Configured LeanKG for Gemini CLI at $config_file"
    fi
}

configure_antigravity() {
    local config_dir="$HOME/.gemini/antigravity"
    local config_file="$config_dir/mcp_config.json"
    local leankg_path="${INSTALL_DIR}/${BINARY_NAME}"
    local srv_json="{\"name\": \"leankg\", \"transport\": \"stdio\", \"command\": \"$leankg_path\", \"args\": [\"mcp-stdio\", \"--watch\"], \"enabled\": true}"

    mkdir -p "$config_dir"

    if [ -f "$config_file" ]; then
        local content
        content=$(cat "$config_file")
        if echo "$content" | jq -e '(.mcpServers | type == "array") and (.mcpServers[] | select(.name == "leankg"))' > /dev/null 2>&1; then
            echo "LeanKG already configured in Anti Gravity"
            return
        fi
        local tmp_file
        tmp_file=$(mktemp)
        if echo "$content" | jq -e '.mcpServers | type == "array"' > /dev/null 2>&1; then
            cat "$config_file" | jq --argjson srv "$srv_json" '.mcpServers += [$srv]' > "$tmp_file"
        else
            cat "$config_file" | jq --argjson srv "$srv_json" '.mcpServers = [$srv]' > "$tmp_file"
        fi
        mv "$tmp_file" "$config_file"
    else
        echo "{\"mcpServers\": [$srv_json]}" > "$config_file"
    fi
    echo "Configured LeanKG for Anti Gravity at $config_file"
}

install_claude_instructions() {
    local claude_md="$HOME/.config/claude/CLAUDE.md"
    mkdir -p "$(dirname "$claude_md")"
    
    if [ -f "$claude_md" ]; then
        if grep -q "MANDATORY" "$claude_md" 2>/dev/null; then
            echo "LeanKG instructions already exist in Claude Code CLAUDE.md"
        else
            echo "" >> "$claude_md"
            curl -fsSL "$CLAUDE_TEMPLATE_URL" >> "$claude_md" 2>/dev/null || cat >> "$claude_md" <<'EOF'

# LeanKG

## MANDATORY: Use LeanKG First

Before ANY codebase search/navigation, use LeanKG tools:
1. `mcp_status` - check if ready
2. Use tool: `search_code`, `find_function`, `query_file`, `get_impact_radius`, `get_dependencies`, `get_dependents`, `get_tested_by`, `get_context`
3. Only fallback to grep/read if LeanKG fails

| Task | Use |
|------|-----|
| Where is X? | `search_code` or `find_function` |
| What breaks if I change Y? | `get_impact_radius` |
| What tests cover Y? | `get_tested_by` |
| How does X work? | `get_context` |
EOF
            echo "Added LeanKG instructions to Claude Code CLAUDE.md"
        fi
    else
        curl -fsSL "$CLAUDE_TEMPLATE_URL" > "$claude_md" 2>/dev/null || cat > "$claude_md" <<'EOF'
# LeanKG

## MANDATORY: Use LeanKG First

Before ANY codebase search/navigation, use LeanKG tools:
1. `mcp_status` - check if ready
2. Use tool: `search_code`, `find_function`, `query_file`, `get_impact_radius`, `get_dependencies`, `get_dependents`, `get_tested_by`, `get_context`
3. Only fallback to grep/read if LeanKG fails

| Task | Use |
|------|-----|
| Where is X? | `search_code` or `find_function` |
| What breaks if I change Y? | `get_impact_radius` |
| What tests cover Y? | `get_tested_by` |
| How does X work? | `get_context` |
EOF
        echo "Created CLAUDE.md for Claude Code at $claude_md"
    fi
}

index_leankg_project() {
    local leankg_path="${INSTALL_DIR}/${BINARY_NAME}"
    local project_dir="${1:-$(pwd)}"
    
    echo "Indexing LeanKG project at $project_dir..."
    
    if [ ! -x "$leankg_path" ]; then
        echo "LeanKG binary not found at $leankg_path - skipping indexing"
        return 1
    fi
    
    if [ -d "$project_dir/.git" ]; then
        if [ -f "$project_dir/Cargo.toml" ]; then
            echo "Detected Rust project - indexing source code..."
            "$leankg_path" index "$project_dir/src" 2>/dev/null || echo "Indexing completed (or warnings are normal)"
            return 0
        fi
    fi
    
    echo "Not a recognized project structure - skipping indexing"
    return 1
}

install_opencode_skills() {
    local skills_dir="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/skills"
    install_leankg_skill "$skills_dir" "opencode"
}

install_leankg_skill() {
    local skills_dir="$1"
    local agent_name="$2"
    local leankg_skill_dir="$skills_dir/using-leankg"
    
    mkdir -p "$leankg_skill_dir"
    
    local skill_content=$(cat <<'EOF'
---
name: using-leankg
description: Use for ANY code search, navigation, or finding code logic - "where is X", "find logic Y", "how does Z work", impact analysis, dependencies. LeanKG is MANDATORY first.
---

<EXTREMELY_IMPORTANT>
STRICT ENFORCEMENT: You MUST use LeanKG tools FIRST before ANY other method.

1. ALWAYS invoke `mcp_status` first to check LeanKG readiness
2. If NOT ready, invoke `mcp_init` or `mcp_index` to initialize
3. Use LeanKG tools for ALL searches
4. ONLY fall back to RTK or grep if LeanKG returns EMPTY results

**NO EXCEPTIONS. NO RATIONALIZATION. Use LeanKG first.**
</EXTREMELY_IMPORTANT>

## MANDATORY Search Flow: LeanKG -> RTK -> Grep

```
When user asks "where is X", "find logic Y", "how does Z work", etc:
           |
           v
1. mcp_status (ALWAYS check first)
           |
           v
2. search_code("X") or find_function("X") or query_file("X")
           |
           +-- Results returned --> Use get_context(file) to read content
           |
           v (EMPTY)
3. rtk grep "X" --path .
           |
           v (EMPTY)
4. grep -rn "X" --include="*.rs"
```

## LeanKG MCP Tools (Use in this order)

| Step | Tool | When to Use |
|------|------|-------------|
| 1 | `mcp_status` | ALWAYS check first |
| 2 | `search_code("X")` | Find code by name/type |
| 3 | `find_function("X")` | Locate function definitions |
| 4 | `query_file("*X*")` | Find files by name |
| 5 | `get_impact_radius(file)` | Blast radius for changes |
| 6 | `get_context(file)` | READ file content (token-optimized) |
| 7 | `get_dependencies(file)` | Get imports |
| 8 | `get_tested_by(file)` | Find tests |

## Critical: After search_code returns file paths

**IMPORTANT:** When `search_code` returns results with file paths:
1. Use `get_context(file_path)` to READ the actual file content
2. Do NOT just report the file paths - show the code

## RTK Fallback (Only if LeanKG EMPTY)

```bash
rtk grep "search term" --path .
rtk file "pattern" --path .
```

## Grep Fallback (LAST RESORT, only if RTK EMPTY too)

```bash
grep -rn "X" --include="*.rs"
```

## Common Triggers for LeanKG

| User says... | LeanKG tool |
|--------------|-------------|
| "where is X" | `search_code("X")` or `find_function("X")` |
| "find the logic" | `search_code("logic_name")` |
| "how does X work" | `get_context(file)` after search_code |
| "what calls X" | `get_call_graph("X")` |
| "what breaks if I change X" | `get_impact_radius("X")` |
| "find all files named X" | `query_file("X")` |
EOF
)

    if [ -f "$leankg_skill_dir/SKILL.md" ]; then
        if grep -q "STRICT ENFORCEMENT" "$leankg_skill_dir/SKILL.md" 2>/dev/null; then
            echo "LeanKG skill already installed at $leankg_skill_dir"
            return
        fi
    fi
    
    echo "$skill_content" > "$leankg_skill_dir/SKILL.md"
    echo "Installed LeanKG skill to $leankg_skill_dir for $agent_name"
}

install_agents_instructions() {
    local agents_file="$1"
    local agents_dir="$(dirname "$agents_file")"
    mkdir -p "$agents_dir"
    
    local agents_content
    agents_content=$(cat <<'EOF'

## LeanKG Tools Usage

### MANDATORY RULE - ALWAYS USE LEANKG FIRST

**Before doing ANY codebase search, navigation, or analysis, you MUST use LeanKG tools.**

### This Is Not Optional

When a user asks about the codebase, you MUST:
1. Check if LeanKG is available via `mcp_status`
2. If LeanKG is not initialized, run `mcp_init` first
3. Use the appropriate LeanKG tool for the task
4. **ONLY after LeanKG is exhausted** may you fall back to other methods

### Why This Matters

| Instead of | Use LeanKG |
|------------|------------|
| grep/ripgrep for "where is X?" | `search_code` or `find_function` |
| glob + content search for tests | `get_tested_by` |
| Manual dependency tracing | `get_impact_radius` or `get_dependencies` |
| Reading entire files | `get_context` (token-optimized) |

### LeanKG Tools Quick Reference

| Tool | Use Case |
|------|----------|
| `mcp_status` | Check if LeanKG is ready |
| `search_code` | Find code by name/type |
| `find_function` | Locate function definition |
| `query_file` | Find file by name/pattern |
| `get_impact_radius` | Blast radius before changes |
| `get_call_graph` | Understand function flow |
| `get_dependencies` | Get direct imports |
| `get_dependents` | Get files depending on target |
| `get_tested_by` | Find related tests |
| `get_context` | Minimal AI context (token-optimized) |
| `get_review_context` | Focused subgraph for reviews |

### Example Decision Flow

**User: "Where is the auth function?"**
```
1. mcp_status -> confirmed ready
2. search_code("auth") OR find_function("auth")
3. Return result from LeanKG
```

**User: "What would break if I change main.rs?"**
```
1. mcp_status -> confirmed ready
2. get_impact_radius("src/main.rs", depth=3)
3. Return affected elements
```

### Important Notes

- LeanKG maintains a **knowledge graph** of your codebase
- `get_impact_radius` calculates blast radius - always check before changes
- `get_context` returns token-optimized output (~99% token savings)
- Tools are pre-indexed and **much faster** than runtime grep/search
- If LeanKG returns empty/incomplete results, THEN you may use fallback methods
EOF
)

    if [ -f "$agents_file" ]; then
        if grep -qi "LEANKG" "$agents_file" 2>/dev/null; then
            echo "LeanKG instructions already exist in $agents_file"
        else
            echo "$agents_content" >> "$agents_file"
            echo "Added LeanKG instructions to $agents_file"
        fi
    else
        cat > "$agents_file" <<'EOF'
# LeanKG Agent Instructions

## MANDATORY: Use LeanKG First
EOF
        echo "$agents_content" >> "$agents_file"
        echo "Created $agents_file with LeanKG instructions"
    fi
}

main() {
    local target="${1:-}"

    if [ -z "$target" ]; then
        usage
        exit 1
    fi

    local platform
    platform=$(detect_platform)

    case "$target" in
        update)
            update_binary "$platform"
            # Reinstall hooks on update (they may have new features)
            setup_claude_hooks
            # Remove old skill (replaced by hooks)
            remove_old_skill
            echo "LeanKG hooks updated, old skill removed."
            exit 0
            ;;
        version)
            show_version
            exit 0
            ;;
        opencode|cursor|claude|gemini|kilo|antigravity)
            install_binary "$platform" "full"
            ;;
        *)
            echo "Unknown command: $target" >&2
            usage
            exit 1
            ;;
    esac

    if [ "$target" != "update" ]; then
        case "$target" in
            opencode)
                configure_opencode
                install_opencode_skills
                install_agents_instructions "$HOME/.config/opencode/AGENTS.md"
                index_leankg_project "$(pwd)"
                ;;
            cursor)
                configure_cursor
                setup_cursor_hooks
                install_leankg_skill "$HOME/.cursor/skills" "cursor"
                install_agents_instructions "$HOME/.cursor/AGENTS.md"
                ;;
            claude)
                configure_claude
                setup_claude_hooks
                install_claude_instructions
                ;;
            gemini)
                configure_gemini
                install_leankg_skill "$HOME/.gemini/skills" "gemini"
                install_agents_instructions "$HOME/.gemini/GEMINI.md"
                ;;
            kilo)
                configure_kilo
                install_leankg_skill "$HOME/.config/kilo/skills" "kilo"
                install_agents_instructions "$HOME/.config/kilo/AGENTS.md"
                ;;
            antigravity)
                configure_antigravity
                install_leankg_skill "$HOME/.gemini/antigravity/skills" "antigravity"
                install_agents_instructions "$HOME/.gemini/GEMINI.md"
                ;;
        esac
    fi

    echo ""
    echo "Run 'leankg --help' to get started."
    echo "To update later: curl -fsSL $GITHUB_RAW/scripts/install.sh | bash -s -- update"
}

main "$@"
