#!/usr/bin/env node

// dist/analyze.js
import { resolve as resolve3, dirname as dirname5, basename as basename2 } from "path";
import { existsSync as existsSync7, statSync as statSync3 } from "fs";

// dist/cache.js
import { createHash } from "crypto";
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, unlinkSync } from "fs";
import { join, dirname } from "path";
import { execFileSync } from "child_process";
import { fileURLToPath } from "url";
function _readBuildSha() {
  try {
    const metaPath = join(dirname(fileURLToPath(import.meta.url)), "version-meta.json");
    const meta = JSON.parse(readFileSync(metaPath, "utf8"));
    return meta.sha && meta.sha !== "" ? meta.sha : "unknown";
  } catch {
    return "unknown";
  }
}
var BUILD_SHA = _readBuildSha();
function getCacheDir(repoRoot) {
  return join(repoRoot, ".eye-cache");
}
function getEntryCacheDir(repoRoot) {
  return join(repoRoot, ".eye-cache", "builds", BUILD_SHA);
}
var FILE_LEVEL_CACHE_SENTINEL = "__file__";
var EYEIGNORE_ABSENT_SENTINEL = "__no_eyeignore__";
var EYEIGNORE_ABSENT_MTIME = -1;
var _eyeIgnoreHashMemo = /* @__PURE__ */ new Map();
function _getEyeIgnoreHash(repoRoot) {
  const eyeIgnorePath = join(repoRoot, ".eyeignore");
  let currentMtime;
  try {
    currentMtime = statSync(eyeIgnorePath).mtimeMs;
  } catch {
    currentMtime = EYEIGNORE_ABSENT_MTIME;
  }
  const cached = _eyeIgnoreHashMemo.get(repoRoot);
  if (cached !== void 0 && cached.mtimeMs === currentMtime) {
    return cached.hash;
  }
  let hash;
  if (currentMtime === EYEIGNORE_ABSENT_MTIME) {
    hash = EYEIGNORE_ABSENT_SENTINEL;
  } else {
    try {
      const content = readFileSync(eyeIgnorePath);
      hash = createHash("sha256").update(content).digest("hex");
    } catch {
      hash = EYEIGNORE_ABSENT_SENTINEL;
      _eyeIgnoreHashMemo.set(repoRoot, { mtimeMs: EYEIGNORE_ABSENT_MTIME, hash });
      return hash;
    }
  }
  _eyeIgnoreHashMemo.set(repoRoot, { mtimeMs: currentMtime, hash });
  return hash;
}
function getCacheKey(absFile, symbol, repoRoot) {
  const symbolKey = symbol === "" ? FILE_LEVEL_CACHE_SENTINEL : symbol;
  let gitHead = "";
  try {
    gitHead = execFileSync("git", ["-C", repoRoot, "log", "-1", "--format=%H", "--", absFile], {
      stdio: ["pipe", "pipe", "pipe"]
    }).toString().trim();
  } catch {
  }
  let cacheComponent;
  if (gitHead) {
    cacheComponent = gitHead;
  } else {
    try {
      cacheComponent = statSync(absFile).mtimeMs.toString();
    } catch {
      cacheComponent = "untracked";
    }
  }
  const eyeIgnoreHash = _getEyeIgnoreHash(repoRoot);
  return createHash("sha256").update(`${absFile}:${symbolKey}:${cacheComponent}:${eyeIgnoreHash}`).digest("hex");
}
function readCache(cacheDir, key, opts = {}) {
  const maxAgeDays = opts.maxAgeDays ?? 7;
  const cacheFile = join(cacheDir, `${key}.json`);
  if (!existsSync(cacheFile))
    return null;
  try {
    const stat = statSync(cacheFile);
    const ageMs = Date.now() - stat.mtimeMs;
    const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
    if (ageMs > maxAgeMs) {
      return null;
    }
    return JSON.parse(readFileSync(cacheFile, "utf8"));
  } catch {
    return null;
  }
}
function writeCache(cacheDir, key, result) {
  try {
    mkdirSync(cacheDir, { recursive: true });
    writeFileSync(join(cacheDir, `${key}.json`), JSON.stringify(result, null, 2), "utf8");
  } catch {
  }
}
var SCAN_HEAD_FILENAME = "scan-head.txt";
function writeScanHead(repoRoot, sha) {
  try {
    const dir = getCacheDir(repoRoot);
    mkdirSync(dir, { recursive: true });
    writeFileSync(join(dir, SCAN_HEAD_FILENAME), sha, "utf8");
  } catch {
  }
}
function readScanHead(repoRoot) {
  try {
    const file = join(getCacheDir(repoRoot), SCAN_HEAD_FILENAME);
    if (!existsSync(file))
      return null;
    const sha = readFileSync(file, "utf8").trim();
    return sha || null;
  } catch {
    return null;
  }
}
function cleanCache(cacheDir, olderThanDays) {
  if (!existsSync(cacheDir))
    return 0;
  const maxAgeMs = olderThanDays * 24 * 60 * 60 * 1e3;
  const now = Date.now();
  let deleted = 0;
  try {
    const entries = readdirSync(cacheDir);
    for (const entry of entries) {
      if (!entry.endsWith(".json"))
        continue;
      const filePath = join(cacheDir, entry);
      try {
        const stat = statSync(filePath);
        if (now - stat.mtimeMs > maxAgeMs) {
          unlinkSync(filePath);
          deleted++;
        }
      } catch {
      }
    }
  } catch {
  }
  return deleted;
}

// dist/ts-backend.js
import { Project, Node, SyntaxKind } from "ts-morph";
import { relative as relative2, join as join6, basename } from "path";

// dist/format.js
var _noColorFlag = false;
function setNoColorFlag(value) {
  _noColorFlag = value;
}
function buildConfidenceSummary(refs) {
  const summary = { high: 0, medium: 0, low: 0 };
  for (const ref of refs) {
    const c = ref.confidence ?? "medium";
    summary[c]++;
  }
  return summary;
}
function formatHuman(result) {
  const lines = [];
  lines.push(`\u2500\u2500 eye: ${result.target} \u2500\u2500`);
  lines.push(`  total: ${result.total}`);
  for (const ref of result.refs) {
    const kind = (ref.kind + "         ").slice(0, 9);
    const preview = ref.preview ? `  // ${ref.preview}` : "";
    lines.push(`  ${kind} ${ref.file}:${ref.line}${preview}`);
  }
  return lines.join("\n");
}
function formatJson(result, pretty = true) {
  return JSON.stringify(result, null, pretty ? 2 : 0);
}

// dist/walk-utils.js
import { readdirSync as readdirSync2, lstatSync, realpathSync } from "fs";
import { join as join3, resolve } from "path";

// dist/eyeignore.js
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
import { join as join2 } from "path";
function globMatch(pattern, name) {
  const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
  try {
    const re = new RegExp(`^${regexStr}$`);
    return re.test(name);
  } catch {
    return false;
  }
}
function loadEyeIgnore(repoRoot) {
  const filePath = join2(repoRoot, ".eyeignore");
  if (!existsSync2(filePath)) {
    return { exactDirs: /* @__PURE__ */ new Set(), patterns: [] };
  }
  let content;
  try {
    content = readFileSync2(filePath, "utf8");
  } catch {
    return { exactDirs: /* @__PURE__ */ new Set(), patterns: [] };
  }
  const exactDirs = /* @__PURE__ */ new Set();
  const patterns = [];
  for (const rawLine of content.split("\n")) {
    const line = rawLine.trim().replace(/\/+$/, "");
    if (!line || line.startsWith("#"))
      continue;
    if (line.includes("*")) {
      patterns.push(line);
    } else {
      exactDirs.add(line);
    }
  }
  return { exactDirs, patterns };
}

// dist/walk-utils.js
var EXACT_SKIP_DIRS = /* @__PURE__ */ new Set([
  "node_modules",
  "dist",
  "build",
  ".next",
  "coverage",
  ".cache",
  ".eye-cache",
  "__pycache__",
  "venv",
  ".venv",
  ".git",
  "vendor",
  ".turbo",
  "out",
  // ts-backend extras
  "dist-runtime",
  "__openclaw_vitest__",
  ".vite",
  // Python packaging
  "site-packages",
  // C# / .NET build output (LIDS is ASP.NET — must skip)
  "bin",
  "obj"
]);
function shouldSkipDir(name, eyeIgnore) {
  if (EXACT_SKIP_DIRS.has(name))
    return true;
  if (name === "venv" || name === ".venv")
    return true;
  if (name.endsWith("_venv"))
    return true;
  if (eyeIgnore) {
    if (eyeIgnore.exactDirs.has(name))
      return true;
    for (const pat of eyeIgnore.patterns) {
      if (globMatch(pat, name))
        return true;
    }
  }
  return false;
}
function buildExcludeGlobs(eyeIgnore) {
  const globs = [];
  for (const d of EXACT_SKIP_DIRS) {
    globs.push(`!${d}/`);
  }
  globs.push("!*_venv/");
  if (eyeIgnore) {
    for (const d of eyeIgnore.exactDirs) {
      globs.push(`!${d}/`);
    }
    for (const pat of eyeIgnore.patterns) {
      globs.push(`!${pat}/`);
    }
  }
  return globs;
}
var MAX_DEPTH = 12;
function walkFiles(dir, opts) {
  const { extensions, maxFiles = Infinity, followSymlinks = false, maxDepth = MAX_DEPTH, eyeIgnore } = opts;
  const results = [];
  const visitedRealPaths = /* @__PURE__ */ new Set();
  function walk(d, depth) {
    if (depth > maxDepth)
      return;
    if (results.length >= maxFiles)
      return;
    let realD;
    try {
      realD = realpathSync(d);
    } catch {
      return;
    }
    if (visitedRealPaths.has(realD))
      return;
    visitedRealPaths.add(realD);
    let entries;
    try {
      entries = readdirSync2(d, { withFileTypes: true });
    } catch {
      return;
    }
    for (const e of entries) {
      if (results.length >= maxFiles)
        return;
      if (e.name.startsWith("."))
        continue;
      if (e.isDirectory()) {
        if (shouldSkipDir(e.name, eyeIgnore))
          continue;
        const entryPath = resolve(d, e.name);
        if (!followSymlinks) {
          try {
            const lst = lstatSync(entryPath);
            if (lst.isSymbolicLink())
              continue;
          } catch {
            continue;
          }
        }
        walk(entryPath, depth + 1);
      } else if (e.isFile() && extensions.test(e.name)) {
        results.push(join3(d, e.name));
      }
    }
  }
  walk(dir, 0);
  return results;
}

// dist/ripgrep.js
import { spawnSync } from "child_process";
import { createRequire as createRequire2 } from "module";
import { existsSync as existsSync4 } from "fs";

// dist/backends/tree-sitter-backend.js
import { Parser, Language } from "web-tree-sitter";
import { readFileSync as readFileSync3, readdirSync as readdirSync3, existsSync as existsSync3 } from "fs";
import { relative, join as join4, dirname as dirname2 } from "path";
import { createRequire } from "module";
var PARSE_CACHE_BYTES_PER_ENTRY_EST = 15e4;
var PARSE_CACHE_MAX_BYTES = 300 * 1024 * 1024;
var _globalParseCacheHits = 0;
var _globalParseCacheMisses = 0;
function getAndResetParseCacheStats() {
  const stats = { hits: _globalParseCacheHits, misses: _globalParseCacheMisses };
  _globalParseCacheHits = 0;
  _globalParseCacheMisses = 0;
  return stats;
}
var _require = createRequire(import.meta.url);
var _repoBundledWasmDir = join4(dirname2(dirname2(dirname2(
  // import.meta.url → .../src/backends/tree-sitter-backend.ts → strip 3 levels → repo root
  new URL(import.meta.url).pathname
))), "wasm");
function wasmPath(packageName, wasmFile) {
  const wasmDir = process.env["EYE_WASM_DIR"];
  if (wasmDir) {
    const sidecar = join4(wasmDir, wasmFile);
    if (existsSync3(sidecar))
      return sidecar;
  }
  let candidate;
  try {
    const pkgDir = dirname2(_require.resolve(`${packageName}/package.json`));
    candidate = join4(pkgDir, wasmFile);
    if (existsSync3(candidate))
      return candidate;
  } catch {
  }
  const bundled = join4(_repoBundledWasmDir, wasmFile);
  if (existsSync3(bundled))
    return bundled;
  const candidateInfo = candidate ? ` (tried: ${candidate})` : "";
  throw new Error(`Cannot find WASM grammar: ${packageName}/${wasmFile}${candidateInfo}`);
}
var _parserInitialised = false;
var _parserInitPromise = null;
async function ensureParserInit() {
  if (_parserInitialised)
    return;
  if (_parserInitPromise)
    return _parserInitPromise;
  const wasmDir = process.env["EYE_WASM_DIR"];
  if (wasmDir) {
    const wtWasm = join4(wasmDir, "web-tree-sitter.wasm");
    if (existsSync3(wtWasm)) {
      _parserInitPromise = Parser.init({ locateFile: () => wtWasm }).then(() => {
        _parserInitialised = true;
        _parserInitPromise = null;
      });
      return _parserInitPromise;
    }
  }
  _parserInitPromise = Parser.init().then(() => {
    _parserInitialised = true;
    _parserInitPromise = null;
  });
  return _parserInitPromise;
}
var _languageCache = /* @__PURE__ */ new Map();
var _languageInflight = /* @__PURE__ */ new Map();
async function loadLanguage(packageName, wasmFile) {
  const key = `${packageName}/${wasmFile}`;
  const cached = _languageCache.get(key);
  if (cached)
    return cached;
  const inflight = _languageInflight.get(key);
  if (inflight)
    return inflight;
  await ensureParserInit();
  const path = wasmPath(packageName, wasmFile);
  const promise = Language.load(path).then((lang) => {
    _languageCache.set(key, lang);
    _languageInflight.delete(key);
    return lang;
  }, (err) => {
    _languageInflight.delete(key);
    throw err;
  });
  _languageInflight.set(key, promise);
  return promise;
}
async function getLanguage(lang) {
  switch (lang) {
    case "cs":
      return loadLanguage("tree-sitter-c-sharp", "tree-sitter-c_sharp.wasm");
    case "py":
      return loadLanguage("tree-sitter-python", "tree-sitter-python.wasm");
    case "go":
      return loadLanguage("tree-sitter-go", "tree-sitter-go.wasm");
    case "tsx":
      return loadLanguage("tree-sitter-typescript", "tree-sitter-tsx.wasm");
    case "php":
      return loadLanguage("tree-sitter-php", "tree-sitter-php.wasm");
    case "swift":
      return loadLanguage("tree-sitter-swift", "tree-sitter-swift.wasm");
    case "java":
      return loadLanguage("tree-sitter-java", "tree-sitter-java.wasm");
    case "rs":
      return loadLanguage("tree-sitter-rust", "tree-sitter-rust.wasm");
    case "kt":
      return loadLanguage("tree-sitter-kotlin", "tree-sitter-kotlin.wasm");
    case "ts":
    default:
      return loadLanguage("tree-sitter-typescript", "tree-sitter-typescript.wasm");
  }
}
function extToLang(filePath) {
  const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
  if (ext === "cs")
    return "cs";
  if (ext === "py")
    return "py";
  if (ext === "go")
    return "go";
  if (ext === "tsx")
    return "tsx";
  if (ext === "ts")
    return "ts";
  if (ext === "php")
    return "php";
  if (ext === "swift")
    return "swift";
  if (ext === "java")
    return "java";
  if (ext === "rs")
    return "rs";
  if (ext === "kt" || ext === "kts")
    return "kt";
  return null;
}
var DBCONTEXT_SUBCLASS_RE = /\bpublic\s+(?:partial\s+)?class\s+([A-Z]\w*)\s*:\s*(?:\w+\s*,\s*)*(?:\w*DbContext\b)/g;
var DBSET_PROPERTY_RE = /\bpublic\s+(?:virtual\s+)?DbSet\s*<([A-Z][A-Za-z0-9_]*)>\s+([A-Za-z][A-Za-z0-9_]*)/g;
var EF_ATTRIBUTES = ["[Table", "[Key", "[ForeignKey", "[Index", "[Required", "[Column", "[DatabaseGenerated"];
function augmentCSharpSymbolsFromSource(source, astSymbols) {
  const extra = [];
  {
    const re = new RegExp(DBCONTEXT_SUBCLASS_RE.source, "g");
    let m;
    while ((m = re.exec(source)) !== null) {
      const className = m[1];
      const lineNum = source.slice(0, m.index).split("\n").length - 1;
      extra.push({ name: `${className}#DbContext`, isPublic: true, kind: "class", line: lineNum });
    }
  }
  {
    const re = new RegExp(DBSET_PROPERTY_RE.source, "g");
    let m;
    while ((m = re.exec(source)) !== null) {
      const entityType = m[1];
      const propName = m[2];
      const lineNum = source.slice(0, m.index).split("\n").length - 1;
      extra.push({ name: propName, isPublic: true, kind: "property", line: lineNum });
      extra.push({ name: `DbSet:${entityType}`, isPublic: true, kind: "property", line: lineNum });
    }
  }
  {
    const lines = source.split("\n");
    for (let i = 0; i < lines.length; i++) {
      const classMatch = lines[i].match(/\bclass\s+([A-Z][A-Za-z0-9_]*)/);
      if (!classMatch)
        continue;
      const className = classMatch[1];
      const lookbackStart = Math.max(0, i - 5);
      const precedingBlock = lines.slice(lookbackStart, i).join("\n");
      const hasEfAttribute = EF_ATTRIBUTES.some((attr) => precedingBlock.includes(attr));
      if (hasEfAttribute) {
        extra.push({ name: `${className}#EFEntity`, isPublic: true, kind: "class", line: i });
      }
    }
  }
  return [...astSymbols, ...extra];
}
function extractCSharpSymbols(rootNode, source) {
  const symbols = [];
  function walk(node) {
    const type = node.type;
    if (type === "namespace_declaration" || type === "file_scoped_namespace_declaration") {
      const ident = node.childForFieldName("name") ?? node.children.find((c) => c.type === "identifier" || c.type === "qualified_name");
      if (ident) {
        symbols.push({ name: ident.text, isPublic: true, kind: "namespace", line: node.startPosition.row });
      }
    } else if (type === "class_declaration") {
      const mods = node.children.filter((c) => c.type === "modifier").map((c) => c.text);
      const isPublic = mods.includes("public");
      const ident = node.childForFieldName("name") ?? node.children.find((c) => c.type === "identifier");
      if (ident) {
        symbols.push({ name: ident.text, isPublic, kind: "class", line: node.startPosition.row });
      }
    } else if (type === "interface_declaration") {
      const mods = node.children.filter((c) => c.type === "modifier").map((c) => c.text);
      const isPublic = mods.includes("public");
      const ident = node.childForFieldName("name") ?? node.children.find((c) => c.type === "identifier");
      if (ident) {
        symbols.push({ name: ident.text, isPublic, kind: "interface", line: node.startPosition.row });
      }
    } else if (type === "enum_declaration") {
      const mods = node.children.filter((c) => c.type === "modifier").map((c) => c.text);
      const isPublic = mods.includes("public");
      const ident = node.childForFieldName("name") ?? node.children.find((c) => c.type === "identifier");
      if (ident) {
        symbols.push({ name: ident.text, isPublic, kind: "enum", line: node.startPosition.row });
      }
    } else if (type === "record_declaration") {
      const mods = node.children.filter((c) => c.type === "modifier").map((c) => c.text);
      const isPublic = mods.includes("public");
      const ident = node.childForFieldName("name") ?? node.children.find((c) => c.type === "identifier");
      if (ident) {
        symbols.push({ name: ident.text, isPublic, kind: "class", line: node.startPosition.row });
      }
    } else if (type === "struct_declaration") {
      const mods = node.children.filter((c) => c.type === "modifier").map((c) => c.text);
      const isPublic = mods.includes("public");
      const ident = node.childForFieldName("name") ?? node.children.find((c) => c.type === "identifier");
      if (ident) {
        symbols.push({ name: ident.text, isPublic, kind: "class", line: node.startPosition.row });
      }
    } else if (type === "method_declaration") {
      const mods = node.children.filter((c) => c.type === "modifier").map((c) => c.text);
      const isPublic = mods.includes("public");
      const ident = node.childForFieldName("name") ?? node.children.find((c) => c.type === "identifier");
      if (ident) {
        symbols.push({ name: ident.text, isPublic, kind: "method", line: node.startPosition.row });
      }
      return;
    } else if (type === "property_declaration") {
      const mods = node.children.filter((c) => c.type === "modifier").map((c) => c.text);
      const isPublic = mods.includes("public");
      const ident = node.childForFieldName("name") ?? node.children.find((c) => c.type === "identifier");
      if (ident) {
        symbols.push({ name: ident.text, isPublic, kind: "property", line: node.startPosition.row });
      }
      return;
    }
    for (const child of node.children)
      walk(child);
  }
  walk(rootNode);
  if (source !== void 0) {
    return augmentCSharpSymbolsFromSource(source, symbols);
  }
  return symbols;
}
function extractDecoratorName(decoratorNode) {
  const expr = decoratorNode.children.find((c) => c.isNamed);
  if (!expr)
    return decoratorNode.text.replace(/^@/, "").split("(")[0].trim();
  if (expr.type === "identifier")
    return expr.text;
  if (expr.type === "attribute")
    return expr.text;
  if (expr.type === "call") {
    const callee = expr.childForFieldName("function") ?? expr.children.find((c) => c.isNamed);
    if (callee)
      return callee.text;
  }
  return decoratorNode.text.replace(/^@/, "").split("(")[0].trim();
}
function extractPythonSymbols(rootNode) {
  const symbols = [];
  function walk(node) {
    if (node.type === "decorated_definition") {
      const decoratorNames = node.children.filter((c) => c.type === "decorator").map(extractDecoratorName);
      const inner = node.children.find((c) => c.type === "function_definition" || c.type === "class_definition");
      if (inner) {
        if (inner.type === "function_definition") {
          const ident = inner.childForFieldName("name");
          if (ident) {
            symbols.push({
              name: ident.text,
              isPublic: !ident.text.startsWith("_"),
              kind: "method",
              line: node.startPosition.row,
              decorators: decoratorNames
            });
          }
        } else if (inner.type === "class_definition") {
          const ident = inner.childForFieldName("name");
          if (ident) {
            symbols.push({
              name: ident.text,
              isPublic: !ident.text.startsWith("_"),
              kind: "class",
              line: node.startPosition.row,
              decorators: decoratorNames
            });
          }
          for (const child of inner.children)
            walk(child);
        }
      }
      return;
    }
    if (node.type === "function_definition") {
      const ident = node.childForFieldName("name");
      if (ident) {
        const isPublic = !ident.text.startsWith("_");
        symbols.push({ name: ident.text, isPublic, kind: "method", line: node.startPosition.row });
      }
      return;
    } else if (node.type === "class_definition") {
      const ident = node.childForFieldName("name");
      if (ident) {
        symbols.push({ name: ident.text, isPublic: !ident.text.startsWith("_"), kind: "class", line: node.startPosition.row });
      }
    }
    for (const child of node.children)
      walk(child);
  }
  walk(rootNode);
  return symbols;
}
function extractPythonEventListenerHandlers(rootNode) {
  const handlers = /* @__PURE__ */ new Set();
  function walk(node) {
    if (node.type === "decorated_definition") {
      const decoratorNodes = node.children.filter((c) => c.type === "decorator");
      for (const dec of decoratorNodes) {
        const name = extractDecoratorName(dec);
        if (name === "event.listens_for") {
          const inner = node.children.find((c) => c.type === "function_definition");
          if (inner) {
            const ident = inner.childForFieldName("name");
            if (ident)
              handlers.add(ident.text);
          }
        }
      }
      for (const child of node.children)
        walk(child);
      return;
    }
    if (node.type === "expression_statement") {
      const callNode = node.children.find((c) => c.type === "call");
      if (callNode) {
        const funcNode = callNode.childForFieldName("function");
        if (funcNode && funcNode.text === "event.listen") {
          const argsNode = callNode.childForFieldName("arguments");
          if (argsNode) {
            const posArgs = argsNode.children.filter((c) => c.isNamed && c.type !== "keyword_argument");
            const thirdArg = posArgs[2];
            if (thirdArg && thirdArg.type === "identifier") {
              handlers.add(thirdArg.text);
            }
          }
        }
      }
    }
    for (const child of node.children)
      walk(child);
  }
  walk(rootNode);
  return handlers;
}
function extractEventListenCallHandlers(rootNode) {
  const handlers = [];
  function walk(node) {
    if (node.type === "expression_statement") {
      const callNode = node.children.find((c) => c.type === "call");
      if (callNode) {
        const funcNode = callNode.childForFieldName("function");
        if (funcNode && funcNode.text === "event.listen") {
          const argsNode = callNode.childForFieldName("arguments");
          if (argsNode) {
            const posArgs = argsNode.children.filter((c) => c.isNamed && c.type !== "keyword_argument");
            const thirdArg = posArgs[2];
            if (thirdArg && thirdArg.type === "identifier") {
              handlers.push(thirdArg.text);
            }
          }
        }
      }
    }
    for (const child of node.children)
      walk(child);
  }
  walk(rootNode);
  return handlers;
}
function extractPythonImportAliases(rootNode) {
  const aliases = /* @__PURE__ */ new Map();
  function walk(node) {
    if (node.type === "import_from_statement") {
      for (const child of node.children) {
        if (child.type === "aliased_import") {
          const nameNode = child.childForFieldName("name");
          const aliasNode = child.childForFieldName("alias");
          if (nameNode && aliasNode) {
            aliases.set(aliasNode.text, nameNode.text);
          }
        }
      }
    }
    for (const child of node.children)
      walk(child);
  }
  walk(rootNode);
  return aliases;
}
function extractPythonHandlerImportSource(rootNode) {
  const result = /* @__PURE__ */ new Map();
  function walk(node) {
    if (node.type === "import_from_statement") {
      const namedChildren = node.children.filter((c) => c.isNamed);
      const moduleNode = namedChildren[0];
      if (!moduleNode) {
        for (const child of node.children)
          walk(child);
        return;
      }
      const moduleName = moduleNode.text;
      for (let i = 1; i < namedChildren.length; i++) {
        const child = namedChildren[i];
        if (child.type === "aliased_import") {
          const nameNode = child.childForFieldName("name");
          const aliasNode = child.childForFieldName("alias");
          if (nameNode)
            result.set(nameNode.text, moduleName);
          if (aliasNode)
            result.set(aliasNode.text, moduleName);
        } else if (child.type === "dotted_name" || child.type === "identifier") {
          result.set(child.text, moduleName);
        }
      }
    }
    for (const child of node.children)
      walk(child);
  }
  walk(rootNode);
  return result;
}
async function buildCrossFileEventListenerMap(pyFiles) {
  if (pyFiles.length === 0)
    return /* @__PURE__ */ new Map();
  const grammar = await getLanguage("py");
  const result = /* @__PURE__ */ new Map();
  for (const absFile of pyFiles) {
    let sourceCode;
    try {
      sourceCode = readFileSync3(absFile, "utf8");
    } catch {
      continue;
    }
    const parser = new Parser();
    parser.setLanguage(grammar);
    const treeOrNull = parser.parse(sourceCode);
    if (!treeOrNull)
      continue;
    const rootNode = treeOrNull.rootNode;
    const aliasMap = extractPythonImportAliases(rootNode);
    const importSourceMap = extractPythonHandlerImportSource(rootNode);
    const rawHandlers = extractEventListenCallHandlers(rootNode);
    for (const rawName of rawHandlers) {
      const originalName = aliasMap.get(rawName) ?? rawName;
      const importSourceModule = importSourceMap.get(rawName) ?? importSourceMap.get(originalName) ?? null;
      let bucket = result.get(originalName);
      if (!bucket) {
        bucket = [];
        result.set(originalName, bucket);
      }
      if (!bucket.some((e) => e.regFile === absFile)) {
        bucket.push({ regFile: absFile, importSourceModule });
      }
    }
  }
  return result;
}
function extractGoReceiverType(methodNode) {
  const receiverNode = methodNode.childForFieldName("receiver");
  if (!receiverNode)
    return void 0;
  for (const child of receiverNode.children) {
    if (child.type === "parameter_declaration") {
      const typeNode = child.childForFieldName("type") ?? [...child.children].reverse().find((c) => c.isNamed);
      if (!typeNode)
        continue;
      if (typeNode.type === "pointer_type") {
        const baseType = typeNode.children.find((c) => c.type === "type_identifier");
        if (baseType)
          return baseType.text;
        return typeNode.text.replace(/^\*/, "");
      }
      if (typeNode.type === "type_identifier")
        return typeNode.text;
      return typeNode.text.replace(/^\*/, "");
    }
  }
  return void 0;
}
function extractGoSymbols(rootNode) {
  const symbols = [];
  function walk(node) {
    if (node.type === "function_declaration") {
      const ident = node.childForFieldName("name");
      if (ident) {
        const isPublic = /^[A-Z]/.test(ident.text);
        symbols.push({ name: ident.text, isPublic, kind: "method", line: node.startPosition.row });
      }
      return;
    } else if (node.type === "method_declaration") {
      const ident = node.childForFieldName("name");
      if (ident) {
        const receiverType = extractGoReceiverType(node);
        const symbolName = receiverType ? `${receiverType}.${ident.text}` : ident.text;
        const isPublic = /^[A-Z]/.test(ident.text);
        symbols.push({ name: symbolName, isPublic, kind: "method", line: node.startPosition.row, receiverType });
      }
      return;
    } else if (node.type === "type_declaration") {
      for (const child of node.children) {
        if (child.type === "type_spec") {
          const ident = child.childForFieldName("name");
          if (ident) {
            const isPublic = /^[A-Z]/.test(ident.text);
            const bodyChild = child.children.find((c) => c.type === "struct_type" || c.type === "interface_type");
            const kind = bodyChild?.type === "interface_type" ? "interface" : "class";
            symbols.push({ name: ident.text, isPublic, kind, line: node.startPosition.row });
          }
        }
      }
      return;
    }
    for (const child of node.children)
      walk(child);
  }
  walk(rootNode);
  return symbols;
}
function extractPhpSymbols(rootNode) {
  const symbols = [];
  function walk(node, depth) {
    if (depth > 4)
      return;
    const type = node.type;
    if (type === "function_definition") {
      const ident = node.childForFieldName("name");
      if (ident) {
        symbols.push({ name: ident.text, isPublic: true, kind: "method", line: node.startPosition.row });
      }
      return;
    } else if (type === "method_declaration") {
      const modifiers = node.children.filter((c) => c.type === "visibility_modifier").map((c) => c.text);
      const isPublic = modifiers.length === 0 || modifiers.includes("public");
      if (!isPublic)
        return;
      const ident = node.childForFieldName("name");
      if (ident) {
        symbols.push({ name: ident.text, isPublic, kind: "method", line: node.startPosition.row });
      }
      return;
    } else if (type === "class_declaration") {
      const ident = node.childForFieldName("name");
      if (ident) {
        symbols.push({ name: ident.text, isPublic: true, kind: "class", line: node.startPosition.row });
      }
    }
    for (const child of node.children)
      walk(child, depth + 1);
  }
  walk(rootNode, 0);
  return symbols;
}
function swiftIsPublic(node) {
  const mods = node.children.find((c) => c.type === "modifiers");
  if (!mods)
    return false;
  const text = mods.text;
  return text.includes("public") || text.includes("open");
}
function extractSwiftSymbols(rootNode) {
  const symbols = [];
  function walk(node, depth) {
    if (depth > 5)
      return;
    const type = node.type;
    if (type === "class_declaration") {
      const kwNode = node.children.find((c) => !c.isNamed && ["class", "struct", "enum", "extension", "actor"].includes(c.type));
      const kw = kwNode?.type ?? "class";
      if (kw === "extension") {
        const nameNode = node.children.find((c) => c.type === "user_type" || c.type === "type_identifier");
        if (nameNode) {
          const ident = nameNode.type === "user_type" ? nameNode.children.find((c) => c.type === "type_identifier") : nameNode;
          if (ident) {
            symbols.push({ name: `${ident.text}#extension`, isPublic: true, kind: "class", line: node.startPosition.row });
          }
        }
      } else {
        const ident = node.children.find((c) => c.type === "type_identifier");
        if (ident) {
          const isPublic = swiftIsPublic(node);
          const kind = kw === "enum" ? "enum" : kw === "struct" ? "class" : (
            // struct → "class" kind (same as Go struct)
            "class"
          );
          symbols.push({ name: ident.text, isPublic, kind, line: node.startPosition.row });
        }
      }
      for (const child of node.children)
        walk(child, depth + 1);
      return;
    } else if (type === "function_declaration") {
      const ident = node.children.find((c) => c.type === "simple_identifier");
      if (ident) {
        const isPublic = swiftIsPublic(node);
        symbols.push({ name: ident.text, isPublic, kind: "method", line: node.startPosition.row });
      }
      return;
    } else if (type === "protocol_declaration") {
      const ident = node.children.find((c) => c.type === "type_identifier");
      if (ident) {
        const isPublic = swiftIsPublic(node);
        symbols.push({ name: ident.text, isPublic, kind: "interface", line: node.startPosition.row });
      }
      for (const child of node.children)
        walk(child, depth + 1);
      return;
    } else if (type === "protocol_function_declaration") {
      const ident = node.children.find((c) => c.type === "simple_identifier");
      if (ident) {
        symbols.push({ name: ident.text, isPublic: true, kind: "method", line: node.startPosition.row });
      }
      return;
    }
    for (const child of node.children)
      walk(child, depth + 1);
  }
  walk(rootNode, 0);
  return symbols;
}
function extractTsSymbols(rootNode) {
  const symbols = [];
  function walk(node) {
    if (node.type === "export_statement") {
      const decl = node.children.find((c) => [
        "function_declaration",
        "class_declaration",
        "lexical_declaration",
        "type_alias_declaration",
        "interface_declaration",
        "enum_declaration"
      ].includes(c.type));
      if (decl) {
        const ident = decl.childForFieldName("name") ?? decl.children.find((c) => c.type === "identifier");
        if (ident) {
          const kind = decl.type.includes("class") ? "class" : decl.type.includes("interface") ? "interface" : decl.type.includes("enum") ? "enum" : "method";
          const isTypeExport = decl.type === "type_alias_declaration" || decl.type === "interface_declaration";
          symbols.push({ name: ident.text, isPublic: true, kind, line: node.startPosition.row, ...isTypeExport ? { isTypeExport: true } : {} });
        }
      }
    }
    for (const child of node.children)
      walk(child);
  }
  walk(rootNode);
  return symbols;
}
function javaIsPublic(node) {
  const mods = node.children.find((c) => c.type === "modifiers");
  if (!mods)
    return false;
  return mods.children.some((c) => c.type === "public");
}
function extractJavaSymbols(rootNode) {
  const symbols = [];
  function walk(node, depth) {
    if (depth > 6)
      return;
    const type = node.type;
    if (type === "class_declaration" || type === "interface_declaration" || type === "enum_declaration" || type === "record_declaration") {
      const ident = node.childForFieldName("name");
      if (ident) {
        const isPublic = javaIsPublic(node);
        const kind = type === "interface_declaration" ? "interface" : type === "enum_declaration" ? "enum" : "class";
        symbols.push({ name: ident.text, isPublic, kind, line: node.startPosition.row });
      }
      for (const child of node.children)
        walk(child, depth + 1);
      return;
    } else if (type === "method_declaration") {
      const ident = node.childForFieldName("name");
      if (ident) {
        const isPublic = javaIsPublic(node);
        symbols.push({ name: ident.text, isPublic, kind: "method", line: node.startPosition.row });
      }
      return;
    }
    for (const child of node.children)
      walk(child, depth + 1);
  }
  walk(rootNode, 0);
  return symbols;
}
function rustIsPublic(node) {
  return node.children.some((c) => c.type === "visibility_modifier" && c.text.startsWith("pub"));
}
function extractRustSymbols(rootNode) {
  const symbols = [];
  function walkImpl(node, implTypeName, depth) {
    if (depth > 8)
      return;
    const type = node.type;
    if (type === "function_item") {
      const ident = node.childForFieldName("name");
      if (ident) {
        const isPublic = rustIsPublic(node);
        const name = implTypeName ? `${implTypeName}::${ident.text}` : ident.text;
        symbols.push({ name, isPublic, kind: "method", line: node.startPosition.row });
      }
      return;
    } else if (type === "function_signature_item") {
      const ident = node.childForFieldName("name");
      if (ident && implTypeName) {
        symbols.push({ name: `${implTypeName}::${ident.text}`, isPublic: true, kind: "method", line: node.startPosition.row });
      }
      return;
    } else if (type === "struct_item") {
      const ident = node.childForFieldName("name");
      if (ident) {
        const isPublic = rustIsPublic(node);
        symbols.push({ name: ident.text, isPublic, kind: "class", line: node.startPosition.row });
      }
      return;
    } else if (type === "enum_item") {
      const ident = node.childForFieldName("name");
      if (ident) {
        const isPublic = rustIsPublic(node);
        symbols.push({ name: ident.text, isPublic, kind: "enum", line: node.startPosition.row });
      }
      return;
    } else if (type === "trait_item") {
      const ident = node.childForFieldName("name");
      if (ident) {
        const isPublic = rustIsPublic(node);
        symbols.push({ name: ident.text, isPublic, kind: "interface", line: node.startPosition.row });
        const body = node.childForFieldName("body");
        if (body)
          walkImpl(body, ident.text, depth + 1);
      }
      return;
    } else if (type === "impl_item") {
      const typeNode = node.childForFieldName("type");
      const traitNode = node.childForFieldName("trait");
      const contextName = traitNode ? traitNode.text : typeNode?.text ?? null;
      const body = node.childForFieldName("body");
      if (body)
        walkImpl(body, contextName, depth + 1);
      return;
    } else if (type === "declaration_list") {
      for (const child of node.children)
        walkImpl(child, implTypeName, depth + 1);
      return;
    }
    for (const child of node.children)
      walkImpl(child, implTypeName, depth + 1);
  }
  walkImpl(rootNode, null, 0);
  return symbols;
}
function kotlinIsPublic(node) {
  const mods = node.children.find((c) => c.type === "modifiers");
  if (!mods)
    return true;
  return !mods.children.some((c) => c.type === "visibility_modifier" && (c.text === "private" || c.text === "protected" || c.text === "internal"));
}
function kotlinIsInterface(node) {
  return node.children.some((c) => !c.isNamed && c.text === "interface");
}
function extractKotlinSymbols(rootNode) {
  const symbols = [];
  function walkFunctionDeclaration(node, contextName) {
    const firstNamed = node.children.find((c) => c.isNamed);
    const isExtension = firstNamed?.type === "user_type";
    const ident = node.children.find((c) => c.type === "simple_identifier");
    if (!ident)
      return;
    const isPublic = kotlinIsPublic(node);
    let name;
    if (isExtension && firstNamed) {
      name = `${firstNamed.text}.${ident.text}`;
    } else if (contextName) {
      name = ident.text;
    } else {
      name = ident.text;
    }
    symbols.push({ name, isPublic, kind: "method", line: node.startPosition.row });
  }
  function walkClass(node, depth) {
    const ident = node.children.find((c) => c.type === "type_identifier");
    if (!ident)
      return;
    const isPublic = kotlinIsPublic(node);
    const kind = kotlinIsInterface(node) ? "interface" : "class";
    symbols.push({ name: ident.text, isPublic, kind, line: node.startPosition.row });
    const body = node.children.find((c) => c.type === "class_body" || c.type === "enum_class_body");
    if (body)
      walkBody(body, ident.text, depth + 1);
  }
  function walkBody(node, contextName, depth) {
    if (depth > 8)
      return;
    for (const child of node.children) {
      if (child.type === "function_declaration") {
        walkFunctionDeclaration(child, contextName);
      } else if (child.type === "class_declaration" || child.type === "object_declaration") {
        walkClass(child, depth + 1);
      }
    }
  }
  function walk(node, depth) {
    if (depth > 8)
      return;
    const type = node.type;
    if (type === "function_declaration") {
      walkFunctionDeclaration(node, null);
      return;
    } else if (type === "class_declaration" || type === "object_declaration") {
      walkClass(node, depth);
      return;
    }
    for (const child of node.children)
      walk(child, depth + 1);
  }
  walk(rootNode, 0);
  return symbols;
}
function assertValidIdentifier(symbol) {
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(symbol);
}
function grepSymbolInRepo(repoRoot, symbol, ext, eyeIgnore) {
  if (!assertValidIdentifier(symbol))
    return [];
  const includeGlobs = ext.map((e) => `*.${e}`);
  const excludeGlobs = buildExcludeGlobs(eyeIgnore);
  return rgLines(repoRoot, `\\b${symbol}\\b`, {
    includeGlobs,
    excludeGlobs,
    noIgnore: true
  });
}
function grepSymbol(repoRoot, symbol, ext, eyeIgnore) {
  return grepSymbolInRepo(repoRoot, symbol, ext, eyeIgnore);
}
function lookupFromIndex(symbol, exts, invertedIndex) {
  if (!assertValidIdentifier(symbol))
    return [];
  const bucket = invertedIndex.get(symbol);
  if (!bucket)
    return [];
  const re = new RegExp(`\\b${symbol}\\b`);
  const extSet = new Set(exts.map((e) => `.${e}`));
  return bucket.filter((hit) => {
    const dot = hit.file.lastIndexOf(".");
    if (dot === -1)
      return false;
    const ext = hit.file.slice(dot);
    if (!extSet.has(ext))
      return false;
    return re.test(hit.preview);
  });
}
var EF_METHOD_ACCESS_RE = /\.(Where|First|FirstOrDefault|Single|SingleOrDefault|Add|AddRange|Remove|Update|Find|Count|Any|Include|ThenInclude|OrderBy|OrderByDescending|ToList|ToListAsync|SaveChanges|SaveChangesAsync)\b/;
function escapeRegex(s) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function isInCommentOrString(line, symbol) {
  const trimmed = line.trimStart();
  if (trimmed.startsWith("#"))
    return true;
  if (trimmed.startsWith("//"))
    return true;
  const idx = trimmed.indexOf(symbol);
  if (idx === -1)
    return false;
  const hashPos = trimmed.indexOf("#");
  if (hashPos !== -1 && hashPos < idx)
    return true;
  const slashPos = trimmed.indexOf("//");
  if (slashPos !== -1 && slashPos < idx)
    return true;
  const before = trimmed.slice(0, idx);
  const singleCount = (before.match(/(?<!\\)'/g) ?? []).length;
  const doubleCount = (before.match(/(?<!\\)"/g) ?? []).length;
  const backtickCount = (before.match(/(?<!\\)`/g) ?? []).length;
  if (singleCount % 2 === 1)
    return true;
  if (doubleCount % 2 === 1)
    return true;
  if (backtickCount % 2 === 1)
    return true;
  return false;
}
function isPythonDefLine(line, symbol) {
  const p = line.trimStart();
  const esc = escapeRegex(symbol);
  return new RegExp(`^(async\\s+)?def\\s+${esc}\\b`).test(p) || new RegExp(`^class\\s+${esc}\\b`).test(p);
}
function isPythonShadowingAssignment(line, symbol) {
  const p = line.trimStart();
  const esc = escapeRegex(symbol);
  const assignRe = new RegExp(`\\b${esc}\\s*=(?![=])\\s*`);
  if (!assignRe.test(p))
    return false;
  if (new RegExp(`\\b${esc}\\s*\\.`).test(p))
    return false;
  if (new RegExp(`\\b${esc}\\s*\\[`).test(p))
    return false;
  if (new RegExp(`\\b${esc}\\s*:=`).test(p))
    return false;
  return true;
}
function isGoDefLine(line, symbol) {
  const p = line.trimStart();
  const esc = escapeRegex(symbol);
  return new RegExp(`^func\\s+(\\([^)]+\\)\\s+)?${esc}\\b`).test(p) || new RegExp(`^(var|const|type)\\s+${esc}\\b`).test(p);
}
function isGoShadowingAssignment(line, symbol) {
  const p = line.trimStart();
  const esc = escapeRegex(symbol);
  const assignRe = new RegExp(`\\b${esc}\\s*(:=|=(?![=]))`);
  if (!assignRe.test(p))
    return false;
  if (new RegExp(`\\b${esc}\\s*\\.`).test(p))
    return false;
  if (new RegExp(`\\b${esc}\\s*\\[`).test(p))
    return false;
  return true;
}
function isKotlinDefLine(line, symbol) {
  const p = line.trimStart();
  const esc = escapeRegex(symbol);
  return (
    // fun Symbol( / override fun Symbol( / suspend fun Symbol( / private fun Symbol( etc.
    new RegExp(`\\bfun\\s+${esc}\\s*[(<]`).test(p) || // class Symbol / data class Symbol / sealed class Symbol / abstract class Symbol etc.
    new RegExp(`\\bclass\\s+${esc}\\b`).test(p) || // object Symbol
    new RegExp(`\\bobject\\s+${esc}\\b`).test(p) || // interface Symbol
    new RegExp(`\\binterface\\s+${esc}\\b`).test(p) || // typealias Symbol
    new RegExp(`\\btypealias\\s+${esc}\\b`).test(p)
  );
}
function isRustDefLine(line, symbol) {
  const p = line.trimStart();
  const esc = escapeRegex(symbol);
  return (
    // pub fn Symbol / fn Symbol / pub async fn Symbol / async fn Symbol
    new RegExp(`\\bfn\\s+${esc}\\b`).test(p) || // pub struct Symbol / struct Symbol
    new RegExp(`\\bstruct\\s+${esc}\\b`).test(p) || // pub enum Symbol / enum Symbol
    new RegExp(`\\benum\\s+${esc}\\b`).test(p) || // pub trait Symbol / trait Symbol
    new RegExp(`\\btrait\\s+${esc}\\b`).test(p) || // impl Symbol { — direct impl block
    new RegExp(`\\bimpl\\s+${esc}\\s*[\\w{<]`).test(p) || // impl Trait for Symbol { — trait implementation
    new RegExp(`\\bimpl\\s+.*\\bfor\\s+${esc}\\b`).test(p) || // type Symbol = ...
    new RegExp(`\\btype\\s+${esc}\\b`).test(p)
  );
}
function isCSharpDefLine(line, symbol) {
  const p = line.trimStart();
  const esc = escapeRegex(symbol);
  return new RegExp(`\\b${esc}\\s*\\(`).test(p) && /\b(public|private|protected|internal|static|virtual|override|abstract|sealed|async)\b/.test(p) && !/\bnew\s+/.test(p);
}
function classifyRef(preview, symbol, lang) {
  const p = preview.trimStart();
  const esc = escapeRegex(symbol);
  if (lang === "py") {
    if (/^(import |from )/.test(p))
      return "import";
    if (isPythonDefLine(p, symbol))
      return "def";
    if (isInCommentOrString(p, symbol))
      return "comment";
    if (isPythonShadowingAssignment(p, symbol))
      return "shadowing";
    if (new RegExp(`\\b${esc}\\s*\\(`).test(p))
      return "caller";
    if (new RegExp(`\\b${esc}\\s*\\.`).test(p))
      return "caller";
    if (new RegExp(`\\bdef\\s+\\w+\\s*\\([^)]*\\b${esc}\\b`).test(p))
      return "caller";
    return "ref";
  } else if (lang === "go") {
    if (/^import /.test(p))
      return "import";
    if (isGoDefLine(p, symbol))
      return "def";
    if (isInCommentOrString(p, symbol))
      return "comment";
    if (isGoShadowingAssignment(p, symbol))
      return "shadowing";
    if (new RegExp(`\\b${esc}\\s*\\(`).test(p))
      return "caller";
    if (new RegExp(`\\b${esc}\\s*\\.`).test(p))
      return "caller";
    return "ref";
  } else if (lang === "cs") {
    if (/^using /.test(p))
      return "import";
    if (isInCommentOrString(p, symbol))
      return "comment";
    if (/\bnew\s+/.test(p) && new RegExp(`\\b${esc}\\s*\\(`).test(p))
      return "caller";
    if (isCSharpDefLine(p, symbol))
      return "def";
    if (new RegExp(`\\b${esc}\\s*\\(`).test(p))
      return "caller";
    if (p.includes(symbol) && EF_METHOD_ACCESS_RE.test(p))
      return "caller";
    if (new RegExp(`Set\\s*<\\s*${esc}\\s*>\\s*\\(`).test(p))
      return "caller";
    if (new RegExp(`\\b${esc}\\s*\\.`).test(p))
      return "caller";
    return "ref";
  } else if (lang === "java") {
    if (/^import /.test(p))
      return "import";
    if (isInCommentOrString(p, symbol))
      return "comment";
    if (/\bnew\s+/.test(p) && new RegExp(`\\b${esc}\\s*\\(`).test(p))
      return "caller";
    if (new RegExp(`\\b${esc}\\s*\\(`).test(p))
      return "caller";
    if (new RegExp(`\\b${esc}\\s*\\.`).test(p))
      return "caller";
    return "ref";
  } else if (lang === "swift") {
    if (/^import /.test(p))
      return "import";
    if (isInCommentOrString(p, symbol))
      return "comment";
    if (new RegExp(`\\b${esc}\\s*\\(`).test(p))
      return "caller";
    return "ref";
  } else if (lang === "php") {
    if (/^use /.test(p))
      return "import";
    if (isInCommentOrString(p, symbol))
      return "comment";
    if (new RegExp(`\\b${esc}\\s*\\(`).test(p))
      return "caller";
    return "ref";
  } else if (lang === "rs") {
    if (/^use /.test(p))
      return "import";
    if (isInCommentOrString(p, symbol))
      return "comment";
    if (isRustDefLine(p, symbol))
      return "def";
    if (new RegExp(`\\b${esc}\\s*\\(`).test(p))
      return "caller";
    if (new RegExp(`\\.\\s*${esc}\\s*\\(`).test(p))
      return "caller";
    if (new RegExp(`::${esc}\\b`).test(p))
      return "caller";
    return "ref";
  } else if (lang === "kt") {
    if (/^import /.test(p))
      return "import";
    if (isInCommentOrString(p, symbol))
      return "comment";
    if (isKotlinDefLine(p, symbol))
      return "def";
    if (new RegExp(`\\b${esc}\\s*\\(`).test(p))
      return "caller";
    if (new RegExp(`\\b${esc}\\s*\\.`).test(p))
      return "caller";
    return "ref";
  } else {
    if (/^(import |from )/.test(p) || p.includes("import"))
      return "import";
    if (p.includes("export"))
      return "reexport";
    if (new RegExp(`\\b${symbol}\\s*\\(`).test(p))
      return "caller";
    return "ref";
  }
}
async function analyzeTreeSitter(repoRoot, absFile, symbolArg, invertedIndex, parseCache, eyeIgnore, crossFileEventListeners) {
  const relFile = relative(repoRoot, absFile);
  const lang = extToLang(absFile);
  if (!lang) {
    throw new Error(`Unsupported file extension for tree-sitter backend: ${absFile}`);
  }
  const grammar = await getLanguage(lang);
  let sourceCode;
  let tree;
  if (parseCache !== void 0) {
    const cachedEntry = parseCache.get(absFile);
    if (cachedEntry) {
      _globalParseCacheHits++;
      sourceCode = cachedEntry.sourceCode;
      tree = cachedEntry.tree;
    } else {
      _globalParseCacheMisses++;
      const parser = new Parser();
      parser.setLanguage(grammar);
      try {
        sourceCode = readFileSync3(absFile, "utf8");
      } catch (err) {
        throw new Error(`Cannot read file: ${absFile}: ${String(err)}`);
      }
      const treeOrNull = parser.parse(sourceCode);
      if (!treeOrNull) {
        throw new Error(`Tree-sitter failed to parse file: ${absFile}`);
      }
      tree = treeOrNull;
      const estimatedBytes = parseCache.size * PARSE_CACHE_BYTES_PER_ENTRY_EST;
      if (estimatedBytes < PARSE_CACHE_MAX_BYTES) {
        parseCache.set(absFile, { sourceCode, tree });
      }
    }
  } else {
    const parser = new Parser();
    parser.setLanguage(grammar);
    try {
      sourceCode = readFileSync3(absFile, "utf8");
    } catch (err) {
      throw new Error(`Cannot read file: ${absFile}: ${String(err)}`);
    }
    const treeOrNull = parser.parse(sourceCode);
    if (!treeOrNull) {
      throw new Error(`Tree-sitter failed to parse file: ${absFile}`);
    }
    tree = treeOrNull;
  }
  const searchExts = {
    cs: ["cs"],
    py: ["py"],
    go: ["go"],
    ts: ["ts", "tsx", "js", "jsx"],
    tsx: ["ts", "tsx", "js", "jsx"],
    php: ["php"],
    swift: ["swift"],
    java: ["java"],
    rs: ["rs"],
    kt: ["kt", "kts"]
  };
  const exts = searchExts[lang];
  const target = symbolArg ? `${relFile}:${symbolArg}` : relFile;
  const refs = [];
  const seen = /* @__PURE__ */ new Set();
  function addRef(file, line, preview, kind, isDefinition = false) {
    if (isDefinition)
      return;
    const key = `${file}:${line}`;
    if (seen.has(key))
      return;
    seen.add(key);
    refs.push({
      file,
      line,
      kind,
      preview: preview.trimStart().slice(0, 100),
      confidence: "medium",
      confidence_reason: "tree-sitter syntax-level analysis"
    });
  }
  if (symbolArg) {
    let symbolExists = false;
    let defLine = -1;
    if (lang === "cs") {
      const symbols = extractCSharpSymbols(tree.rootNode, sourceCode);
      const match = symbols.find((s) => s.name === symbolArg);
      if (match) {
        symbolExists = true;
        defLine = match.line + 1;
      }
    } else if (lang === "py") {
      const symbols = extractPythonSymbols(tree.rootNode);
      const match = symbols.find((s) => s.name === symbolArg);
      if (match) {
        symbolExists = true;
        defLine = match.line + 1;
        const eventListeners = extractPythonEventListenerHandlers(tree.rootNode);
        if (eventListeners.has(symbolArg)) {
          addRef(relFile, match.line + 1, `# [event listener] ${symbolArg}`, "caller");
        } else if (crossFileEventListeners?.has(symbolArg)) {
          const entries = crossFileEventListeners.get(symbolArg);
          const handlerModuleStem = absFile.replace(/\.py$/, "").split(/[/\\]/).pop() ?? "";
          const matchingEntry = entries.find((e) => {
            if (!e.importSourceModule)
              return false;
            const srcStem = e.importSourceModule.split(".").pop() ?? e.importSourceModule;
            return srcStem === handlerModuleStem;
          });
          if (matchingEntry) {
            const regRelPath = relative(repoRoot, matchingEntry.regFile);
            addRef(relFile, match.line + 1, `# [cross-file event listener] ${symbolArg} (registered in ${regRelPath})`, "caller");
          }
        }
      }
    } else if (lang === "go") {
      const symbols = extractGoSymbols(tree.rootNode);
      const match = symbols.find((s) => s.name === symbolArg);
      if (match) {
        symbolExists = true;
        defLine = match.line + 1;
      }
    } else if (lang === "php") {
      const symbols = extractPhpSymbols(tree.rootNode);
      const match = symbols.find((s) => s.name === symbolArg);
      if (match) {
        symbolExists = true;
        defLine = match.line + 1;
      }
    } else if (lang === "swift") {
      const symbols = extractSwiftSymbols(tree.rootNode);
      const match = symbols.find((s) => s.name === symbolArg);
      if (match) {
        symbolExists = true;
        defLine = match.line + 1;
      }
    } else if (lang === "java") {
      const symbols = extractJavaSymbols(tree.rootNode);
      const match = symbols.find((s) => s.name === symbolArg);
      if (match) {
        symbolExists = true;
        defLine = match.line + 1;
      }
    } else if (lang === "rs") {
      const symbols = extractRustSymbols(tree.rootNode);
      const match = symbols.find((s) => s.name === symbolArg);
      if (match) {
        symbolExists = true;
        defLine = match.line + 1;
      }
    } else if (lang === "kt") {
      const symbols = extractKotlinSymbols(tree.rootNode);
      const match = symbols.find((s) => s.name === symbolArg);
      if (match) {
        symbolExists = true;
        defLine = match.line + 1;
      }
    } else {
      const symbols = extractTsSymbols(tree.rootNode);
      const match = symbols.find((s) => s.name === symbolArg);
      if (match) {
        symbolExists = true;
        defLine = match.line + 1;
      }
    }
    if (!symbolExists) {
      throw new Error(`symbol not found in file: ${symbolArg}`);
    }
    const matches = invertedIndex ? lookupFromIndex(symbolArg, exts, invertedIndex) : grepSymbol(repoRoot, symbolArg, exts, eyeIgnore);
    for (const { file, line, preview } of matches) {
      if (file === relFile && line === defLine)
        continue;
      const kind = classifyRef(preview, symbolArg, lang);
      addRef(file, line, preview, kind);
    }
  } else {
    let symbols = [];
    if (lang === "cs") {
      symbols = extractCSharpSymbols(tree.rootNode, sourceCode).filter((s) => s.isPublic);
    } else if (lang === "py") {
      symbols = extractPythonSymbols(tree.rootNode).filter((s) => s.isPublic);
    } else if (lang === "go") {
      symbols = extractGoSymbols(tree.rootNode).filter((s) => s.isPublic);
    } else if (lang === "php") {
      symbols = extractPhpSymbols(tree.rootNode).filter((s) => s.isPublic);
    } else if (lang === "swift") {
      symbols = extractSwiftSymbols(tree.rootNode).filter((s) => s.isPublic);
    } else if (lang === "java") {
      symbols = extractJavaSymbols(tree.rootNode).filter((s) => s.isPublic);
    } else if (lang === "rs") {
      symbols = extractRustSymbols(tree.rootNode).filter((s) => s.isPublic);
    } else if (lang === "kt") {
      symbols = extractKotlinSymbols(tree.rootNode).filter((s) => s.isPublic);
    } else {
      symbols = extractTsSymbols(tree.rootNode).filter((s) => s.isPublic);
    }
    const defLines = new Set(symbols.map((s) => s.line + 1));
    for (const sym of symbols) {
      const matches = invertedIndex ? lookupFromIndex(sym.name, exts, invertedIndex) : grepSymbol(repoRoot, sym.name, exts, eyeIgnore);
      for (const { file, line, preview } of matches) {
        if (file === relFile && defLines.has(line))
          continue;
        const kind = classifyRef(preview, sym.name, lang);
        addRef(file, line, preview, kind);
      }
    }
  }
  refs.sort((a, b) => a.file === b.file ? a.line - b.line : a.file.localeCompare(b.file));
  const MEANINGFUL_KINDS = /* @__PURE__ */ new Set(["caller", "ref", "import", "type-ref", "reexport"]);
  const meaningfulTotal = refs.filter((r) => MEANINGFUL_KINDS.has(r.kind)).length;
  return { target, total: meaningfulTotal, refs, confidence_summary: buildConfidenceSummary(refs) };
}

// dist/ripgrep.js
function resolveRgPath() {
  const envRg = process.env.EYE_RG_PATH;
  if (envRg && existsSync4(envRg)) {
    return envRg;
  }
  try {
    const req = createRequire2(import.meta.url);
    const mod = req("@vscode/ripgrep");
    return mod.rgPath;
  } catch {
  }
  return "rg";
}
var RG_PATH = resolveRgPath();
function rgFiles(root, opts) {
  const args = [
    "--files-with-matches",
    "--fixed-strings",
    "--no-heading"
  ];
  if (opts.noIgnore) {
    args.push("--no-ignore", "--hidden");
  }
  for (const g of opts.includeGlobs ?? []) {
    args.push("--glob", g);
  }
  for (const g of opts.excludeGlobs ?? []) {
    args.push("--glob", g);
  }
  for (const p of opts.patterns) {
    args.push("-e", p);
  }
  args.push(".");
  try {
    const result = spawnSync(RG_PATH, args, {
      cwd: root,
      encoding: "utf8",
      maxBuffer: 10 * 1024 * 1024
    });
    if (result.status !== 0 && result.status !== 1)
      return [];
    return (result.stdout ?? "").split("\n").map((l) => l.replace(/^\.\//, "").trim()).filter(Boolean);
  } catch {
    return [];
  }
}
function rgLines(root, pattern, opts = {}) {
  const args = [
    "--json",
    "--line-number",
    "--no-heading",
    "--with-filename"
  ];
  if (opts.noIgnore) {
    args.push("--no-ignore", "--hidden");
  }
  if (opts.fixedStrings) {
    args.push("--fixed-strings");
  }
  for (const g of opts.includeGlobs ?? []) {
    args.push("--glob", g);
  }
  for (const g of opts.excludeGlobs ?? []) {
    args.push("--glob", g);
  }
  args.push(pattern, ".");
  try {
    const result = spawnSync(RG_PATH, args, {
      cwd: root,
      encoding: "utf8",
      maxBuffer: opts.maxBuffer ?? 10 * 1024 * 1024
    });
    if (result.status !== 0 && result.status !== 1)
      return [];
    const hits = [];
    for (const rawLine of (result.stdout ?? "").split("\n")) {
      if (!rawLine)
        continue;
      const hit = parseRgLine(rawLine);
      if (hit)
        hits.push(hit);
    }
    return hits;
  } catch {
    return [];
  }
}
function rgLinesInFiles(files, pattern, fixedStrings = false) {
  if (!files.length || !pattern)
    return [];
  const results = [];
  const BATCH = 50;
  for (let i = 0; i < files.length; i += BATCH) {
    const batch = files.slice(i, i + BATCH);
    const args = [
      "--json",
      "--line-number",
      "--no-heading",
      "--with-filename"
    ];
    if (fixedStrings)
      args.push("--fixed-strings");
    args.push(pattern, ...batch);
    try {
      const result = spawnSync(RG_PATH, args, {
        encoding: "utf8",
        maxBuffer: 10 * 1024 * 1024
      });
      if (!result.stdout)
        continue;
      for (const rawLine of result.stdout.split("\n").filter(Boolean)) {
        const hit = parseRgFileLine(rawLine);
        if (hit)
          results.push(hit);
      }
    } catch {
    }
  }
  return results;
}
function parseRgLine(rawLine) {
  let event;
  try {
    event = JSON.parse(rawLine);
  } catch {
    return null;
  }
  if (event.type !== "match")
    return null;
  const d = event.data;
  if (!d || !d.path || typeof d.line_number !== "number" || !d.lines)
    return null;
  const file = d.path.text.replace(/^\.\//, "");
  const preview = d.lines.text.trimEnd().trimStart().slice(0, 100);
  return { file, line: d.line_number, preview };
}
function parseRgFileLine(rawLine) {
  let event;
  try {
    event = JSON.parse(rawLine);
  } catch {
    return null;
  }
  if (event.type !== "match")
    return null;
  const d = event.data;
  if (!d || !d.path || typeof d.line_number !== "number" || !d.lines)
    return null;
  const absFile = d.path.text;
  const content = d.lines.text.trimEnd().trimStart().slice(0, 100);
  return { absFile, line: d.line_number, content };
}

// dist/git-utils.js
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
import { join as join5 } from "path";
import { execFile, execFileSync as execFileSync2 } from "child_process";
import { promisify } from "util";
var execFileAsync = promisify(execFile);
async function gitExec(args, opts, token) {
  const fullArgs = token ? ["-c", `http.extraHeader=Authorization: Bearer ${token}`, ...args] : args;
  await execFileAsync("git", fullArgs, opts);
}
async function cloneOrPull(cloneUrl, destPath, token) {
  const resolvedToken = token ?? process.env.GITEA_TOKEN ?? "";
  if (existsSync5(join5(destPath, ".git"))) {
    await execFileAsync("git", ["remote", "set-url", "origin", cloneUrl], { cwd: destPath });
    await gitExec(["fetch", "--unshallow"], { cwd: destPath, timeout: 12e4 }, resolvedToken || void 0).catch(() => {
    });
    await gitExec(["fetch", "origin"], { cwd: destPath, timeout: 6e4 }, resolvedToken || void 0);
    await execFileAsync("git", ["reset", "--hard", "FETCH_HEAD"], { cwd: destPath, timeout: 3e4 });
  } else {
    mkdirSync2(destPath, { recursive: true });
    await gitExec(["clone", "--depth=1", cloneUrl, destPath], { timeout: 12e4 }, resolvedToken || void 0);
  }
}
function getRepoHead(repoPath) {
  try {
    return execFileSync2("git", ["-C", repoPath, "rev-parse", "HEAD"], {
      stdio: ["pipe", "pipe", "pipe"]
    }).toString().trim();
  } catch {
    return "";
  }
}
function getChangedFilesSince(repoPath, fromSha) {
  if (!fromSha)
    return null;
  try {
    const out = execFileSync2("git", ["-C", repoPath, "diff", "--name-only", fromSha, "HEAD"], { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
    if (!out)
      return [];
    return out.split("\n").filter(Boolean);
  } catch {
    return null;
  }
}

// dist/ts-backend.js
var _projectCache = /* @__PURE__ */ new Map();
var PROJECT_CACHE_MAX = 3;
function _getCachedProject(repoRoot, cacheKey, collectedFiles) {
  const entry = _projectCache.get(repoRoot);
  if (!entry)
    return null;
  if (entry.cacheKey !== cacheKey)
    return null;
  if (entry.files.length !== collectedFiles.length)
    return null;
  const newSorted = [...collectedFiles].sort().join("\0");
  const cachedSorted = [...entry.files].sort().join("\0");
  if (newSorted !== cachedSorted)
    return null;
  return entry.project;
}
function _setCachedProject(repoRoot, cacheKey, collectedFiles, project) {
  if (!_projectCache.has(repoRoot) && _projectCache.size >= PROJECT_CACHE_MAX) {
    const firstKey = _projectCache.keys().next().value;
    _projectCache.delete(firstKey);
  }
  _projectCache.set(repoRoot, { project, cacheKey, files: [...collectedFiles] });
}
var TS_EXTS = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
var MAX_FILES = 8e3;
var INCREMENTAL_THRESHOLD = (() => {
  const v = parseInt(process.env.EYE_INCREMENTAL_THRESHOLD ?? "", 10);
  return Number.isNaN(v) || v <= 0 ? MAX_FILES : v;
})();
function find1HopImporters(repoRoot, absFile, eyeIgnore) {
  const relFile = relative2(repoRoot, absFile);
  const noExt = relFile.replace(/\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, "");
  const fileBase = basename(noExt);
  const pattern1 = `/${fileBase}"`;
  const pattern2 = `/${fileBase}'`;
  const pattern3 = `/${fileBase}.`;
  try {
    const excludeGlobs = buildExcludeGlobs(eyeIgnore);
    const relFiles = rgFiles(repoRoot, {
      includeGlobs: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.mts", "*.cts", "*.mjs", "*.cjs"],
      excludeGlobs,
      patterns: [pattern1, pattern2, pattern3],
      noIgnore: true
    });
    return relFiles.map((l) => join6(repoRoot, l)).filter((l) => l !== absFile);
  } catch {
    return [];
  }
}
function grepSymbolInFiles(repoRoot, files, symbol) {
  if (!files.length || !symbol)
    return [];
  if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(symbol))
    return [];
  return rgLinesInFiles(files, `\\b${symbol}\\b`);
}
function analyzeTsIncremental(repoRoot, absFile, symbolArg, eyeIgnore) {
  const relFile = relative2(repoRoot, absFile);
  const target = symbolArg ? `${relFile}:${symbolArg}` : relFile;
  const importers = find1HopImporters(repoRoot, absFile, eyeIgnore);
  const refs = [];
  const seen = /* @__PURE__ */ new Set();
  if (!symbolArg) {
    for (const imp of importers) {
      const relImp = relative2(repoRoot, imp);
      try {
        const fileBase = basename(absFile).replace(/\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, "");
        const hits = rgLinesInFiles([imp], `/${fileBase}"`, true).concat(rgLinesInFiles([imp], `/${fileBase}'`, true)).concat(rgLinesInFiles([imp], `/${fileBase}.`, true));
        for (const { line: lineNum, content } of hits) {
          const key = `${relImp}:${lineNum}`;
          if (seen.has(key))
            continue;
          seen.add(key);
          refs.push({ file: relImp, line: lineNum, kind: "import", preview: content, confidence: "high", confidence_reason: "grep 1-hop importer" });
        }
      } catch {
      }
    }
  } else {
    const matches = grepSymbolInFiles(repoRoot, importers, symbolArg);
    for (const { absFile: mFile, line, content } of matches) {
      const relMFile = relative2(repoRoot, mFile);
      const key = `${relMFile}:${line}`;
      if (seen.has(key))
        continue;
      seen.add(key);
      let kind = "ref";
      if (/^(from |import )/.test(content))
        kind = "import";
      else if (content.includes(`${symbolArg}(`))
        kind = "caller";
      refs.push({ file: relMFile, line, kind, preview: content, confidence: "high", confidence_reason: "grep 1-hop importer" });
    }
  }
  appendCjsRequireRefs(repoRoot, absFile, symbolArg, seen, refs, eyeIgnore, importers);
  refs.sort((a, b) => a.file === b.file ? a.line - b.line : a.file.localeCompare(b.file));
  return { target, total: refs.length, refs, confidence_summary: buildConfidenceSummary(refs) };
}
function collectWalkFiles(dir, eyeIgnore) {
  return walkFiles(dir, { extensions: TS_EXTS, maxFiles: MAX_FILES, eyeIgnore });
}
function classifyNode(node) {
  let p = node.getParent();
  while (p) {
    const k = p.getKind();
    if (k === SyntaxKind.ImportDeclaration || k === SyntaxKind.ImportSpecifier || k === SyntaxKind.ImportClause) {
      return "import";
    }
    if (k === SyntaxKind.ExportDeclaration || k === SyntaxKind.ExportSpecifier) {
      return "reexport";
    }
    if (k === SyntaxKind.CallExpression) {
      return "caller";
    }
    if (k === SyntaxKind.TypeReference || k === SyntaxKind.TypeQuery) {
      return "type-ref";
    }
    p = p.getParent();
  }
  return "ref";
}
function analyzeTs(repoRoot, absFile, symbolArg, eyeIgnore) {
  const collectedFiles = collectWalkFiles(repoRoot, eyeIgnore);
  if (collectedFiles.length >= INCREMENTAL_THRESHOLD) {
    process.stderr.write(`[warn] hit MAX_FILES=${MAX_FILES} cap (${collectedFiles.length}+ files); using incremental 1-hop grep mode
`);
    return analyzeTsIncremental(repoRoot, absFile, symbolArg, eyeIgnore);
  }
  const headSha = getRepoHead(repoRoot);
  const cacheKey = headSha || "no-git";
  let project = _getCachedProject(repoRoot, cacheKey, collectedFiles);
  if (!project) {
    project = new Project({
      compilerOptions: { allowJs: true, jsx: 4, target: 99, module: 99, esModuleInterop: true },
      useInMemoryFileSystem: false,
      skipAddingFilesFromTsConfig: true,
      skipFileDependencyResolution: false
    });
    for (const f of collectedFiles) {
      try {
        project.addSourceFileAtPathIfExists(f);
      } catch {
      }
    }
    _setCachedProject(repoRoot, cacheKey, collectedFiles, project);
  }
  let sf = project.getSourceFile(absFile);
  if (!sf)
    sf = project.addSourceFileAtPathIfExists(absFile);
  if (!sf) {
    throw new Error(`could not load source file: ${absFile}`);
  }
  const refs = [];
  const seen = /* @__PURE__ */ new Set();
  function pushRef(node) {
    const file = node.getSourceFile();
    const filePath = relative2(repoRoot, file.getFilePath());
    const { line } = file.getLineAndColumnAtPos(node.getStart());
    const key = `${filePath}:${line}`;
    if (seen.has(key))
      return;
    seen.add(key);
    const lineText = file.getFullText().split("\n")[line - 1] || "";
    refs.push({
      file: filePath,
      line,
      kind: classifyNode(node),
      preview: lineText.trim().slice(0, 100),
      confidence: "high",
      confidence_reason: "ts-morph AST direct reference"
    });
  }
  function collectFromIdentifier(idNode) {
    try {
      const id = idNode;
      for (const r of id.findReferences()) {
        for (const ref of r.getReferences()) {
          const n = ref.getNode();
          if (n.getSourceFile().getFilePath() === absFile && n.getStart() === idNode.getStart())
            continue;
          pushRef(n);
        }
      }
    } catch {
    }
  }
  function expandInterfaceOverrides(idNode) {
    try {
      const id = idNode;
      for (const r of id.findReferences()) {
        for (const ref of r.getReferences()) {
          const n = ref.getNode();
          if (n.getSourceFile().getFilePath() === absFile && n.getStart() === idNode.getStart())
            continue;
          const parentNode = n.getParent();
          if (!Node.isMethodDeclaration(parentNode))
            continue;
          const file = n.getSourceFile();
          const filePath = relative2(repoRoot, file.getFilePath());
          const { line } = file.getLineAndColumnAtPos(n.getStart());
          const key = `${filePath}:${line}`;
          if (seen.has(key))
            continue;
          seen.add(key);
          const lineText = file.getFullText().split("\n")[line - 1] || "";
          refs.push({
            file: filePath,
            line,
            kind: "impl_override",
            preview: lineText.trim().slice(0, 100),
            confidence: "high",
            confidence_reason: "ts-morph findReferences() interface implementor"
          });
        }
      }
    } catch {
    }
  }
  let target = "";
  if (symbolArg) {
    target = `${relative2(repoRoot, absFile)}:${symbolArg}`;
    let found = false;
    sf.forEachDescendant((n) => {
      if (Node.isIdentifier(n) && n.getText() === symbolArg) {
        const p = n.getParent();
        if (Node.isFunctionDeclaration(p) || Node.isClassDeclaration(p) || Node.isVariableDeclaration(p) || Node.isInterfaceDeclaration(p) || Node.isTypeAliasDeclaration(p) || Node.isEnumDeclaration(p) || Node.isExportSpecifier(p) || Node.isMethodDeclaration(p) || Node.isMethodSignature(p) || Node.isPropertyAssignment(p)) {
          found = true;
          if (Node.isMethodSignature(p)) {
            expandInterfaceOverrides(n);
          }
          collectFromIdentifier(n);
        }
      }
    });
    if (!found) {
      throw new Error(`symbol not found in file: ${symbolArg}`);
    }
  } else {
    target = relative2(repoRoot, absFile);
    const exported = sf.getExportedDeclarations();
    for (const [, decls] of exported) {
      for (const d of decls) {
        const id = d.getFirstChildByKind(SyntaxKind.Identifier) || (Node.isVariableDeclaration(d) ? d.getNameNode() : null);
        if (id && Node.isIdentifier(id))
          collectFromIdentifier(id);
      }
    }
    for (const sf2 of project.getSourceFiles()) {
      for (const imp of sf2.getImportDeclarations()) {
        try {
          const ms = imp.getModuleSpecifierSourceFile();
          if (ms && ms.getFilePath() === absFile) {
            pushRef(imp.getModuleSpecifier());
          }
        } catch {
        }
      }
    }
  }
  appendCjsRequireRefs(repoRoot, absFile, symbolArg, seen, refs, eyeIgnore);
  refs.sort((a, b) => a.file === b.file ? a.line - b.line : a.file.localeCompare(b.file));
  return { target, total: refs.length, refs, confidence_summary: buildConfidenceSummary(refs) };
}
function appendCjsRequireRefs(repoRoot, absFile, symbolArg, seen, refs, eyeIgnore, precomputedImporters) {
  const rawCandidates = precomputedImporters ?? find1HopImporters(repoRoot, absFile, eyeIgnore);
  if (!rawCandidates.length)
    return;
  const CJS_CANDIDATE_CAP = 500;
  let candidateFiles = rawCandidates;
  if (rawCandidates.length > CJS_CANDIDATE_CAP) {
    process.stderr.write(`[eye] warning: ${rawCandidates.length} importers found for ${absFile}, CJS scan capped at ${CJS_CANDIDATE_CAP}, results may be incomplete
`);
    candidateFiles = rawCandidates.slice(0, CJS_CANDIDATE_CAP);
  }
  if (!candidateFiles.length)
    return;
  const fileBase = basename(absFile).replace(/\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, "");
  const requirePattern1 = `/${fileBase}"`;
  const requirePattern2 = `/${fileBase}'`;
  const requirePattern3 = `/${fileBase}.`;
  const cjsConsumers = [];
  const JS_ONLY_EXTS = /\.(js|jsx|mjs|cjs)$/;
  for (const candidateAbs of candidateFiles) {
    if (!JS_ONLY_EXTS.test(candidateAbs))
      continue;
    const relCandidate = relative2(repoRoot, candidateAbs);
    let hasCjsOrDynamic = false;
    try {
      const allHits = rgLinesInFiles([candidateAbs], requirePattern1, true).concat(rgLinesInFiles([candidateAbs], requirePattern2, true)).concat(rgLinesInFiles([candidateAbs], requirePattern3, true));
      for (const { line: lineNum, content } of allHits) {
        const isComment = /^\s*(\/\/|\/\*|\*)/.test(content);
        const isCjsOrDynamic = /\brequire\s*\(/.test(content) || /\bimport\s*\(/.test(content);
        if (isComment || !isCjsOrDynamic)
          continue;
        hasCjsOrDynamic = true;
        const key = `${relCandidate}:${lineNum}`;
        if (!seen.has(key)) {
          seen.add(key);
          refs.push({
            file: relCandidate,
            line: lineNum,
            kind: "import",
            preview: content.slice(0, 100),
            confidence: "medium",
            confidence_reason: "grep-heuristic CJS/dynamic-import"
          });
        }
      }
    } catch {
    }
    if (hasCjsOrDynamic) {
      cjsConsumers.push(candidateAbs);
    }
  }
  if (symbolArg && cjsConsumers.length > 0) {
    const symbolMatches = grepSymbolInFiles(repoRoot, cjsConsumers, symbolArg);
    for (const { absFile: mFile, line, content } of symbolMatches) {
      const relMFile = relative2(repoRoot, mFile);
      const key = `${relMFile}:${line}`;
      if (seen.has(key))
        continue;
      seen.add(key);
      let kind = "ref";
      if (content.includes(`${symbolArg}(`))
        kind = "caller";
      refs.push({
        file: relMFile,
        line,
        kind,
        preview: content.slice(0, 100),
        confidence: "medium",
        confidence_reason: "grep-heuristic CJS/dynamic-import"
      });
    }
  }
}

// dist/backends/css-backend.js
import { readFileSync as readFileSync4 } from "fs";
import { relative as relative3, resolve as resolvePath, dirname as dirname3 } from "path";
import { createRequire as createRequire3 } from "module";
var _require2 = createRequire3(import.meta.url);
function loadLightningCss() {
  try {
    return _require2("lightningcss");
  } catch {
    return null;
  }
}
var _lcss;
function getLightningCss() {
  if (_lcss === void 0)
    _lcss = loadLightningCss();
  return _lcss;
}
function extractVarRefsFromValue(node, out) {
  if (!node || typeof node !== "object")
    return;
  if (Array.isArray(node)) {
    for (const item of node)
      extractVarRefsFromValue(item, out);
    return;
  }
  const obj = node;
  if (obj.type === "var" && obj.value) {
    const v = obj.value;
    if (v.name) {
      const nameNode = v.name;
      if (typeof nameNode.ident === "string")
        out.add(nameNode.ident);
    }
  }
  for (const val of Object.values(obj))
    extractVarRefsFromValue(val, out);
}
function collectClassesFromSelectors(selectors, defined, referenced) {
  for (const sel of selectors) {
    if (!Array.isArray(sel))
      continue;
    const classComponents = sel.filter((c) => typeof c === "object" && c !== null && c.type === "class");
    if (classComponents.length === 0)
      continue;
    const hasOtherComponents = sel.some((c) => typeof c === "object" && c !== null && c.type !== "class");
    for (const cls of classComponents) {
      const name = "." + cls.name;
      if (hasOtherComponents) {
        referenced.add(name);
      } else {
        defined.add(name);
      }
    }
  }
}
function extractWithLightningCss(source, filePath) {
  const lcss = getLightningCss();
  if (!lcss)
    return null;
  const isScss = filePath.endsWith(".scss");
  const classesDefined = /* @__PURE__ */ new Set();
  const classesReferenced = /* @__PURE__ */ new Set();
  const customPropsDefined = /* @__PURE__ */ new Set();
  const varUsages = /* @__PURE__ */ new Set();
  const composes = /* @__PURE__ */ new Set();
  const animNames = /* @__PURE__ */ new Set();
  const keyframesDefined = /* @__PURE__ */ new Set();
  try {
    lcss.transform({
      filename: filePath,
      code: Buffer.from(source),
      visitor: {
        Rule: {
          style(rule) {
            const r = rule;
            if (r.value) {
              const v = r.value;
              if (Array.isArray(v.selectors)) {
                collectClassesFromSelectors(v.selectors, classesDefined, classesReferenced);
              }
            }
          },
          keyframes(rule) {
            const r = rule;
            if (r.value) {
              const v = r.value;
              if (v.name) {
                const nameNode = v.name;
                if (typeof nameNode.value === "string") {
                  keyframesDefined.add(nameNode.value);
                } else if (typeof nameNode === "string") {
                  keyframesDefined.add(nameNode);
                }
              }
            }
          }
        },
        Declaration(decl) {
          const d = decl;
          if (d.property === "custom" && d.value) {
            const v = d.value;
            if (typeof v.name === "string")
              customPropsDefined.add(v.name);
          }
          extractVarRefsFromValue(d, varUsages);
          if (d.property === "unparsed" && d.value) {
            const v = d.value;
            const propId = v.propertyId;
            if (propId && propId.property === "composes" && Array.isArray(v.value)) {
              for (const token of v.value) {
                const t = token;
                if (t.type === "token" && t.value) {
                  const tv = t.value;
                  if (tv.type === "ident" && typeof tv.value === "string") {
                    composes.add(tv.value);
                  }
                }
              }
            }
          }
          if (d.property === "animation" && Array.isArray(d.value)) {
            for (const anim of d.value) {
              const a = anim;
              if (a.name) {
                const n = a.name;
                if (n.type === "ident" && typeof n.value === "string") {
                  animNames.add(n.value);
                }
              }
            }
          }
          if (d.property === "animation-name" && Array.isArray(d.value)) {
            for (const nameItem of d.value) {
              const n = nameItem;
              if (n.type === "ident" && typeof n.value === "string") {
                animNames.add(n.value);
              }
            }
          }
        }
      },
      errorRecovery: true,
      ...isScss ? { drafts: { nesting: true } } : {}
    });
    const { mixinsDefinedHere, includesHere, placeholdersDefinedHere, extendsHere, keyframesDefinedHere: regexKeyframes, animationNamesHere: regexAnimNames } = extractScssSymbolsWithRegex(source);
    for (const kf of regexKeyframes)
      keyframesDefined.add(kf);
    for (const an of regexAnimNames)
      animNames.add(an);
    return {
      classesDefinedHere: classesDefined,
      classesReferencedHere: classesReferenced,
      customPropsDefinedHere: customPropsDefined,
      varUsagesHere: varUsages,
      composesHere: composes,
      animationNamesHere: animNames,
      keyframesDefinedHere: keyframesDefined,
      mixinsDefinedHere,
      includesHere,
      placeholdersDefinedHere,
      extendsHere,
      usedLightningcss: true
    };
  } catch {
    return null;
  }
}
var ANIMATION_KEYWORDS = /* @__PURE__ */ new Set([
  "none",
  "inherit",
  "initial",
  "unset",
  "revert",
  "linear",
  "ease",
  "ease-in",
  "ease-out",
  "ease-in-out",
  "step-start",
  "step-end",
  "infinite",
  "forwards",
  "backwards",
  "both",
  "paused",
  "running",
  "normal",
  "reverse",
  "alternate",
  "alternate-reverse"
]);
function stripCssComments(source) {
  let stripped = source.replace(/\/\*[\s\S]*?\*\//g, (match) => {
    return match.replace(/[^\n]/g, " ");
  });
  stripped = stripped.replace(/\/\/[^\n]*/g, "");
  return stripped;
}
function extractScssSymbolsWithRegex(source) {
  const stripped = stripCssComments(source);
  const mixinsDefined = /* @__PURE__ */ new Set();
  const includes = /* @__PURE__ */ new Set();
  const placeholdersDefined = /* @__PURE__ */ new Set();
  const extends_ = /* @__PURE__ */ new Set();
  const keyframesDefined = /* @__PURE__ */ new Set();
  const animNames = /* @__PURE__ */ new Set();
  let m;
  const keyframesRe = /@(?:-\w+-)?keyframes\s+([\w-]+)/g;
  while ((m = keyframesRe.exec(stripped)) !== null) {
    keyframesDefined.add(m[1]);
  }
  const animRe = /animation(?:-name)?\s*:\s*([^;{}]+)/g;
  while ((m = animRe.exec(stripped)) !== null) {
    const valueStr = m[1].trim();
    for (const animValue of valueStr.split(",")) {
      const identRe = /\b([a-zA-Z_][\w-]*)\b/g;
      let im;
      while ((im = identRe.exec(animValue.trim())) !== null) {
        const token = im[1];
        if (!ANIMATION_KEYWORDS.has(token) && !/^\d/.test(token)) {
          animNames.add(token);
        }
      }
    }
  }
  const mixinRe = /@mixin\s+([\w-]+)/g;
  while ((m = mixinRe.exec(stripped)) !== null) {
    mixinsDefined.add(m[1]);
  }
  const includeRe = /@include\s+([\w-]+)/g;
  while ((m = includeRe.exec(stripped)) !== null) {
    includes.add(m[1]);
  }
  const placeholderDefRe = /^\s*%([\w-]+)\s*(?:\{|,)/gm;
  while ((m = placeholderDefRe.exec(stripped)) !== null) {
    placeholdersDefined.add(m[1]);
  }
  const extendRe = /@extend\s+%([\w-]+)/g;
  while ((m = extendRe.exec(stripped)) !== null) {
    extends_.add(m[1]);
  }
  return {
    mixinsDefinedHere: mixinsDefined,
    includesHere: includes,
    placeholdersDefinedHere: placeholdersDefined,
    extendsHere: extends_,
    keyframesDefinedHere: keyframesDefined,
    animationNamesHere: animNames
  };
}
function extractWithRegex(source) {
  const classesDefined = /* @__PURE__ */ new Set();
  const classesReferenced = /* @__PURE__ */ new Set();
  const customPropsDefined = /* @__PURE__ */ new Set();
  const varUsages = /* @__PURE__ */ new Set();
  const composes = /* @__PURE__ */ new Set();
  const customPropDefRe = /(--[\w-]+)\s*:/g;
  let m;
  while ((m = customPropDefRe.exec(source)) !== null) {
    customPropsDefined.add(m[1]);
  }
  const varRe = /var\(\s*(--[\w-]+)/g;
  while ((m = varRe.exec(source)) !== null) {
    varUsages.add(m[1]);
  }
  const lines = source.split("\n");
  for (const line of lines) {
    const trimmed = line.trim();
    if (/^\s*[-\w]+\s*:/.test(trimmed) && !trimmed.endsWith("{"))
      continue;
    const classRe = /\.([a-zA-Z_-][\w-]*)/g;
    while ((m = classRe.exec(trimmed)) !== null) {
      const name = "." + m[1];
      const simplified = trimmed.replace(/\s*\{.*/, "").trim();
      if (simplified === name || simplified === name + ":hover" || simplified === name + ":focus") {
        classesDefined.add(name);
      } else if (trimmed.includes(" ") || /[.:#[>+~]/.test(trimmed.replace(name, ""))) {
        classesReferenced.add(name);
      } else {
        classesDefined.add(name);
      }
    }
  }
  const composesRe = /composes:\s*([\w-]+)/g;
  while ((m = composesRe.exec(source)) !== null) {
    composes.add(m[1]);
  }
  const scssInfo = extractScssSymbolsWithRegex(source);
  return {
    classesDefinedHere: classesDefined,
    classesReferencedHere: classesReferenced,
    customPropsDefinedHere: customPropsDefined,
    varUsagesHere: varUsages,
    composesHere: composes,
    animationNamesHere: scssInfo.animationNamesHere,
    keyframesDefinedHere: scssInfo.keyframesDefinedHere,
    mixinsDefinedHere: scssInfo.mixinsDefinedHere,
    includesHere: scssInfo.includesHere,
    placeholdersDefinedHere: scssInfo.placeholdersDefinedHere,
    extendsHere: scssInfo.extendsHere,
    usedLightningcss: false
  };
}
function extractCssSymbolInfo(source, filePath) {
  const lcssResult = extractWithLightningCss(source, filePath);
  if (lcssResult) {
    if (filePath.endsWith(".scss") && lcssResult.customPropsDefinedHere.size === 0) {
      const regexResult = extractWithRegex(source);
      for (const prop of regexResult.customPropsDefinedHere) {
        lcssResult.customPropsDefinedHere.add(prop);
      }
    }
    return lcssResult;
  }
  return extractWithRegex(source);
}
function extractCssFileSymbols(absFile) {
  let content;
  try {
    content = readFileSync4(absFile, "utf8");
  } catch {
    return [];
  }
  const info = extractCssSymbolInfo(content, absFile);
  const symbols = [];
  for (const cls of info.classesDefinedHere)
    symbols.push(cls);
  for (const cls of info.classesReferencedHere) {
    if (!info.classesDefinedHere.has(cls))
      symbols.push(cls);
  }
  for (const prop of info.customPropsDefinedHere)
    symbols.push(prop);
  for (const kf of info.keyframesDefinedHere)
    symbols.push(`@keyframes-${kf}`);
  for (const mx of info.mixinsDefinedHere)
    symbols.push(`@mixin-${mx}`);
  for (const ph of info.placeholdersDefinedHere)
    symbols.push(`%${ph}`);
  return [...new Set(symbols)];
}
var CSS_EXTENSIONS = /\.(css|scss)$/;
function walkCssFiles(dir, eyeIgnore) {
  return walkFiles(dir, {
    extensions: CSS_EXTENSIONS,
    ...eyeIgnore ? { eyeIgnore } : {}
  });
}
async function analyzeCss(repoRoot, absFile, symbol, eyeIgnore) {
  const isClassSymbol = symbol.startsWith(".");
  const isCustomPropSymbol = symbol.startsWith("--");
  const isKeyframesSymbol = symbol.startsWith("@keyframes-");
  const isMixinSymbol = symbol.startsWith("@mixin-");
  const isPlaceholderSymbol = symbol.startsWith("%") && !symbol.startsWith("%-");
  if (!isClassSymbol && !isCustomPropSymbol && !isKeyframesSymbol && !isMixinSymbol && !isPlaceholderSymbol && symbol !== "") {
    return {
      target: symbol || absFile,
      total: 0,
      refs: [],
      skipped: true,
      reason: `CSS symbol must start with '.' (class), '--' (custom property), '@keyframes-' (@keyframes), '@mixin-' (SCSS mixin), or '%' (SCSS placeholder), got: ${symbol}`
    };
  }
  const keyframesName = isKeyframesSymbol ? symbol.slice("@keyframes-".length) : "";
  const mixinName = isMixinSymbol ? symbol.slice("@mixin-".length) : "";
  const placeholderName = isPlaceholderSymbol ? symbol.slice(1) : "";
  let source;
  try {
    source = readFileSync4(absFile, "utf8");
  } catch {
    return { target: symbol, total: 0, refs: [], skipped: true, reason: "file read error" };
  }
  const targetInfo = extractCssSymbolInfo(source, absFile);
  let knownKeyframesNames = null;
  if (isKeyframesSymbol) {
    knownKeyframesNames = /* @__PURE__ */ new Set();
    const cssFiles2 = walkCssFiles(repoRoot, eyeIgnore);
    const kfRe = /@(?:-\w+-)?keyframes\s+([\w-]+)/g;
    for (const f of cssFiles2) {
      let fSrc;
      try {
        fSrc = readFileSync4(f, "utf8");
      } catch {
        continue;
      }
      const stripped = stripCssComments(fSrc);
      let km;
      kfRe.lastIndex = 0;
      while ((km = kfRe.exec(stripped)) !== null) {
        knownKeyframesNames.add(km[1]);
      }
    }
  }
  const refs = [];
  const relTarget = relative3(repoRoot, absFile);
  if (symbol === "") {
    return { target: absFile, total: 0, refs: [] };
  }
  if (isClassSymbol) {
    if (targetInfo.classesDefinedHere.has(symbol)) {
      refs.push({
        file: relTarget,
        line: findSymbolLine(source, symbol, "class-def"),
        kind: "def",
        preview: `${symbol} { ... }`,
        confidence: "medium",
        confidence_reason: "lightningcss class selector"
      });
    }
    if (targetInfo.classesReferencedHere.has(symbol)) {
      refs.push({
        file: relTarget,
        line: findSymbolLine(source, symbol, "class-ref"),
        kind: "ref",
        preview: `compound selector containing ${symbol}`,
        confidence: "medium",
        confidence_reason: "lightningcss compound selector"
      });
    }
  } else if (isCustomPropSymbol) {
    if (targetInfo.customPropsDefinedHere.has(symbol)) {
      refs.push({
        file: relTarget,
        line: findSymbolLine(source, symbol, "custom-prop-def"),
        kind: "def",
        preview: `${symbol}: ...`,
        confidence: "medium",
        confidence_reason: "lightningcss custom property"
      });
    }
    if (targetInfo.varUsagesHere.has(symbol)) {
      refs.push({
        file: relTarget,
        line: findSymbolLine(source, symbol, "var-usage"),
        kind: "caller",
        preview: `var(${symbol})`,
        confidence: "medium",
        confidence_reason: "lightningcss var() usage"
      });
    }
  } else if (isKeyframesSymbol) {
    if (targetInfo.keyframesDefinedHere.has(keyframesName)) {
      refs.push({
        file: relTarget,
        line: findSymbolLine(source, keyframesName, "keyframes-def"),
        kind: "def",
        preview: `@keyframes ${keyframesName} { ... }`,
        confidence: "medium",
        confidence_reason: "regex @keyframes definition"
      });
    }
    if (targetInfo.animationNamesHere.has(keyframesName) && (knownKeyframesNames === null || knownKeyframesNames.has(keyframesName))) {
      refs.push({
        file: relTarget,
        line: findSymbolLine(source, keyframesName, "animation-usage"),
        kind: "caller",
        preview: `animation: ${keyframesName} ...`,
        confidence: "medium",
        confidence_reason: "regex animation name usage"
      });
    }
  } else if (isMixinSymbol) {
    if (targetInfo.mixinsDefinedHere.has(mixinName)) {
      refs.push({
        file: relTarget,
        line: findSymbolLine(source, mixinName, "mixin-def"),
        kind: "def",
        preview: `@mixin ${mixinName} { ... }`,
        confidence: "medium",
        confidence_reason: "regex @mixin definition"
      });
    }
    if (targetInfo.includesHere.has(mixinName)) {
      refs.push({
        file: relTarget,
        line: findSymbolLine(source, mixinName, "include-usage"),
        kind: "caller",
        preview: `@include ${mixinName}`,
        confidence: "medium",
        confidence_reason: "regex @include usage"
      });
    }
  } else if (isPlaceholderSymbol) {
    if (targetInfo.placeholdersDefinedHere.has(placeholderName)) {
      refs.push({
        file: relTarget,
        line: findSymbolLine(source, placeholderName, "placeholder-def"),
        kind: "def",
        preview: `%${placeholderName} { ... }`,
        confidence: "medium",
        confidence_reason: "regex %placeholder definition"
      });
    }
    if (targetInfo.extendsHere.has(placeholderName)) {
      refs.push({
        file: relTarget,
        line: findSymbolLine(source, placeholderName, "extend-usage"),
        kind: "caller",
        preview: `@extend %${placeholderName}`,
        confidence: "medium",
        confidence_reason: "regex @extend usage"
      });
    }
  }
  const cssFiles = walkCssFiles(repoRoot, eyeIgnore);
  for (const cssFile of cssFiles) {
    if (cssFile === absFile)
      continue;
    let fileSource;
    try {
      fileSource = readFileSync4(cssFile, "utf8");
    } catch {
      continue;
    }
    const fileInfo = extractCssSymbolInfo(fileSource, cssFile);
    const relFile = relative3(repoRoot, cssFile);
    if (isClassSymbol) {
      if (fileInfo.classesDefinedHere.has(symbol)) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, symbol, "class-def"),
          kind: "def",
          preview: `${symbol} { ... }`,
          confidence: "medium",
          confidence_reason: "lightningcss class selector (cross-file)"
        });
      }
      if (fileInfo.classesReferencedHere.has(symbol)) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, symbol, "class-ref"),
          kind: "ref",
          preview: `compound selector containing ${symbol}`,
          confidence: "medium",
          confidence_reason: "lightningcss compound selector (cross-file)"
        });
      }
      const symbolWithoutDot = symbol.slice(1);
      if (fileInfo.composesHere.has(symbolWithoutDot)) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, symbolWithoutDot, "composes"),
          kind: "caller",
          preview: `composes: ${symbolWithoutDot}`,
          confidence: "medium",
          confidence_reason: "CSS Modules composes"
        });
      }
    } else if (isCustomPropSymbol) {
      if (fileInfo.customPropsDefinedHere.has(symbol)) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, symbol, "custom-prop-def"),
          kind: "def",
          preview: `${symbol}: ...`,
          confidence: "medium",
          confidence_reason: "lightningcss custom property (cross-file)"
        });
      }
      if (fileInfo.varUsagesHere.has(symbol)) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, symbol, "var-usage"),
          kind: "caller",
          preview: `var(${symbol})`,
          confidence: "medium",
          confidence_reason: "lightningcss var() usage (cross-file)"
        });
      }
    } else if (isKeyframesSymbol) {
      if (fileInfo.keyframesDefinedHere.has(keyframesName)) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, keyframesName, "keyframes-def"),
          kind: "def",
          preview: `@keyframes ${keyframesName} { ... }`,
          confidence: "medium",
          confidence_reason: "regex @keyframes definition (cross-file)"
        });
      }
      if (fileInfo.animationNamesHere.has(keyframesName) && (knownKeyframesNames === null || knownKeyframesNames.has(keyframesName))) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, keyframesName, "animation-usage"),
          kind: "caller",
          preview: `animation: ${keyframesName} ...`,
          confidence: "medium",
          confidence_reason: "regex animation name usage (cross-file)"
        });
      }
    } else if (isMixinSymbol) {
      if (fileInfo.mixinsDefinedHere.has(mixinName)) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, mixinName, "mixin-def"),
          kind: "def",
          preview: `@mixin ${mixinName} { ... }`,
          confidence: "medium",
          confidence_reason: "regex @mixin definition (cross-file)"
        });
      }
      if (fileInfo.includesHere.has(mixinName)) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, mixinName, "include-usage"),
          kind: "caller",
          preview: `@include ${mixinName}`,
          confidence: "medium",
          confidence_reason: "regex @include usage (cross-file)"
        });
      }
    } else if (isPlaceholderSymbol) {
      if (fileInfo.placeholdersDefinedHere.has(placeholderName)) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, placeholderName, "placeholder-def"),
          kind: "def",
          preview: `%${placeholderName} { ... }`,
          confidence: "medium",
          confidence_reason: "regex %placeholder definition (cross-file)"
        });
      }
      if (fileInfo.extendsHere.has(placeholderName)) {
        refs.push({
          file: relFile,
          line: findSymbolLine(fileSource, placeholderName, "extend-usage"),
          kind: "caller",
          preview: `@extend %${placeholderName}`,
          confidence: "medium",
          confidence_reason: "regex @extend usage (cross-file)"
        });
      }
    }
  }
  if (isClassSymbol) {
    const jsxCallers = findJsxClassNameCallers(repoRoot, symbol, eyeIgnore);
    for (const caller of jsxCallers) {
      refs.push(caller);
    }
    const cnClsxCallers = findCnClsxCallers(repoRoot, symbol, eyeIgnore);
    const existingKeys = new Set(refs.map((r) => `${r.file}:${r.line}`));
    for (const caller of cnClsxCallers) {
      const key = `${caller.file}:${caller.line}`;
      if (!existingKeys.has(key)) {
        refs.push(caller);
        existingKeys.add(key);
      }
    }
    if (/\.module\.(css|scss)$/.test(absFile)) {
      const cssModuleCallers = findCssModuleCallers(repoRoot, absFile, symbol, eyeIgnore);
      for (const caller of cssModuleCallers) {
        refs.push(caller);
      }
    }
  }
  const confidenceSummary = buildConfidenceSummary(refs);
  return {
    target: symbol,
    total: refs.length,
    refs,
    confidence_summary: confidenceSummary
  };
}
function camelToKebab(camel) {
  return camel.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);
}
function findCssModuleCallers(repoRoot, absFile, symbol, eyeIgnore) {
  const className = symbol.slice(1);
  const baseExcludeGlobs = buildExcludeGlobs(eyeIgnore);
  const cssBasename = absFile.split("/").pop();
  const candidateFiles = rgFiles(repoRoot, {
    patterns: [cssBasename],
    includeGlobs: ["*.tsx", "*.jsx", "*.ts", "*.js"],
    excludeGlobs: baseExcludeGlobs,
    noIgnore: true
  });
  if (candidateFiles.length === 0)
    return [];
  const absFiles = candidateFiles.map((f) => f.startsWith("/") ? f : `${repoRoot}/${f}`);
  const results = [];
  const seen = /* @__PURE__ */ new Set();
  for (const candidateAbs of absFiles) {
    let fileSource;
    try {
      fileSource = readFileSync4(candidateAbs, "utf8");
    } catch {
      continue;
    }
    const importRe = /import\s+([\w$]+)\s+from\s+['"]([^'"]+\.module\.(?:css|scss))['"]/g;
    let m;
    const matchedBindings = [];
    while ((m = importRe.exec(fileSource)) !== null) {
      const bindingName = m[1];
      const importSource = m[2];
      const resolvedPath = resolvePath(dirname3(candidateAbs), importSource);
      if (resolvedPath === absFile) {
        matchedBindings.push(bindingName);
      }
    }
    if (matchedBindings.length === 0)
      continue;
    const fileLines = fileSource.split("\n");
    for (const binding of matchedBindings) {
      for (let lineIdx = 0; lineIdx < fileLines.length; lineIdx++) {
        const line = fileLines[lineIdx];
        if (!line)
          continue;
        if (isCommentLine(line))
          continue;
        const memberAccessRe = new RegExp(`(?<![\\w$])${escapeRegex2(binding)}\\.([a-zA-Z_$][\\w$]*)`, "g");
        let ma;
        while ((ma = memberAccessRe.exec(line)) !== null) {
          const prop = ma[1];
          const kebabProp = camelToKebab(prop);
          if (kebabProp === className) {
            const relFile = relative3(repoRoot, candidateAbs);
            const key = `${relFile}:${lineIdx + 1}`;
            if (!seen.has(key)) {
              seen.add(key);
              results.push({
                file: relFile,
                line: lineIdx + 1,
                kind: "caller",
                preview: `[css-module] ${line.trim().slice(0, 80)}`,
                confidence: "medium",
                confidence_reason: "CSS Modules member-access (camelCase mapped)"
              });
            }
          }
        }
        const bracketAccessRe = new RegExp(`(?<![\\w$])${escapeRegex2(binding)}\\[['"]([^'"]+)['"]\\]`, "g");
        while ((ma = bracketAccessRe.exec(line)) !== null) {
          const literal = ma[1];
          if (literal === className) {
            const relFile = relative3(repoRoot, candidateAbs);
            const key = `${relFile}:${lineIdx + 1}`;
            if (!seen.has(key)) {
              seen.add(key);
              results.push({
                file: relFile,
                line: lineIdx + 1,
                kind: "caller",
                preview: `[css-module] ${line.trim().slice(0, 80)}`,
                confidence: "medium",
                confidence_reason: "CSS Modules bracket-access (literal string)"
              });
            }
          }
        }
      }
    }
  }
  return results;
}
function escapeRegex2(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function findJsxClassNameCallers(repoRoot, symbol, eyeIgnore) {
  const className = symbol.slice(1);
  const baseExcludeGlobs = buildExcludeGlobs(eyeIgnore);
  const candidateFiles = rgFiles(repoRoot, {
    patterns: ["className=", "class="],
    includeGlobs: ["*.tsx", "*.jsx", "*.html", "*.vue"],
    excludeGlobs: baseExcludeGlobs,
    noIgnore: true
  });
  if (candidateFiles.length === 0)
    return [];
  const absFiles = candidateFiles.map((f) => f.startsWith("/") ? f : `${repoRoot}/${f}`);
  const doubleQuotePattern = `(?:className|class)="[^"]*(?:^|[ \\t])${className}(?:[ \\t]|")[^"]*"`;
  const singleQuotePattern = `(?:className|class)='[^']*(?:^|[ \\t])${className}(?:[ \\t]|')[^']*'`;
  const doubleHits = rgLinesInFiles(absFiles, `(?:className|class)="[^"]*${className}[^"]*"`, false);
  const singleHits = rgLinesInFiles(absFiles, `(?:className|class)='[^']*${className}[^']*'`, false);
  void doubleQuotePattern;
  void singleQuotePattern;
  const results = [];
  const seen = /* @__PURE__ */ new Set();
  for (const hit of [...doubleHits, ...singleHits]) {
    if (isCommentLine(hit.content))
      continue;
    if (!isWholeClassToken(hit.content, className))
      continue;
    const relFile = relative3(repoRoot, hit.absFile);
    const key = `${relFile}:${hit.line}`;
    if (seen.has(key))
      continue;
    seen.add(key);
    results.push({
      file: relFile,
      line: hit.line,
      kind: "caller",
      preview: `[className] ${hit.content.trim().slice(0, 80)}`,
      confidence: "medium",
      confidence_reason: "literal className/class attribute (JSX/HTML)"
    });
  }
  return results;
}
function findCnClsxCallers(repoRoot, symbol, eyeIgnore) {
  const className = symbol.slice(1);
  const baseExcludeGlobs = buildExcludeGlobs(eyeIgnore);
  const cnExcludeGlobs = [
    ...baseExcludeGlobs,
    "*.test.ts",
    "*.test.tsx",
    "*.spec.ts",
    "*.spec.tsx"
  ];
  const candidateFiles = rgFiles(repoRoot, {
    patterns: ["cn(", "clsx(", "classNames("],
    includeGlobs: ["*.tsx", "*.jsx", "*.ts", "*.js"],
    excludeGlobs: cnExcludeGlobs,
    noIgnore: true
  });
  if (candidateFiles.length === 0)
    return [];
  const absFiles = candidateFiles.map((f) => f.startsWith("/") ? f : `${repoRoot}/${f}`);
  const escapedClass = className.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
  const doubleQuotePattern = `\\b(?:cn|clsx|classNames)\\s*\\([^)]*"[^"]*${escapedClass}[^"]*"`;
  const singleQuotePattern = `\\b(?:cn|clsx|classNames)\\s*\\([^)]*'[^']*${escapedClass}[^']*'`;
  const doubleHits = rgLinesInFiles(absFiles, doubleQuotePattern, false);
  const singleHits = rgLinesInFiles(absFiles, singleQuotePattern, false);
  const results = [];
  const seen = /* @__PURE__ */ new Set();
  for (const hit of [...doubleHits, ...singleHits]) {
    if (isCommentLine(hit.content))
      continue;
    if (!isCnLiteralArgMatch(hit.content, className))
      continue;
    const relFile = relative3(repoRoot, hit.absFile);
    const key = `${relFile}:${hit.line}`;
    if (seen.has(key))
      continue;
    seen.add(key);
    results.push({
      file: relFile,
      line: hit.line,
      kind: "caller",
      preview: `[cn/clsx] ${hit.content.trim().slice(0, 80)}`,
      confidence: "medium",
      confidence_reason: "literal string arg in cn()/clsx()/classNames() call"
    });
  }
  return results;
}
function isCnLiteralArgMatch(lineContent, className) {
  const callRe = /\b(cn|clsx|classNames)\s*\(/g;
  let callMatch;
  while ((callMatch = callRe.exec(lineContent)) !== null) {
    const callStart = callMatch.index;
    const before = lineContent.slice(0, callStart);
    const doubleQuoteCount = (before.match(/(?<!\\)"/g) ?? []).length;
    if (doubleQuoteCount % 2 !== 0) {
      continue;
    }
    const singleQuoteCount = (before.match(/(?<!\\)'/g) ?? []).length;
    if (singleQuoteCount % 2 !== 0) {
      continue;
    }
    const openParenIdx = callStart + callMatch[0].length - 1;
    let depth = 1;
    let i = openParenIdx + 1;
    for (; i < lineContent.length && depth > 0; i++) {
      if (lineContent[i] === "(")
        depth++;
      else if (lineContent[i] === ")")
        depth--;
    }
    const callBody = lineContent.slice(openParenIdx + 1, i - 1);
    const argStartRe = /(?:^|[,(]|&&|\|\||\?)\s*(['"])/g;
    let argMatch;
    while ((argMatch = argStartRe.exec(callBody)) !== null) {
      const quoteChar = argMatch[1];
      const literalStart = argMatch.index + argMatch[0].length;
      let end = literalStart;
      while (end < callBody.length) {
        if (callBody[end] === quoteChar && callBody[end - 1] !== "\\")
          break;
        end++;
      }
      if (end >= callBody.length)
        continue;
      const literalContent = callBody.slice(literalStart, end);
      const tokens = literalContent.split(/\s+/);
      if (tokens.includes(className))
        return true;
    }
  }
  return false;
}
function isCommentLine(lineContent) {
  const trimmed = lineContent.trimStart();
  return trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*");
}
function isWholeClassToken(lineContent, className) {
  const attrDoubleRe = /(?:className|class)="([^"]*)"/g;
  const attrSingleRe = /(?:className|class)='([^']*)'/g;
  let m;
  while ((m = attrDoubleRe.exec(lineContent)) !== null) {
    const tokens = m[1].split(/\s+/);
    if (tokens.includes(className))
      return true;
  }
  while ((m = attrSingleRe.exec(lineContent)) !== null) {
    const tokens = m[1].split(/\s+/);
    if (tokens.includes(className))
      return true;
  }
  return false;
}
function findSymbolLine(source, symbol, mode) {
  const lines = source.split("\n");
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    if (!line)
      continue;
    switch (mode) {
      case "class-def":
      case "class-ref": {
        const name = symbol.startsWith(".") ? symbol.slice(1) : symbol;
        if (line.includes("." + name))
          return i + 1;
        break;
      }
      case "custom-prop-def":
        if (line.includes(symbol + ":") || line.includes(symbol + " :"))
          return i + 1;
        break;
      case "var-usage":
        if (line.includes("var(" + symbol) || line.includes("var( " + symbol))
          return i + 1;
        break;
      case "composes":
        if (line.includes("composes") && line.includes(symbol))
          return i + 1;
        break;
      case "keyframes-def":
        if (/@(?:-\w+-)?keyframes/.test(line) && line.includes(symbol))
          return i + 1;
        break;
      case "animation-usage":
        if (/animation(?:-name)?/.test(line) && line.includes(symbol))
          return i + 1;
        break;
      case "mixin-def":
        if (/@mixin/.test(line) && line.includes(symbol))
          return i + 1;
        break;
      case "include-usage":
        if (/@include/.test(line) && line.includes(symbol))
          return i + 1;
        break;
      case "placeholder-def":
        if (line.trimStart().startsWith("%" + symbol))
          return i + 1;
        break;
      case "extend-usage":
        if (/@extend/.test(line) && line.includes("%" + symbol))
          return i + 1;
        break;
    }
  }
  return 1;
}
async function scanCssDeadCode(dir, eyeIgnore) {
  const CSS_CAVEAT = "WARNING: This list shows symbols with no static usage found. Dynamic className patterns (cn()/clsx()/conditional expressions/styles[variable]) are NOT tracked by eye. Classes used only via dynamic patterns will appear here but may not be dead. Treat this as a candidate list \u2014 manual verification required before deleting.";
  const cssFiles = walkCssFiles(dir, eyeIgnore);
  if (cssFiles.length === 0) {
    return {
      dead_candidates: [],
      total: 0,
      scanned_files: 0,
      scanned_symbols: 0,
      caveat: CSS_CAVEAT
    };
  }
  const fileData = [];
  for (const absFile of cssFiles) {
    let content;
    try {
      content = readFileSync4(absFile, "utf8");
    } catch {
      continue;
    }
    const info = extractCssSymbolInfo(content, absFile);
    const relFile = relative3(dir, absFile);
    fileData.push({ absFile, relFile, info });
  }
  const definedClasses = /* @__PURE__ */ new Map();
  const definedCustomProps = /* @__PURE__ */ new Map();
  for (const { relFile, info } of fileData) {
    for (const cls of info.classesDefinedHere) {
      if (!definedClasses.has(cls))
        definedClasses.set(cls, relFile);
    }
    for (const prop of info.customPropsDefinedHere) {
      if (!definedCustomProps.has(prop))
        definedCustomProps.set(prop, relFile);
    }
  }
  const callerCount = /* @__PURE__ */ new Map();
  for (const { relFile, info } of fileData) {
    for (const cls of info.classesReferencedHere) {
      const defFile = definedClasses.get(cls);
      if (defFile && defFile !== relFile) {
        callerCount.set(cls, (callerCount.get(cls) ?? 0) + 1);
      }
    }
    for (const prop of info.varUsagesHere) {
      if (definedCustomProps.has(prop)) {
        callerCount.set(prop, (callerCount.get(prop) ?? 0) + 1);
      }
    }
    for (const cls of info.composesHere) {
      const dotCls = "." + cls;
      if (definedClasses.has(dotCls)) {
        callerCount.set(dotCls, (callerCount.get(dotCls) ?? 0) + 1);
      }
    }
  }
  const baseExcludeGlobs = buildExcludeGlobs(eyeIgnore);
  let jsxCandidateFiles = [];
  try {
    const relFiles = rgFiles(dir, {
      patterns: ["className=", "class="],
      includeGlobs: ["*.tsx", "*.jsx", "*.html", "*.vue"],
      excludeGlobs: baseExcludeGlobs,
      noIgnore: true
    });
    jsxCandidateFiles = relFiles.map((f) => f.startsWith("/") ? f : `${dir}/${f}`);
  } catch {
  }
  if (jsxCandidateFiles.length > 0 && definedClasses.size > 0) {
    const allClassNames = new Set([...definedClasses.keys()].map((c) => c.slice(1)));
    for (const absJsxFile of jsxCandidateFiles) {
      let fileSource;
      try {
        fileSource = readFileSync4(absJsxFile, "utf8");
      } catch {
        continue;
      }
      const attrDoubleRe = /(?:className|class)="([^"]*)"/g;
      const attrSingleRe = /(?:className|class)='([^']*)'/g;
      let m;
      const lineArr = fileSource.split("\n");
      for (const line of lineArr) {
        if (isCommentLine(line))
          continue;
        while ((m = attrDoubleRe.exec(line)) !== null) {
          for (const token of m[1].split(/\s+/)) {
            if (token && allClassNames.has(token)) {
              const dotCls = "." + token;
              callerCount.set(dotCls, (callerCount.get(dotCls) ?? 0) + 1);
            }
          }
        }
        attrDoubleRe.lastIndex = 0;
        while ((m = attrSingleRe.exec(line)) !== null) {
          for (const token of m[1].split(/\s+/)) {
            if (token && allClassNames.has(token)) {
              const dotCls = "." + token;
              callerCount.set(dotCls, (callerCount.get(dotCls) ?? 0) + 1);
            }
          }
        }
        attrSingleRe.lastIndex = 0;
      }
    }
  }
  const dead_candidates = [];
  for (const [cls, relFile] of definedClasses.entries()) {
    const count = callerCount.get(cls) ?? 0;
    if (count === 0) {
      dead_candidates.push({ file: relFile, symbol: cls, kind: "class" });
    }
  }
  for (const [prop, relFile] of definedCustomProps.entries()) {
    const count = callerCount.get(prop) ?? 0;
    if (count === 0) {
      dead_candidates.push({ file: relFile, symbol: prop, kind: "custom-property" });
    }
  }
  dead_candidates.sort((a, b) => a.file.localeCompare(b.file) || a.symbol.localeCompare(b.symbol));
  const scannedSymbols = definedClasses.size + definedCustomProps.size;
  return {
    dead_candidates,
    total: dead_candidates.length,
    scanned_files: cssFiles.length,
    scanned_symbols: scannedSymbols,
    caveat: CSS_CAVEAT
  };
}

// dist/repo.js
import { existsSync as existsSync6, statSync as statSync2 } from "fs";
import { dirname as dirname4, resolve as resolve2 } from "path";
function findRepoRoot(startDir) {
  let dir = resolve2(startDir);
  while (true) {
    if (existsSync6(resolve2(dir, ".git"))) {
      return dir;
    }
    const parent = dirname4(dir);
    if (parent === dir)
      return null;
    dir = parent;
  }
}
function detectLanguage(filePath) {
  const ext = filePath.split(".").pop()?.toLowerCase() || "";
  if (/^(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(ext))
    return "ts";
  if (ext === "py")
    return "py";
  if (ext === "cs")
    return "cs";
  if (ext === "go")
    return "go";
  if (ext === "php")
    return "php";
  if (ext === "swift")
    return "swift";
  if (ext === "java")
    return "java";
  if (ext === "rs")
    return "rs";
  if (ext === "kt" || ext === "kts")
    return "kt";
  if (ext === "css" || ext === "scss")
    return "css";
  return "unsupported";
}

// dist/analyze.js
var EyeError = class extends Error {
  code;
  constructor(message, code = 1) {
    super(message);
    this.code = code;
    this.name = "EyeError";
  }
};
async function analyze(input) {
  const { file, symbol = "", options = {}, backend = "auto", invertedIndex, parseCache, eyeIgnore, crossFileEventListeners } = input;
  const absFile = resolve3(process.cwd(), file);
  if (!existsSync7(absFile) || !statSync3(absFile).isFile()) {
    throw new EyeError(`file not found: ${basename2(file)}`, 2);
  }
  const repoRoot = findRepoRoot(dirname5(absFile)) ?? dirname5(absFile);
  const lang = detectLanguage(absFile);
  if (lang === "unsupported") {
    return { target: basename2(absFile), total: 0, refs: [], skipped: true, reason: "shell not analysed" };
  }
  if (lang === "css") {
    return analyzeCss(repoRoot, absFile, symbol, eyeIgnore);
  }
  const cacheDir = getEntryCacheDir(repoRoot);
  if (!options.noCache) {
    const key = getCacheKey(absFile, symbol, repoRoot);
    const cached = readCache(cacheDir, key);
    if (cached)
      return cached;
  }
  let result;
  if (backend === "tree-sitter") {
    result = await analyzeTreeSitter(repoRoot, absFile, symbol, invertedIndex, parseCache, eyeIgnore, crossFileEventListeners);
  } else if (backend === "ts-morph") {
    if (lang === "ts") {
      result = analyzeTs(repoRoot, absFile, symbol, eyeIgnore);
    } else {
      result = await analyzeTreeSitter(repoRoot, absFile, symbol, invertedIndex, parseCache, eyeIgnore, crossFileEventListeners);
    }
  } else {
    if (lang === "ts") {
      result = analyzeTs(repoRoot, absFile, symbol, eyeIgnore);
    } else {
      result = await analyzeTreeSitter(repoRoot, absFile, symbol, invertedIndex, parseCache, eyeIgnore, crossFileEventListeners);
    }
  }
  if (!options.noCache) {
    const key = getCacheKey(absFile, symbol, repoRoot);
    writeCache(cacheDir, key, result);
  }
  return result;
}

// dist/detect-changes.js
import { execFileSync as execFileSync3 } from "child_process";
import { resolve as resolve4 } from "path";
import { existsSync as existsSync8 } from "fs";

// dist/path-utils.js
function parseFileSymbol(target) {
  let drivePrefix = "";
  let rest = target;
  if (/^[A-Za-z]:\\/.test(target)) {
    drivePrefix = target.slice(0, 2);
    rest = target.slice(2);
  }
  const idx = rest.lastIndexOf(":");
  let file;
  let symbol;
  if (idx >= 0 && rest.slice(idx + 1).length > 0) {
    symbol = rest.slice(idx + 1);
    file = drivePrefix + rest.slice(0, idx);
  } else {
    symbol = "";
    file = drivePrefix + rest;
  }
  return { file, symbol };
}

// dist/semaphore.js
var Semaphore = class {
  limit;
  queue = [];
  active = 0;
  constructor(limit) {
    this.limit = limit;
  }
  async acquire() {
    if (this.active < this.limit) {
      this.active++;
      return;
    }
    return new Promise((resolve12) => this.queue.push(resolve12));
  }
  release() {
    this.active--;
    const next = this.queue.shift();
    if (next) {
      this.active++;
      next();
    }
  }
  /** Convenience: acquire, run fn, release. Returns the fn's resolved value. */
  async run(fn) {
    await this.acquire();
    try {
      return await fn();
    } finally {
      this.release();
    }
  }
};

// dist/detect-changes.js
function refExists(repoRoot, ref) {
  try {
    execFileSync3("git", ["rev-parse", "--verify", ref], {
      cwd: repoRoot,
      encoding: "utf8",
      stdio: ["pipe", "pipe", "pipe"]
    });
    return true;
  } catch {
    return false;
  }
}
function computeMergeBase(repoRoot, base) {
  try {
    const sha = execFileSync3("git", ["merge-base", base, "HEAD"], {
      cwd: repoRoot,
      encoding: "utf8",
      stdio: ["pipe", "pipe", "pipe"]
    }).trim();
    if (/^[0-9a-f]{40}$/.test(sha))
      return sha;
    return null;
  } catch {
    return null;
  }
}
var PRIORITY_KEYWORDS = ["auth", "db", "payment", "secret", "token", "cred", "password", "session"];
function mapChangeType(letter) {
  switch (letter) {
    case "A":
      return "added";
    case "C":
      return "copied";
    case "R":
      return "renamed";
    case "T":
      return "type_changed";
    case "M":
    default:
      return "modified";
  }
}
function riskLevel(total) {
  if (total >= 10)
    return "high";
  if (total >= 4)
    return "medium";
  if (total >= 1)
    return "low";
  return "none";
}
function getChangedFilesWithStatus(repoRoot, opts, diffFilter = "ACMRT") {
  const gitArgs = opts.staged ? ["diff", "--cached", "--name-status", `--diff-filter=${diffFilter}`] : ["diff", "--name-status", `--diff-filter=${diffFilter}`, opts.from, opts.to];
  let raw;
  try {
    raw = execFileSync3("git", gitArgs, {
      cwd: repoRoot,
      encoding: "utf8",
      stdio: ["pipe", "pipe", "pipe"],
      maxBuffer: 10 * 1024 * 1024
    });
  } catch (err) {
    const stderr = err instanceof Error && "stderr" in err ? String(err.stderr) : "";
    if (/unknown revision|bad revision|ambiguous argument|not a valid object/i.test(stderr)) {
      const refHint = stderr.match(/'([^']+)'/)?.[1] ?? gitArgs.join(" ");
      throw new EyeError(`unknown git ref "${refHint}"`, 2);
    }
    return [];
  }
  const results = [];
  for (const line of raw.split("\n").filter(Boolean)) {
    const parts = line.split("	");
    if (parts.length < 2)
      continue;
    const letter = parts[0][0].toUpperCase();
    const relFile = (letter === "R" || letter === "C") && parts[2] ? parts[2] : parts[1];
    if (!relFile)
      continue;
    results.push({ relFile: relFile.trim(), changeType: mapChangeType(letter), status: letter });
  }
  return results;
}
function applyMaxFilesFilter(files, maxFiles) {
  if (files.length <= maxFiles) {
    return { files, truncated: false };
  }
  const high = [];
  const normal = [];
  for (const entry of files) {
    const lower = entry.relFile.toLowerCase();
    if (PRIORITY_KEYWORDS.some((kw) => lower.includes(kw))) {
      high.push(entry);
    } else {
      normal.push(entry);
    }
  }
  const combined = [...high, ...normal].slice(0, maxFiles);
  return { files: combined, truncated: true };
}
function resolveRef(repoRoot, ref) {
  try {
    return execFileSync3("git", ["rev-parse", "--short", ref], {
      cwd: repoRoot,
      encoding: "utf8",
      stdio: ["pipe", "pipe", "pipe"]
    }).trim();
  } catch {
    return ref;
  }
}
function buildDiffRange(repoRoot, opts, mergeBaseSha) {
  if (opts.staged)
    return "staged";
  if (opts.base !== void 0 && mergeBaseSha) {
    const headSha = resolveRef(repoRoot, "HEAD");
    return `merge-base(${opts.base},HEAD)=${mergeBaseSha.slice(0, 7)}..${headSha}`;
  }
  const fromSha = resolveRef(repoRoot, opts.from);
  const toSha = resolveRef(repoRoot, opts.to);
  return `${fromSha}..${toSha}`;
}
async function detectChanges(repoRoot, opts) {
  let mergeBaseSha;
  let effectiveOpts = opts;
  if (opts.base !== void 0 && !opts.staged) {
    const mb = computeMergeBase(repoRoot, opts.base);
    if (mb !== null) {
      mergeBaseSha = mb;
      effectiveOpts = {
        ...opts,
        from: mb,
        to: "HEAD",
        fromExplicit: true,
        toExplicit: true
      };
    } else {
      process.stderr.write(`[warn] --base ${opts.base}: git merge-base failed (detached HEAD or shallow clone?); falling back to two-dot diff (${opts.from}..${opts.to})
`);
    }
  }
  const diffRange = buildDiffRange(repoRoot, effectiveOpts, mergeBaseSha);
  let changedEntries;
  try {
    changedEntries = getChangedFilesWithStatus(repoRoot, effectiveOpts);
  } catch (err) {
    if (err instanceof EyeError && !effectiveOpts.fromExplicit && !effectiveOpts.toExplicit) {
      return {
        diff_range: effectiveOpts.staged ? "staged" : `${effectiveOpts.from}..${effectiveOpts.to}`,
        changed_files: [],
        affected_symbols: [],
        summary: { total_symbols: 0, risk_high: 0, risk_medium: 0, risk_low: 0 }
      };
    }
    throw err;
  }
  const { files: cappedEntries, truncated } = applyMaxFilesFilter(changedEntries, effectiveOpts.maxFiles);
  if (truncated) {
    process.stderr.write(`[warn] diff contains ${changedEntries.length} files, exceeds --max-files ${effectiveOpts.maxFiles}; prioritizing auth/db/payment files and analysing first ${effectiveOpts.maxFiles}
`);
  }
  const changedFiles = cappedEntries.map((e) => e.relFile);
  const affectedSymbols = [];
  const seenKey = /* @__PURE__ */ new Set();
  const _detectConcurrency = parseInt(process.env.EYE_DETECT_CONCURRENCY ?? "4", 10);
  const sem = new Semaphore(isNaN(_detectConcurrency) || _detectConcurrency <= 0 ? 4 : _detectConcurrency);
  const tasks = cappedEntries.map(({ relFile, changeType }) => sem.run(async () => {
    const absFile = resolve4(repoRoot, relFile);
    if (!existsSync8(absFile))
      return { type: "skip" };
    let result;
    try {
      result = await analyze({
        file: absFile,
        symbol: "",
        // default: bypass cache for detect-changes CLI path
        options: { noCache: effectiveOpts.noCache ?? true }
      });
    } catch {
      return { type: "skip" };
    }
    if (result.skipped)
      return { type: "skip" };
    return {
      type: "ok",
      symbol: {
        symbol: parseFileSymbol(result.target).symbol || result.target,
        file: relFile,
        change_type: changeType,
        blast_radius: {
          total: result.total,
          risk_level: riskLevel(result.total),
          refs: result.refs
        }
      }
    };
  }));
  const settled = await Promise.allSettled(tasks);
  for (const outcome of settled) {
    if (outcome.status === "rejected")
      continue;
    if (outcome.value.type === "skip")
      continue;
    const dedupeKey = outcome.value.symbol.file;
    if (seenKey.has(dedupeKey))
      continue;
    seenKey.add(dedupeKey);
    affectedSymbols.push(outcome.value.symbol);
  }
  const summary = {
    total_symbols: affectedSymbols.length,
    risk_high: affectedSymbols.filter((s) => s.blast_radius.risk_level === "high").length,
    risk_medium: affectedSymbols.filter((s) => s.blast_radius.risk_level === "medium").length,
    risk_low: affectedSymbols.filter((s) => s.blast_radius.risk_level === "low").length
  };
  return { diff_range: diffRange, changed_files: changedFiles, affected_symbols: affectedSymbols, summary };
}
function parseDetectChangesArgs(args) {
  const opts = {
    from: "HEAD~1",
    fromExplicit: false,
    to: "HEAD",
    toExplicit: false,
    staged: false,
    json: false,
    maxFiles: 50,
    debug: false,
    maxBlastRadius: null
  };
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === "--staged") {
      opts.staged = true;
    } else if (arg === "--json") {
      opts.json = true;
    } else if (arg === "--debug") {
      opts.debug = true;
    } else if (arg === "--no-cache") {
      opts.noCache = true;
    } else if (arg === "--cache") {
      opts.noCache = false;
    } else if (arg === "--base" && args[i + 1]) {
      opts.base = args[++i];
    } else if (arg.startsWith("--base=")) {
      opts.base = arg.slice("--base=".length);
    } else if (arg === "--from" && args[i + 1]) {
      opts.from = args[++i];
      opts.fromExplicit = true;
    } else if (arg === "--to" && args[i + 1]) {
      opts.to = args[++i];
      opts.toExplicit = true;
    } else if (arg === "--max-files" && args[i + 1]) {
      const n = parseInt(args[++i], 10);
      if (!isNaN(n) && n > 0)
        opts.maxFiles = n;
    } else if (arg.startsWith("--from=")) {
      opts.from = arg.slice("--from=".length);
      opts.fromExplicit = true;
    } else if (arg.startsWith("--to=")) {
      opts.to = arg.slice("--to=".length);
      opts.toExplicit = true;
    } else if (arg.startsWith("--max-files=")) {
      const n = parseInt(arg.slice("--max-files=".length), 10);
      if (!isNaN(n) && n > 0)
        opts.maxFiles = n;
    } else if (arg === "--max-blast-radius" && args[i + 1]) {
      const n = parseInt(args[++i], 10);
      if (!isNaN(n) && n >= 0)
        opts.maxBlastRadius = n;
    } else if (arg.startsWith("--max-blast-radius=")) {
      const n = parseInt(arg.slice("--max-blast-radius=".length), 10);
      if (!isNaN(n) && n >= 0)
        opts.maxBlastRadius = n;
    }
  }
  return opts;
}
async function runDetectChanges(args) {
  const opts = parseDetectChangesArgs(args);
  const repoRoot = findRepoRoot(process.cwd());
  if (!repoRoot) {
    process.stderr.write("error: not inside a git repository\n");
    process.exit(2);
  }
  if (!opts.staged) {
    if (opts.base !== void 0 && !refExists(repoRoot, opts.base)) {
      process.stderr.write(`error: unknown git ref "${opts.base}" (--base)
`);
      process.exit(2);
    }
    if (opts.fromExplicit && !refExists(repoRoot, opts.from)) {
      process.stderr.write(`error: unknown git ref "${opts.from}"
`);
      process.exit(2);
    }
    if (opts.toExplicit && !refExists(repoRoot, opts.to)) {
      process.stderr.write(`error: unknown git ref "${opts.to}"
`);
      process.exit(2);
    }
  }
  try {
    const result = await detectChanges(repoRoot, opts);
    if (opts.json) {
      process.stdout.write(JSON.stringify(result, null, 2) + "\n");
    } else {
      process.stdout.write(`diff: ${result.diff_range}
`);
      process.stdout.write(`changed files (${result.changed_files.length}): ${result.changed_files.join(", ") || "(none)"}
`);
      process.stdout.write(`affected symbols: ${result.summary.total_symbols}
`);
      for (const sym of result.affected_symbols) {
        process.stdout.write(`  ${sym.file} [${sym.change_type}] \u2192 blast radius: ${sym.blast_radius.total} (${sym.blast_radius.risk_level})
`);
      }
      process.stdout.write(`summary: high=${result.summary.risk_high} medium=${result.summary.risk_medium} low=${result.summary.risk_low}
`);
    }
    if (opts.maxBlastRadius != null) {
      const exceeded = result.affected_symbols.filter((sym) => sym.blast_radius.total > opts.maxBlastRadius);
      if (exceeded.length > 0) {
        for (const sym of exceeded) {
          process.stderr.write(`eye: blast-radius exceeded threshold (${sym.blast_radius.total} > ${opts.maxBlastRadius}) \u2014 ${sym.file}
`);
        }
        process.exit(1);
      }
    }
    process.exit(0);
  } catch (err) {
    if (err instanceof EyeError) {
      process.stderr.write(`error: ${err.message}
`);
    } else {
      if (opts.debug) {
        process.stderr.write(`error: ${String(err)}
`);
      } else {
        process.stderr.write("error: internal error (see --debug for details)\n");
      }
    }
    process.exit(4);
  }
}

// dist/breaking-changes.js
import { execFileSync as execFileSync4 } from "child_process";
import { readFileSync as readFileSync5 } from "fs";
import { resolve as resolve5 } from "path";

// dist/extract-signatures.js
import { Project as Project2, SyntaxKind as SyntaxKind2, Node as Node2 } from "ts-morph";
import { existsSync as existsSync9, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, rmSync } from "fs";
import { join as join7, resolve as pathResolve, dirname as dirname6 } from "path";
import { tmpdir } from "os";
import { fileURLToPath as fileURLToPath2 } from "url";
import { Parser as Parser2, Language as Language2 } from "web-tree-sitter";
import { createRequire as createRequire4 } from "module";
function extractSignaturesFromContent(content, fileName, repoRoot) {
  let project;
  let tempDir;
  if (repoRoot) {
    const tsConfigPath = join7(repoRoot, "tsconfig.json");
    if (existsSync9(tsConfigPath)) {
      project = new Project2({ tsConfigFilePath: tsConfigPath, skipAddingFilesFromTsConfig: true });
      const absFile = pathResolve(repoRoot, fileName);
      if (existsSync9(absFile)) {
        project.addSourceFileAtPath(absFile);
      } else {
        tempDir = join7(tmpdir(), `eye-sigs-${Date.now()}-${Math.random().toString(36).slice(2)}`);
        mkdirSync3(tempDir, { recursive: true });
        const tempFile = join7(tempDir, fileName.replace(/\//g, "_"));
        writeFileSync2(tempFile, content, "utf8");
        project.addSourceFileAtPath(tempFile);
      }
    } else {
      project = new Project2();
      tempDir = join7(tmpdir(), `eye-sigs-${Date.now()}-${Math.random().toString(36).slice(2)}`);
      mkdirSync3(tempDir, { recursive: true });
      const tempFile = join7(tempDir, fileName.replace(/\//g, "_"));
      writeFileSync2(tempFile, content, "utf8");
      project.addSourceFileAtPath(tempFile);
    }
  } else {
    project = new Project2({ useInMemoryFileSystem: true });
  }
  try {
    let sf;
    if (repoRoot) {
      const files = project.getSourceFiles().filter((f) => !f.getFilePath().endsWith(".d.ts"));
      sf = files[0];
      if (!sf) {
        return [];
      }
    } else {
      sf = project.createSourceFile(fileName, content);
    }
    const results = [];
    for (const [name, nodes] of sf.getExportedDeclarations()) {
      for (const node of nodes) {
        const kind = node.getKind();
        if (kind === SyntaxKind2.FunctionDeclaration) {
          const fn = node;
          if (!Node2.isFunctionDeclaration(fn))
            continue;
          results.push({
            name,
            kind: "function",
            params: extractParams(fn),
            returnType: safeReturnType(fn)
          });
          continue;
        }
        if (kind === SyntaxKind2.FunctionExpression) {
          const fn = node;
          if (!Node2.isFunctionExpression(fn))
            continue;
          results.push({
            name,
            kind: "function",
            params: extractParams(fn),
            returnType: safeReturnType(fn)
          });
          continue;
        }
        if (kind === SyntaxKind2.VariableDeclaration && Node2.isVariableDeclaration(node)) {
          const init = node.getInitializer();
          if (init && Node2.isArrowFunction(init)) {
            results.push({
              name,
              kind: "arrow",
              params: extractParams(init),
              returnType: safeReturnType(init)
            });
          }
          continue;
        }
        if (kind === SyntaxKind2.ClassDeclaration && Node2.isClassDeclaration(node)) {
          const cls = node;
          for (const method of cls.getMethods()) {
            const mods = method.getModifiers().map((m) => m.getText());
            if (mods.includes("private") || mods.includes("protected"))
              continue;
            results.push({
              name: method.getName(),
              kind: "method",
              className: name,
              params: extractParams(method),
              returnType: safeReturnType(method)
            });
          }
          continue;
        }
      }
    }
    return results;
  } finally {
    if (tempDir) {
      try {
        rmSync(tempDir, { recursive: true, force: true });
      } catch {
      }
    }
  }
}
function extractParams(fn) {
  return fn.getParameters().map((p) => {
    const typeText = p.getType().getText();
    const unresolved = typeText.startsWith("import(");
    const param = {
      name: p.getName(),
      type: typeText,
      optional: p.isOptional() || p.hasInitializer()
    };
    if (unresolved)
      param.type_unresolved = true;
    return param;
  });
}
function safeReturnType(fn) {
  try {
    return fn.getReturnType().getText();
  } catch {
    return "unknown";
  }
}
async function extractPySignaturesFromContentAst(content) {
  const lang = await getLanguage("py");
  const parser = new Parser2();
  parser.setLanguage(lang);
  const tree = parser.parse(content);
  if (!tree)
    return [];
  const results = [];
  function extractPyParamsFromNode(paramsNode) {
    const params = [];
    for (let i = 0; i < paramsNode.childCount; i++) {
      const child = paramsNode.child(i);
      if (!child || !child.isNamed)
        continue;
      const ctype = child.type;
      if (ctype === "positional_separator" || ctype === "keyword_separator")
        continue;
      if (ctype === "list_splat_pattern" || ctype === "dictionary_splat_pattern")
        continue;
      if (ctype === "identifier") {
        const name = child.text;
        if (name === "self" || name === "cls")
          continue;
        params.push({ name, type: "Any", optional: false });
        continue;
      }
      if (ctype === "typed_parameter") {
        let name = "";
        for (let j = 0; j < child.childCount; j++) {
          const gc = child.child(j);
          if (gc && gc.type === "identifier") {
            name = gc.text;
            break;
          }
        }
        if (!name || name === "self" || name === "cls")
          continue;
        const typeNode = child.childForFieldName("type");
        const typeText = typeNode ? typeNode.text : "Any";
        params.push({ name, type: typeText, optional: false });
        continue;
      }
      if (ctype === "default_parameter") {
        const nameNode = child.childForFieldName("name");
        if (!nameNode)
          continue;
        const name = nameNode.text;
        if (name === "self" || name === "cls")
          continue;
        params.push({ name, type: "Any", optional: true });
        continue;
      }
      if (ctype === "typed_default_parameter") {
        const nameNode = child.childForFieldName("name");
        if (!nameNode)
          continue;
        const name = nameNode.text;
        if (name === "self" || name === "cls")
          continue;
        const typeNode = child.childForFieldName("type");
        const typeText = typeNode ? typeNode.text : "Any";
        const hasDefault = child.childForFieldName("value") !== null;
        params.push({ name, type: typeText, optional: hasDefault });
        continue;
      }
    }
    return params;
  }
  function walk(node, currentClassName) {
    if (node.type === "function_definition") {
      const nameNode = node.childForFieldName("name");
      if (!nameNode)
        return;
      const fnName = nameNode.text;
      if (fnName.startsWith("_"))
        return;
      const paramsNode = node.childForFieldName("parameters");
      const params = paramsNode ? extractPyParamsFromNode(paramsNode) : [];
      const retNode = node.childForFieldName("return_type");
      const returnType = retNode ? retNode.text : "None";
      const isMethod = currentClassName !== null;
      const sig = {
        name: fnName,
        kind: isMethod ? "method" : "function",
        params,
        returnType,
        language: "python"
      };
      if (isMethod)
        sig.className = currentClassName;
      results.push(sig);
      return;
    }
    if (node.type === "class_definition") {
      const nameNode = node.childForFieldName("name");
      const className = nameNode ? nameNode.text : null;
      const bodyNode = node.childForFieldName("body");
      if (bodyNode) {
        for (let i = 0; i < bodyNode.childCount; i++) {
          const child = bodyNode.child(i);
          if (child && child.isNamed)
            walk(child, className);
        }
      }
      return;
    }
    if (node.type === "decorated_definition") {
      const inner = node.children.find(
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (c) => c.type === "function_definition" || c.type === "class_definition"
      );
      if (inner)
        walk(inner, currentClassName);
      return;
    }
    for (let i = 0; i < node.childCount; i++) {
      const child = node.child(i);
      if (child && child.isNamed)
        walk(child, currentClassName);
    }
  }
  walk(tree.rootNode, null);
  return results;
}
async function extractGoSignaturesFromContentAst(content) {
  const lang = await getLanguage("go");
  const parser = new Parser2();
  parser.setLanguage(lang);
  const tree = parser.parse(content);
  if (!tree)
    return [];
  const results = [];
  function extractGoParamsFromList(paramListNode) {
    const params = [];
    for (let i = 0; i < paramListNode.childCount; i++) {
      const child = paramListNode.child(i);
      if (!child)
        continue;
      if (child.type === "parameter_declaration") {
        const typeNode = child.childForFieldName("type");
        const typeText = typeNode ? typeNode.text : "any";
        const identifiers = [];
        for (let j = 0; j < child.childCount; j++) {
          const gc = child.child(j);
          if (gc && gc.type === "identifier") {
            identifiers.push(gc.text);
          }
        }
        if (identifiers.length === 0) {
          params.push({ name: "", type: typeText, optional: false });
        } else {
          for (const ident of identifiers) {
            params.push({ name: ident, type: typeText, optional: false });
          }
        }
      } else if (child.type === "variadic_parameter_declaration") {
        const nameNode = child.childForFieldName("name");
        const typeNode = child.childForFieldName("type");
        const name = nameNode ? nameNode.text : "";
        const typeText = typeNode ? `...${typeNode.text}` : "...any";
        params.push({ name, type: typeText, optional: false });
      }
    }
    return params;
  }
  function extractGoReturnType(node) {
    const resultNode = node.childForFieldName("result");
    if (!resultNode)
      return "void";
    if (resultNode.type === "parameter_list") {
      return resultNode.text;
    }
    const text = resultNode.text.trim();
    return text || "void";
  }
  function extractGoReceiverClassName(methodNode) {
    const receiverNode = methodNode.childForFieldName("receiver");
    if (!receiverNode)
      return void 0;
    for (let i = 0; i < receiverNode.childCount; i++) {
      const child = receiverNode.child(i);
      if (!child || child.type !== "parameter_declaration")
        continue;
      const typeNode = child.childForFieldName("type");
      if (!typeNode)
        continue;
      if (typeNode.type === "pointer_type") {
        for (let j = 0; j < typeNode.childCount; j++) {
          const tc = typeNode.child(j);
          if (tc && tc.type === "type_identifier")
            return tc.text;
        }
        return typeNode.text.replace(/^\*/, "");
      }
      if (typeNode.type === "type_identifier")
        return typeNode.text;
      return typeNode.text.replace(/^\*/, "");
    }
    return void 0;
  }
  function walk(node) {
    if (node.type === "function_declaration") {
      const nameNode = node.childForFieldName("name");
      if (!nameNode)
        return;
      const fnName = nameNode.text;
      if (!/^[A-Z]/.test(fnName))
        return;
      const paramsNode = node.childForFieldName("parameters");
      const params = paramsNode ? extractGoParamsFromList(paramsNode) : [];
      const returnType = extractGoReturnType(node);
      results.push({ name: fnName, kind: "function", params, returnType, language: "go" });
      return;
    }
    if (node.type === "method_declaration") {
      const nameNode = node.childForFieldName("name");
      if (!nameNode)
        return;
      const fnName = nameNode.text;
      if (!/^[A-Z]/.test(fnName))
        return;
      const paramsNode = node.childForFieldName("parameters");
      const params = paramsNode ? extractGoParamsFromList(paramsNode) : [];
      const returnType = extractGoReturnType(node);
      const className = extractGoReceiverClassName(node);
      const sig = { name: fnName, kind: "method", params, returnType, language: "go" };
      if (className)
        sig.className = className;
      results.push(sig);
      return;
    }
    for (let i = 0; i < node.childCount; i++) {
      const child = node.child(i);
      if (child && child.isNamed)
        walk(child);
    }
  }
  walk(tree.rootNode);
  return results;
}
function parsePhpParam(raw) {
  const part = raw.trim();
  if (!part)
    return null;
  const m = part.match(/^(\??\S+\s+)?\$([A-Za-z_][A-Za-z0-9_]*)(\s*=\s*(.+))?$/);
  if (!m)
    return null;
  const rawType = (m[1] ?? "").trim();
  const name = m[2];
  const hasDefault = m[3] !== void 0;
  const type = rawType || "mixed";
  return { name, type, optional: hasDefault };
}
function splitPhpParams(paramStr) {
  const parts = [];
  let depth = 0;
  let current = "";
  for (const ch of paramStr) {
    if (ch === "(" || ch === "[" || ch === "{" || ch === "<") {
      depth++;
      current += ch;
    } else if (ch === ")" || ch === "]" || ch === "}" || ch === ">") {
      depth--;
      current += ch;
    } else if (ch === "," && depth === 0) {
      parts.push(current);
      current = "";
    } else {
      current += ch;
    }
  }
  if (current.trim())
    parts.push(current);
  return parts;
}
function extractPhpSignaturesFromContent(content) {
  const results = [];
  const lines = content.split("\n");
  let currentClassName = null;
  let i = 0;
  while (i < lines.length) {
    const line = lines[i];
    const classMatch = line.match(/^(?:(?:abstract|final)\s+)?class\s+([A-Za-z_][A-Za-z0-9_]*)/);
    if (classMatch) {
      currentClassName = classMatch[1];
      i++;
      continue;
    }
    const interfaceMatch = line.match(/^interface\s+([A-Za-z_][A-Za-z0-9_]*)/);
    if (interfaceMatch) {
      currentClassName = interfaceMatch[1];
      i++;
      continue;
    }
    const traitMatch = line.match(/^trait\s+([A-Za-z_][A-Za-z0-9_]*)/);
    if (traitMatch) {
      currentClassName = traitMatch[1];
      i++;
      continue;
    }
    if (line.length > 0 && !/^\s/.test(line) && !/^\/[/*]/.test(line)) {
      if (!/^(?:(?:abstract|final)\s+)?class\s+/.test(line) && !/^interface\s+/.test(line) && !/^trait\s+/.test(line) && !/^(?:(?:public|protected|private|static|abstract|final)\s+)*function\s+/.test(line)) {
        currentClassName = null;
      }
    }
    const fnMatch = line.match(/^(\s*)(?:(public|protected|private)(?:\s+(?:static|abstract|final))*\s+)?function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
    if (!fnMatch) {
      i++;
      continue;
    }
    const indent = fnMatch[1];
    const visibility = fnMatch[2] ?? "public";
    const fnName = fnMatch[3];
    if (fnName.startsWith("__")) {
      i++;
      continue;
    }
    if (visibility === "private" || visibility === "protected") {
      i++;
      continue;
    }
    const isMethod = indent.length > 0 && currentClassName !== null;
    const startCol = line.indexOf("(", line.indexOf("function "));
    let parenDepth = 0;
    let fullParams = "";
    let j = i;
    let inParams = false;
    let returnType = "mixed";
    outer: while (j < lines.length) {
      const l = j === i ? lines[j].slice(startCol) : lines[j];
      for (let k = 0; k < l.length; k++) {
        const ch = l[k];
        if (ch === "(") {
          parenDepth++;
          if (!inParams) {
            inParams = true;
          } else
            fullParams += ch;
        } else if (ch === ")") {
          parenDepth--;
          if (parenDepth === 0) {
            const rest = l.slice(k + 1);
            const retMatch = rest.match(/^\s*:\s*(\??\S+)[\s;{]/);
            if (retMatch)
              returnType = retMatch[1];
            else {
              const retMatchEnd = rest.match(/^\s*:\s*(\??\S+)\s*$/);
              if (retMatchEnd)
                returnType = retMatchEnd[1];
            }
            const paramParts = splitPhpParams(fullParams);
            const params = [];
            for (const p of paramParts) {
              const parsed = parsePhpParam(p);
              if (parsed)
                params.push(parsed);
            }
            const sig = {
              name: fnName,
              kind: isMethod ? "method" : "function",
              params,
              returnType
            };
            if (isMethod && currentClassName)
              sig.className = currentClassName;
            results.push(sig);
            i = j + 1;
            break outer;
          } else {
            fullParams += ch;
          }
        } else {
          if (inParams)
            fullParams += ch;
        }
      }
      if (j > i)
        fullParams += " ";
      j++;
    }
    if (j >= lines.length && parenDepth > 0) {
      i = j;
    }
  }
  return results;
}
async function extractCsSignaturesFromContentAst(content) {
  const lang = await getLanguage("cs");
  const parser = new Parser2();
  parser.setLanguage(lang);
  const tree = parser.parse(content);
  if (!tree)
    return [];
  const results = [];
  function extractCsReturnType(methodNode) {
    const nameNode = methodNode.childForFieldName("name");
    const nameText = nameNode ? nameNode.text : null;
    for (let i = 0; i < methodNode.childCount; i++) {
      const child = methodNode.child(i);
      if (!child)
        continue;
      if (nameText && child.type === "identifier" && child.text === nameText)
        break;
      if (!child.isNamed || child.type === "modifier")
        continue;
      return child.text;
    }
    return "void";
  }
  function extractCsParamsFromList(paramListNode) {
    const params = [];
    let pendingParams = false;
    let pendingParamsType = null;
    for (let i = 0; i < paramListNode.childCount; i++) {
      const child = paramListNode.child(i);
      if (!child)
        continue;
      if (child.type === "params") {
        pendingParams = true;
        continue;
      }
      if (pendingParams) {
        if (child.isNamed && child.type !== "identifier") {
          pendingParamsType = child.text;
          continue;
        }
        if (child.isNamed && child.type === "identifier") {
          const name2 = child.text;
          params.push({ name: name2, type: pendingParamsType ?? "object[]", optional: false });
          pendingParams = false;
          pendingParamsType = null;
          continue;
        }
        if (!child.isNamed)
          continue;
      }
      if (child.type !== "parameter")
        continue;
      const typeNode = child.childForFieldName("type");
      const nameNode = child.childForFieldName("name");
      if (!nameNode)
        continue;
      const name = nameNode.text;
      const typeText = typeNode ? typeNode.text : "var";
      let hasDefault = false;
      for (let j = 0; j < child.childCount; j++) {
        const gc = child.child(j);
        if (gc && gc.type === "=") {
          hasDefault = true;
          break;
        }
      }
      params.push({ name, type: typeText, optional: hasDefault });
    }
    return params;
  }
  function walk(node, currentClassName) {
    if (node.type === "class_declaration" || node.type === "interface_declaration" || node.type === "struct_declaration") {
      const nameNode = node.childForFieldName("name");
      const className = nameNode ? nameNode.text : null;
      const bodyNode = node.childForFieldName("body");
      if (bodyNode) {
        for (let i = 0; i < bodyNode.childCount; i++) {
          const child = bodyNode.child(i);
          if (child && child.isNamed)
            walk(child, className);
        }
      }
      return;
    }
    if (node.type === "method_declaration") {
      let isPublic = false;
      for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (child && child.type === "modifier" && child.text === "public") {
          isPublic = true;
          break;
        }
      }
      if (!isPublic)
        return;
      const nameNode = node.childForFieldName("name");
      if (!nameNode)
        return;
      const methodName = nameNode.text;
      if (currentClassName && methodName === currentClassName)
        return;
      const returnType = extractCsReturnType(node);
      const paramsNode = node.childForFieldName("parameters");
      const params = paramsNode ? extractCsParamsFromList(paramsNode) : [];
      const sig = {
        name: methodName,
        kind: currentClassName ? "method" : "function",
        params,
        returnType,
        language: "csharp"
      };
      if (currentClassName)
        sig.className = currentClassName;
      results.push(sig);
      return;
    }
    for (let i = 0; i < node.childCount; i++) {
      const child = node.child(i);
      if (child && child.isNamed)
        walk(child, currentClassName);
    }
  }
  walk(tree.rootNode, null);
  return results;
}
var _swiftParserInitialised = false;
var _swiftLanguage = null;
var _swiftRequire = createRequire4(import.meta.url);
function resolveSwiftWasmPath() {
  const wasmFile = "tree-sitter-swift.wasm";
  const wasmDir = process.env["EYE_WASM_DIR"];
  if (wasmDir) {
    const sidecar = join7(wasmDir, wasmFile);
    if (existsSync9(sidecar))
      return sidecar;
  }
  try {
    const pkgDir = dirname6(_swiftRequire.resolve("tree-sitter-swift/package.json"));
    const candidate = join7(pkgDir, wasmFile);
    if (existsSync9(candidate))
      return candidate;
  } catch {
  }
  const selfDir = dirname6(fileURLToPath2(import.meta.url));
  const repoRoot = dirname6(selfDir);
  const bundled = join7(repoRoot, "wasm", wasmFile);
  if (existsSync9(bundled))
    return bundled;
  throw new Error(`Cannot find tree-sitter-swift.wasm. Tried EYE_WASM_DIR, npm package, and ${bundled}`);
}
async function ensureSwiftLanguage() {
  if (_swiftLanguage)
    return _swiftLanguage;
  if (!_swiftParserInitialised) {
    const wasmDir = process.env["EYE_WASM_DIR"];
    if (wasmDir) {
      const wtWasm = join7(wasmDir, "web-tree-sitter.wasm");
      if (existsSync9(wtWasm)) {
        await Parser2.init({ locateFile: () => wtWasm });
        _swiftParserInitialised = true;
      }
    }
    if (!_swiftParserInitialised) {
      await Parser2.init();
      _swiftParserInitialised = true;
    }
  }
  const swiftWasm = resolveSwiftWasmPath();
  _swiftLanguage = await Language2.load(swiftWasm);
  return _swiftLanguage;
}
function swiftNodeIsPublic(node) {
  const mods = node.children.find((c) => c.type === "modifiers");
  if (!mods)
    return false;
  return mods.text.includes("public") || mods.text.includes("open");
}
async function extractSwiftSignaturesFromContent(content) {
  const lang = await ensureSwiftLanguage();
  const parser = new Parser2();
  parser.setLanguage(lang);
  const tree = parser.parse(content);
  if (!tree)
    return [];
  const results = [];
  function walkSwift(node) {
    if (node.type === "function_declaration") {
      if (!swiftNodeIsPublic(node))
        return;
      const nameNode = node.children.find((c) => c.type === "simple_identifier");
      if (!nameNode)
        return;
      const fnName = nameNode.text;
      const params = [];
      for (const child of node.children) {
        if (child.type !== "parameter")
          continue;
        const idents = child.children.filter((c) => c.type === "simple_identifier").map((c) => c.text);
        const externalLabel = idents[0] ?? "_";
        const typeNode = child.children.find((c) => c.isNamed && c.type !== "simple_identifier" && c.type !== "parameter_modifiers");
        const typeText = typeNode ? typeNode.text : "Any";
        const hasDefault = child.children.some((c) => c.type === "default_value_clause");
        params.push({
          name: externalLabel,
          type: typeText,
          optional: hasDefault
        });
      }
      let returnType = "Void";
      const arrowIdx = node.children.findIndex((c) => c.type === "->");
      if (arrowIdx !== -1) {
        const afterArrow = node.children.slice(arrowIdx + 1);
        const retNode = afterArrow.find((c) => c.isNamed && c.type !== "function_body" && c.type !== "async" && c.type !== "throws");
        if (retNode)
          returnType = retNode.text;
      }
      results.push({
        name: fnName,
        kind: "function",
        params,
        returnType,
        language: "swift"
      });
      return;
    }
    for (const child of node.children) {
      if (child.isNamed)
        walkSwift(child);
    }
  }
  walkSwift(tree.rootNode);
  return results;
}
async function extractRustSignaturesFromContentAst(content) {
  const lang = await getLanguage("rs");
  const parser = new Parser2();
  parser.setLanguage(lang);
  const tree = parser.parse(content);
  if (!tree)
    return [];
  const results = [];
  function rustNodeIsPublic(node) {
    return node.children.some(
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (c) => c.type === "visibility_modifier" && c.text.startsWith("pub")
    );
  }
  function extractRustParams(paramsNode) {
    const params = [];
    for (let i = 0; i < paramsNode.childCount; i++) {
      const child = paramsNode.child(i);
      if (!child || !child.isNamed)
        continue;
      if (child.type === "self_parameter" || child.type === "self")
        continue;
      if (child.type === "parameter") {
        const patNode = child.childForFieldName("pattern");
        const typeNode = child.childForFieldName("type");
        if (!patNode)
          continue;
        const name = patNode.text;
        if (name === "self" || name === "_self")
          continue;
        const typeText = typeNode ? typeNode.text : "unknown";
        params.push({ name, type: typeText, optional: false });
      }
    }
    return params;
  }
  function extractRustReturnType(fnNode) {
    const retNode = fnNode.childForFieldName("return_type");
    if (!retNode)
      return "void";
    return retNode.text.trim() || "void";
  }
  function walk(node, implTypeName) {
    const type = node.type;
    if (type === "function_item") {
      if (!rustNodeIsPublic(node))
        return;
      const nameNode = node.childForFieldName("name");
      if (!nameNode)
        return;
      const fnName = nameNode.text;
      const paramsNode = node.childForFieldName("parameters");
      const params = paramsNode ? extractRustParams(paramsNode) : [];
      const returnType = extractRustReturnType(node);
      const sig = {
        name: fnName,
        kind: implTypeName ? "method" : "function",
        params,
        returnType,
        language: "rust"
      };
      if (implTypeName)
        sig.className = implTypeName;
      results.push(sig);
      return;
    }
    if (type === "impl_item") {
      const typeNode = node.childForFieldName("type");
      const traitNode = node.childForFieldName("trait");
      const contextName = traitNode ? traitNode.text : typeNode ? typeNode.text : null;
      const body = node.childForFieldName("body");
      if (body)
        walk(body, contextName);
      return;
    }
    if (type === "declaration_list") {
      for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (child)
          walk(child, implTypeName);
      }
      return;
    }
    for (let i = 0; i < node.childCount; i++) {
      const child = node.child(i);
      if (child && child.isNamed)
        walk(child, implTypeName);
    }
  }
  walk(tree.rootNode, null);
  return results;
}
async function extractKotlinSignaturesFromContentAst(content) {
  const lang = await getLanguage("kt");
  const parser = new Parser2();
  parser.setLanguage(lang);
  const tree = parser.parse(content);
  if (!tree)
    return [];
  const results = [];
  function kotlinNodeIsPublic(node) {
    const mods = node.children.find((c) => c.type === "modifiers");
    if (!mods)
      return true;
    return !mods.children.some((c) => c.type === "visibility_modifier" && (c.text === "private" || c.text === "protected" || c.text === "internal"));
  }
  function extractKotlinParams(paramsNode) {
    const params = [];
    for (let i = 0; i < paramsNode.childCount; i++) {
      const child = paramsNode.child(i);
      if (!child || !child.isNamed)
        continue;
      let paramNode;
      if (child.type === "parameter") {
        paramNode = child;
      } else if (child.type === "function_value_parameter") {
        paramNode = child.children.find((c) => c.type === "parameter");
        if (!paramNode)
          continue;
      } else {
        continue;
      }
      const nameNode = paramNode.children.find((c) => c.type === "simple_identifier");
      if (!nameNode)
        continue;
      const name = nameNode.text;
      const typeNode = paramNode.children.find((c) => c.isNamed && c.type !== "simple_identifier");
      const typeText = typeNode ? typeNode.text : "Any";
      const hasDefault = child.type === "function_value_parameter" ? child.children.some((c) => !c.isNamed && c.text === "=") : false;
      params.push({ name, type: typeText, optional: hasDefault });
    }
    return params;
  }
  function extractKotlinReturnType(fnNode) {
    const skipTypes = /* @__PURE__ */ new Set([
      "modifiers",
      "simple_identifier",
      "function_value_parameters",
      "function_body",
      "block",
      "type_parameters",
      "receiver_type"
    ]);
    for (let i = 0; i < fnNode.childCount; i++) {
      const child = fnNode.child(i);
      if (!child || !child.isNamed)
        continue;
      if (skipTypes.has(child.type))
        continue;
      return child.text;
    }
    return "Unit";
  }
  function processFunctionDeclaration(fnNode, className) {
    if (!kotlinNodeIsPublic(fnNode))
      return;
    const nameNode = fnNode.children.find((c) => c.type === "simple_identifier");
    if (!nameNode)
      return;
    const fnName = nameNode.text;
    const paramsNode = fnNode.children.find((c) => c.type === "function_value_parameters");
    const params = paramsNode ? extractKotlinParams(paramsNode) : [];
    const returnType = extractKotlinReturnType(fnNode);
    const sig = {
      name: fnName,
      kind: className ? "method" : "function",
      params,
      returnType,
      language: "kotlin"
    };
    if (className)
      sig.className = className;
    results.push(sig);
  }
  function walk(node, className) {
    const type = node.type;
    if (type === "function_declaration") {
      processFunctionDeclaration(node, className);
      return;
    }
    if (type === "class_declaration" || type === "object_declaration") {
      const identNode = node.children.find((c) => c.type === "type_identifier");
      const cname = identNode ? identNode.text : null;
      const body = node.children.find((c) => c.type === "class_body" || c.type === "enum_class_body");
      if (body) {
        for (let i = 0; i < body.childCount; i++) {
          const child = body.child(i);
          if (child && child.isNamed)
            walk(child, cname);
        }
      }
      return;
    }
    for (let i = 0; i < node.childCount; i++) {
      const child = node.child(i);
      if (child && child.isNamed)
        walk(child, className);
    }
  }
  walk(tree.rootNode, null);
  return results;
}
function splitJavaParams(paramStr) {
  const parts = [];
  let depth = 0;
  let current = "";
  for (const ch of paramStr) {
    if (ch === "(" || ch === "[" || ch === "{" || ch === "<") {
      depth++;
      current += ch;
    } else if (ch === ")" || ch === "]" || ch === "}" || ch === ">") {
      depth--;
      current += ch;
    } else if (ch === "," && depth === 0) {
      parts.push(current);
      current = "";
    } else {
      current += ch;
    }
  }
  if (current.trim())
    parts.push(current);
  return parts;
}
function parseJavaParam(raw) {
  const part = raw.trim().replace(/\s+/g, " ");
  if (!part)
    return null;
  const stripped = part.replace(/@\w+\s*/g, "").replace(/\bfinal\s+/g, "").trim();
  const lastSpace = stripped.lastIndexOf(" ");
  if (lastSpace === -1)
    return null;
  const type = stripped.slice(0, lastSpace).trim();
  const name = stripped.slice(lastSpace + 1).trim();
  if (!type || !name)
    return null;
  return { name, type, optional: false };
}
function extractJavaSignaturesFromContent(content) {
  const results = [];
  const lines = content.split("\n");
  let currentClassName = null;
  const methodRe = /^\s*public\s+(?:(?:static|final|abstract|synchronized|native|default|strictfp)\s+)*(?:<[^>]+>\s+)?([A-Za-z_$][A-Za-z0-9_$<>[\],\s]*?)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*\(([^)]*)\)/;
  const classRe = /\bclass\s+([A-Za-z_$][A-Za-z0-9_$]*)/;
  const interfaceRe = /\binterface\s+([A-Za-z_$][A-Za-z0-9_$]*)/;
  for (const line of lines) {
    const classMatch = line.match(classRe);
    if (classMatch) {
      currentClassName = classMatch[1];
    }
    const ifaceMatch = line.match(interfaceRe);
    if (ifaceMatch) {
      currentClassName = ifaceMatch[1];
    }
    const m = line.match(methodRe);
    if (!m)
      continue;
    const returnType = m[1].trim();
    const methodName = m[2];
    const paramStr = m[3].trim();
    if (methodName === currentClassName)
      continue;
    if (!returnType || /^(public|private|protected|static|final|abstract)$/.test(returnType))
      continue;
    const params = paramStr ? splitJavaParams(paramStr).map(parseJavaParam).filter((p) => p !== null) : [];
    results.push({
      name: methodName,
      kind: "method",
      className: currentClassName ?? void 0,
      params,
      returnType
    });
  }
  return results;
}

// dist/diff-signatures.js
function splitUnionTopLevel(t) {
  const members = [];
  let depth = 0;
  let start = 0;
  for (let i = 0; i < t.length; i++) {
    const ch = t[i];
    if (ch === "<" || ch === "(" || ch === "[") {
      depth++;
    } else if (ch === ">" || ch === ")" || ch === "]") {
      depth--;
    } else if (ch === "|" && depth === 0) {
      members.push(t.slice(start, i).trim());
      start = i + 1;
    }
  }
  members.push(t.slice(start).trim());
  return members;
}
function normalizeTypeString(type) {
  let t = type.trim();
  t = t.replace(/\s*\|\s*/g, "|");
  const members = splitUnionTopLevel(t);
  const normalized = members.map((m) => m.replace(/^(\w+)\[\]$/, "Array<$1>"));
  normalized.sort();
  return normalized.join("|");
}
function sigKey(sig) {
  return sig.className ? `${sig.className}.${sig.name}` : sig.name;
}
function requiredCount(sig) {
  return sig.params.filter((p) => !p.optional).length;
}
function diffSignatures(before, after) {
  const afterMap = /* @__PURE__ */ new Map();
  for (const sig of after) {
    afterMap.set(sigKey(sig), sig);
  }
  const changes = [];
  for (const bSig of before) {
    const key = sigKey(bSig);
    const aSig = afterMap.get(key);
    if (!aSig) {
      changes.push({ symbol: key, severity: "critical", reason: "removed" });
      continue;
    }
    const paramRequiredIndices = /* @__PURE__ */ new Set();
    for (let i = 0; i < bSig.params.length && i < aSig.params.length; i++) {
      const bParam = bSig.params[i];
      const aParam = aSig.params[i];
      if (bParam.optional && !aParam.optional) {
        paramRequiredIndices.add(i);
        changes.push({
          symbol: key,
          severity: "critical",
          reason: "param_required",
          before: `${bParam.name}: optional`,
          after: `${aParam.name}: required`
        });
      }
    }
    const bReq = requiredCount(bSig);
    const aReq = requiredCount(aSig);
    if (aReq > bReq) {
      changes.push({
        symbol: key,
        severity: "critical",
        reason: "param_count_increased",
        before: String(bReq),
        after: String(aReq)
      });
    } else if (aReq < bReq) {
      changes.push({
        symbol: key,
        severity: "warning",
        reason: "param_count_decreased",
        before: String(bReq),
        after: String(aReq)
      });
    }
    const minLen = Math.min(bSig.params.length, aSig.params.length);
    for (let i = 0; i < minLen; i++) {
      if (paramRequiredIndices.has(i))
        continue;
      const bParam = bSig.params[i];
      const aParam = aSig.params[i];
      if (bParam.type_unresolved || aParam.type_unresolved)
        continue;
      if (normalizeTypeString(bParam.type) !== normalizeTypeString(aParam.type)) {
        changes.push({
          symbol: key,
          severity: "critical",
          reason: "param_type",
          before: bParam.type,
          after: aParam.type
        });
      }
      if (bSig.language === "swift" && aSig.language === "swift" && bParam.name && aParam.name && bParam.name !== "_" && bParam.name !== aParam.name) {
        changes.push({
          symbol: key,
          severity: "critical",
          reason: "param_name_changed",
          before: bParam.name,
          after: aParam.name
        });
      }
    }
    if (normalizeTypeString(bSig.returnType) !== normalizeTypeString(aSig.returnType)) {
      changes.push({
        symbol: key,
        severity: "critical",
        reason: "return_type",
        before: bSig.returnType,
        after: aSig.returnType
      });
    }
  }
  return changes;
}

// dist/breaking-changes.js
function parseBreakingChangesArgs(args) {
  const opts = {
    from: "HEAD~1",
    to: "HEAD",
    staged: false,
    severity: "all",
    noExitCode: false,
    human: false
  };
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === "--staged") {
      opts.staged = true;
    } else if (arg === "--no-exit-code") {
      opts.noExitCode = true;
    } else if (arg === "--human") {
      opts.human = true;
    } else if (arg === "--from") {
      const val = args[i + 1];
      if (!val || val.startsWith("--")) {
        process.stderr.write("error: --from requires a git ref\n");
        process.exit(2);
      }
      opts.from = args[++i];
    } else if (arg === "--to") {
      const val = args[i + 1];
      if (!val || val.startsWith("--")) {
        process.stderr.write("error: --to requires a git ref\n");
        process.exit(2);
      }
      opts.to = args[++i];
    } else if (arg === "--severity") {
      const val = args[i + 1];
      if (!val || val.startsWith("--")) {
        process.stderr.write("error: --severity requires a value: critical|warning|all\n");
        process.exit(2);
      }
      const v = args[++i];
      if (v === "critical" || v === "warning" || v === "all") {
        opts.severity = v;
      } else {
        process.stderr.write(`error: --severity must be critical, warning, or all (got "${v}")
`);
        process.exit(2);
      }
    } else if (arg.startsWith("--from=")) {
      opts.from = arg.slice("--from=".length);
    } else if (arg.startsWith("--to=")) {
      opts.to = arg.slice("--to=".length);
    } else if (arg.startsWith("--severity=")) {
      const val = arg.slice("--severity=".length);
      if (val === "critical" || val === "warning" || val === "all") {
        opts.severity = val;
      } else {
        process.stderr.write(`error: --severity must be critical, warning, or all (got "${val}")
`);
        process.exit(2);
      }
    }
  }
  return opts;
}
async function processLanguageEntries(entries, repoRoot, opts, extractor) {
  const results = [];
  for (const { relFile, status } of entries) {
    if (status === "A") {
      results.push({ file: relFile, changes: [], reason: "new_file" });
      continue;
    }
    let beforeContent = "";
    try {
      beforeContent = execFileSync4("git", ["show", `${opts.from}:${relFile}`], {
        cwd: repoRoot,
        encoding: "utf8",
        stdio: ["pipe", "pipe", "pipe"]
      });
    } catch {
      beforeContent = "";
    }
    let afterContent = "";
    if (opts.staged) {
      try {
        afterContent = readFileSync5(resolve5(repoRoot, relFile), "utf8");
      } catch {
        afterContent = "";
      }
    } else {
      try {
        afterContent = execFileSync4("git", ["show", `${opts.to}:${relFile}`], {
          cwd: repoRoot,
          encoding: "utf8",
          stdio: ["pipe", "pipe", "pipe"]
        });
      } catch {
        afterContent = "";
      }
    }
    const beforeSigs = await extractor(beforeContent);
    const afterSigs = await extractor(afterContent);
    if (status === "D") {
      if (beforeSigs.length > 0) {
        results.push({ file: relFile, changes: [], file_deleted: true });
      }
      continue;
    }
    let changes = diffSignatures(beforeSigs, afterSigs);
    if (opts.severity !== "all") {
      changes = changes.filter((c) => c.severity === opts.severity);
    }
    if (changes.length > 0) {
      results.push({ file: relFile, changes });
    }
  }
  return results;
}
async function detectBreakingChanges(repoRoot, opts) {
  const diffRange = opts.staged ? "staged" : `${opts.from}..${opts.to}`;
  const changedEntries = getChangedFilesWithStatus(repoRoot, opts, "ACMRDT");
  const breaking_changes = [];
  for (const { relFile } of changedEntries) {
    if (/\.(ts|tsx)$/.test(relFile)) {
      continue;
    }
    if (relFile.endsWith(".py")) {
      continue;
    }
    if (relFile.endsWith(".php")) {
      continue;
    }
    if (relFile.endsWith(".go")) {
      continue;
    }
    if (relFile.endsWith(".cs")) {
      continue;
    }
    if (relFile.endsWith(".swift")) {
      continue;
    }
    if (relFile.endsWith(".java")) {
      continue;
    }
    if (relFile.endsWith(".rs")) {
      continue;
    }
    if (relFile.endsWith(".kt") || relFile.endsWith(".kts")) {
      continue;
    }
    breaking_changes.push({
      file: relFile,
      changes: [],
      skipped: true,
      reason: "language not yet supported for signature diff"
    });
  }
  const pyEntries = changedEntries.filter(({ relFile }) => relFile.endsWith(".py"));
  breaking_changes.push(...await processLanguageEntries(pyEntries, repoRoot, opts, extractPySignaturesFromContentAst));
  const phpEntries = changedEntries.filter(({ relFile }) => relFile.endsWith(".php"));
  breaking_changes.push(...await processLanguageEntries(phpEntries, repoRoot, opts, extractPhpSignaturesFromContent));
  const goEntries = changedEntries.filter(({ relFile }) => relFile.endsWith(".go"));
  breaking_changes.push(...await processLanguageEntries(goEntries, repoRoot, opts, extractGoSignaturesFromContentAst));
  const csEntries = changedEntries.filter(({ relFile }) => relFile.endsWith(".cs"));
  breaking_changes.push(...await processLanguageEntries(csEntries, repoRoot, opts, extractCsSignaturesFromContentAst));
  const swiftEntries = changedEntries.filter(({ relFile }) => relFile.endsWith(".swift"));
  breaking_changes.push(...await processLanguageEntries(swiftEntries, repoRoot, opts, extractSwiftSignaturesFromContent));
  const javaEntries = changedEntries.filter(({ relFile }) => relFile.endsWith(".java"));
  breaking_changes.push(...await processLanguageEntries(javaEntries, repoRoot, opts, async (content) => extractJavaSignaturesFromContent(content)));
  const rustEntries = changedEntries.filter(({ relFile }) => relFile.endsWith(".rs"));
  breaking_changes.push(...await processLanguageEntries(rustEntries, repoRoot, opts, extractRustSignaturesFromContentAst));
  const kotlinEntries = changedEntries.filter(({ relFile }) => relFile.endsWith(".kt") || relFile.endsWith(".kts"));
  breaking_changes.push(...await processLanguageEntries(kotlinEntries, repoRoot, opts, extractKotlinSignaturesFromContentAst));
  const tsEntries = changedEntries.filter(({ relFile }) => {
    return /\.(ts|tsx)$/.test(relFile) && !relFile.endsWith(".d.ts");
  });
  const tsExtractor = (content, file) => extractSignaturesFromContent(content, file ?? "", repoRoot);
  for (const { relFile, status } of tsEntries) {
    if (status === "A") {
      breaking_changes.push({ file: relFile, changes: [], reason: "new_file" });
      continue;
    }
    let beforeContent = "";
    try {
      beforeContent = execFileSync4("git", ["show", `${opts.from}:${relFile}`], {
        cwd: repoRoot,
        encoding: "utf8",
        stdio: ["pipe", "pipe", "pipe"]
      });
    } catch {
      beforeContent = "";
    }
    let afterContent = "";
    if (opts.staged) {
      try {
        afterContent = readFileSync5(resolve5(repoRoot, relFile), "utf8");
      } catch {
        afterContent = "";
      }
    } else {
      try {
        afterContent = execFileSync4("git", ["show", `${opts.to}:${relFile}`], {
          cwd: repoRoot,
          encoding: "utf8",
          stdio: ["pipe", "pipe", "pipe"]
        });
      } catch {
        afterContent = "";
      }
    }
    const beforeSigs = extractSignaturesFromContent(beforeContent, relFile, repoRoot);
    const afterSigs = extractSignaturesFromContent(afterContent, relFile, repoRoot);
    if (status === "D") {
      if (beforeSigs.length > 0) {
        breaking_changes.push({ file: relFile, changes: [], file_deleted: true });
      }
      continue;
    }
    let changes = diffSignatures(beforeSigs, afterSigs);
    if (opts.severity !== "all") {
      changes = changes.filter((c) => c.severity === opts.severity);
    }
    if (changes.length > 0) {
      breaking_changes.push({ file: relFile, changes });
    }
  }
  void tsExtractor;
  let total = 0;
  let critical = 0;
  let warning = 0;
  for (const fbc of breaking_changes) {
    for (const c of fbc.changes) {
      total++;
      if (c.severity === "critical")
        critical++;
      else
        warning++;
    }
  }
  return { diff_range: diffRange, breaking_changes, summary: { total, critical, warning } };
}
function formatBreakingChangesHuman(result) {
  const lines = [];
  for (const fbc of result.breaking_changes) {
    if (fbc.skipped) {
      lines.push(`  (skipped: ${fbc.file} \u2014 language not yet supported)`);
      continue;
    }
    if (fbc.changes.length === 0)
      continue;
    lines.push(`
## ${fbc.file}`);
    for (const c of fbc.changes) {
      lines.push(`  [${c.severity}] ${c.symbol} \u2014 ${c.reason}: ${c.before ?? "(none)"} \u2192 ${c.after ?? "(none)"}`);
    }
  }
  lines.push(`
Total: ${result.summary.critical} critical, ${result.summary.warning} warning`);
  return lines.join("\n");
}
async function runBreakingChanges(args) {
  const opts = parseBreakingChangesArgs(args);
  const repoRoot = findRepoRoot(process.cwd());
  if (!repoRoot) {
    process.stderr.write("error: not inside a git repository\n");
    process.exit(2);
  }
  try {
    const result = await detectBreakingChanges(repoRoot, opts);
    if (opts.human) {
      process.stdout.write(formatBreakingChangesHuman(result) + "\n");
    } else {
      process.stdout.write(JSON.stringify(result, null, 2) + "\n");
    }
    if (!opts.noExitCode && result.summary.total > 0) {
      process.exit(1);
    }
    process.exit(0);
  } catch (err) {
    if (err instanceof EyeError) {
      process.stderr.write(`error: ${err.message}
`);
      process.exit(err.code ?? 1);
    }
    process.stderr.write(`error: ${String(err)}
`);
    process.exit(4);
  }
}

// dist/eye-diff.js
import { execFileSync as execFileSync5 } from "child_process";
function refExists2(repoRoot, ref) {
  try {
    execFileSync5("git", ["rev-parse", "--verify", ref], {
      cwd: repoRoot,
      encoding: "utf8",
      stdio: ["pipe", "pipe", "pipe"]
    });
    return true;
  } catch {
    return false;
  }
}
function parseCommitRange(rangeArg) {
  const idx = rangeArg.indexOf("..");
  if (idx === -1)
    return null;
  const from = rangeArg.slice(0, idx);
  const to = rangeArg.slice(idx + 2);
  if (!from || !to)
    return null;
  return { from, to };
}
async function runEyeDiff(opts, repoRoot) {
  const bcOpts = {
    from: opts.from,
    to: opts.to,
    staged: false,
    severity: "all",
    noExitCode: true,
    human: false
  };
  return detectBreakingChanges(repoRoot, bcOpts);
}
var DIFF_HELP = `eye diff <from>..<to> [--format json|human]

Run breaking-change detection over a commit range.

Arguments:
  <from>..<to>        Commit range, e.g. HEAD~3..HEAD or abc123..def456

Options:
  --format json|human  Output format (default: json)
  --help               Show this help

Exit codes:
  0  Success
  2  Usage error (missing "..", unknown ref, not in git repo)

Examples:
  eye diff HEAD~3..HEAD
  eye diff HEAD~3..HEAD --format human
  eye diff abc123..HEAD
`;
function parseDiffArgs(args) {
  let range = null;
  let format = "json";
  let help = false;
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === "--help" || arg === "-h") {
      help = true;
    } else if (arg === "--format" && args[i + 1]) {
      const val = args[++i];
      if (val === "json" || val === "human") {
        format = val;
      } else {
        format = val;
      }
    } else if (arg.startsWith("--format=")) {
      const val = arg.slice("--format=".length);
      if (val === "json" || val === "human") {
        format = val;
      } else {
        format = val;
      }
    } else if (!arg.startsWith("--")) {
      range = arg;
    }
  }
  return { range, format, help };
}
async function runDiff(args) {
  const parsed = parseDiffArgs(args);
  if (parsed.help) {
    process.stdout.write(DIFF_HELP);
    process.exit(0);
  }
  if (parsed.format !== "json" && parsed.format !== "human") {
    process.stderr.write(`error: --format must be json or human
`);
    process.exit(2);
  }
  if (!parsed.range) {
    process.stderr.write(`error: missing commit range \u2014 usage: eye diff <from>..<to>
`);
    process.exit(2);
  }
  const range = parseCommitRange(parsed.range);
  if (!range) {
    process.stderr.write(`error: invalid commit range "${parsed.range}" \u2014 expected <from>..<to> with ".." separator
`);
    process.exit(2);
  }
  const repoRoot = findRepoRoot(process.cwd());
  if (!repoRoot) {
    process.stderr.write("error: not inside a git repository\n");
    process.exit(2);
  }
  if (!refExists2(repoRoot, range.from)) {
    process.stderr.write(`error: unknown git ref "${range.from}"
`);
    process.exit(2);
  }
  if (!refExists2(repoRoot, range.to)) {
    process.stderr.write(`error: unknown git ref "${range.to}"
`);
    process.exit(2);
  }
  try {
    const result = await runEyeDiff({ from: range.from, to: range.to, format: parsed.format }, repoRoot);
    if (parsed.format === "human") {
      process.stdout.write(formatBreakingChangesHuman(result) + "\n");
    } else {
      process.stdout.write(JSON.stringify(result, null, 2) + "\n");
    }
    process.exit(0);
  } catch (err) {
    process.stderr.write(`error: ${String(err)}
`);
    process.exit(4);
  }
}

// dist/registry.js
import { existsSync as existsSync10, readFileSync as readFileSync7, statSync as statSync4, readdirSync as readdirSync4, mkdirSync as mkdirSync4 } from "fs";
import { tmpdir as tmpdir2 } from "os";

// dist/defaults.js
import { homedir } from "os";
import { join as join8 } from "path";
function getDefaultDbPath() {
  const dataHome = process.env.XDG_DATA_HOME ?? join8(homedir(), ".local", "share");
  return join8(dataHome, "eye", "eye_scan.db");
}

// dist/registry.js
import { join as join9, resolve as resolve6, relative as relative5, dirname as dirname7, sep, basename as basename3 } from "path";
import Database from "better-sqlite3";

// dist/backends/registry-tree-sitter-bridge.js
import { Parser as Parser3 } from "web-tree-sitter";
import { readFileSync as readFileSync6 } from "fs";
import { relative as relative4 } from "path";
async function parseFile(lang, absFile) {
  let source;
  try {
    source = readFileSync6(absFile, "utf8");
  } catch (err) {
    process.stderr.write(`[registry-ts-bridge] warn: cannot read ${absFile}: ${String(err)}
`);
    return null;
  }
  let grammar;
  try {
    grammar = await getLanguage(lang);
  } catch (err) {
    process.stderr.write(`[registry-ts-bridge] warn: grammar load failed for lang=${lang}: ${String(err)}
`);
    return null;
  }
  const parser = new Parser3();
  parser.setLanguage(grammar);
  let rootNode;
  try {
    const tree = parser.parse(source);
    if (!tree) {
      process.stderr.write(`[registry-ts-bridge] warn: tree-sitter returned null tree for ${absFile}
`);
      return null;
    }
    rootNode = tree.rootNode;
  } catch (err) {
    process.stderr.write(`[registry-ts-bridge] warn: parse failed for ${absFile}: ${String(err)}
`);
    return null;
  }
  return { rootNode, source };
}
function toRepoExport(sym, relFile, language) {
  if (!sym.isPublic)
    return null;
  return {
    symbol: sym.name,
    file: relFile,
    language,
    ...sym.isTypeExport ? { isTypeExport: true } : {}
  };
}
async function extractTsExportsAst(absFile, repoRoot) {
  const lang = absFile.endsWith(".tsx") ? "tsx" : "ts";
  const parsed = await parseFile(lang, absFile);
  if (!parsed)
    return [];
  const { rootNode } = parsed;
  const relFile = relative4(repoRoot, absFile);
  const fileBasename = relFile.replace(/\.[^.]+$/, "").replace(/.*[\\/]/, "");
  const results = [];
  const rawSymbols = extractTsSymbols(rootNode);
  for (const sym of rawSymbols) {
    const exported = toRepoExport(sym, relFile, "ts");
    if (exported)
      results.push(exported);
  }
  const seen = new Set(results.map((e) => e.symbol));
  function walkForExports(node) {
    if (node.type === "export_statement") {
      const children = node.children;
      const lexDecl = children.find((c) => c.type === "lexical_declaration");
      if (lexDecl) {
        for (const child of lexDecl.children) {
          if (child.type === "variable_declarator") {
            const nameNode = child.childForFieldName("name") ?? child.children.find((c) => c.type === "identifier");
            if (nameNode && !seen.has(nameNode.text)) {
              seen.add(nameNode.text);
              results.push({ symbol: nameNode.text, file: relFile, language: "ts" });
            }
          }
        }
      }
      const hasDefault = children.some((c) => c.type === "default");
      if (hasDefault && !seen.has(fileBasename)) {
        seen.add(fileBasename);
        results.push({ symbol: fileBasename, file: relFile, language: "ts" });
      }
      const exportClause = children.find((c) => c.type === "export_clause");
      if (exportClause) {
        const fromStringNode = children.find((c) => c.type === "string");
        const hasFrom = fromStringNode != null;
        const isTypeReexport = hasFrom && children.some((c) => c.type === "type" || c.text === "type");
        if (isTypeReexport) {
          return;
        }
        if (hasFrom) {
          const rawSpecifier = fromStringNode.text.replace(/^['"]|['"]$/g, "");
          for (const specifier of exportClause.children) {
            if (specifier.type === "export_specifier") {
              const hasInlineType = specifier.children.some((c) => c.type === "type");
              const aliasNode = specifier.childForFieldName("alias");
              const nameNode = specifier.childForFieldName("name");
              const exportedName = (aliasNode ?? nameNode)?.text;
              if (exportedName && exportedName !== "default") {
                results.push({
                  symbol: exportedName,
                  file: relFile,
                  language: "ts",
                  reexportedFrom: rawSpecifier,
                  ...hasInlineType ? { isTypeExport: true } : {}
                });
              }
            }
          }
        } else {
          for (const specifier of exportClause.children) {
            if (specifier.type === "export_specifier") {
              const hasInlineType = specifier.children.some((c) => c.type === "type");
              const aliasNode = specifier.childForFieldName("alias");
              const nameNode = specifier.childForFieldName("name");
              const exportedName = (aliasNode ?? nameNode)?.text;
              if (exportedName && !seen.has(exportedName)) {
                seen.add(exportedName);
                results.push({
                  symbol: exportedName,
                  file: relFile,
                  language: "ts",
                  ...hasInlineType ? { isTypeExport: true } : {}
                });
              }
            }
          }
        }
      }
      if (!exportClause) {
        const namespaceExportNode = children.find((c) => c.type === "namespace_export");
        const hasStar = namespaceExportNode != null || children.some((c) => c.text === "*");
        const starFromNode = children.find((c) => c.type === "string");
        if (hasStar && starFromNode) {
          const rawSpecifier = starFromNode.text.replace(/^['"]|['"]$/g, "");
          const aliasIdentifier = namespaceExportNode?.children.find((c) => c.type === "identifier");
          if (aliasIdentifier) {
            results.push({ symbol: aliasIdentifier.text, file: relFile, language: "ts", reexportedFrom: rawSpecifier });
          } else {
            results.push({ symbol: "*", file: relFile, language: "ts", reexportedFrom: rawSpecifier });
          }
        }
      }
    }
    for (const child of node.children)
      walkForExports(child);
  }
  walkForExports(rootNode);
  return results;
}
async function extractTsImportsAst(absFile, _repoRoot) {
  const lang = absFile.endsWith(".tsx") ? "tsx" : "ts";
  const parsed = await parseFile(lang, absFile);
  if (!parsed)
    return [];
  const { rootNode, source } = parsed;
  const results = [];
  function walk(node) {
    if (node.type === "import_statement") {
      const sourceNode = node.childForFieldName("source");
      if (!sourceNode) {
        for (const child of node.children)
          walk(child);
        return;
      }
      const importPath = sourceNode.text.replace(/^['"]|['"]$/g, "");
      const line = node.startPosition.row + 1;
      const importClause = node.children.find((c) => c.type === "import_clause");
      if (!importClause) {
        results.push({ file: absFile, line, importPath });
        return;
      }
      const hasTopLevelType = node.children.some((c) => c.type === "type" || c.type === "identifier" && c.text === "type");
      const namedImports = importClause.children.find((c) => c.type === "named_imports");
      const namespaceImport = importClause.children.find((c) => c.type === "namespace_import");
      if (namedImports) {
        const specifierNodes = namedImports.children.filter((c) => c.type === "import_specifier");
        let namedIsTypeOnly = false;
        if (hasTopLevelType) {
          namedIsTypeOnly = true;
        } else if (specifierNodes.length > 0) {
          namedIsTypeOnly = specifierNodes.every((specifier) => specifier.children.some((c) => c.type === "type"));
        }
        for (const specifier of specifierNodes) {
          const nameNode = specifier.childForFieldName("name");
          if (!nameNode)
            continue;
          const symbol = nameNode.text;
          if (symbol && !symbol.startsWith("...")) {
            const record = { file: absFile, line, importPath, symbol };
            if (namedIsTypeOnly)
              record.isTypeOnly = true;
            results.push(record);
          }
        }
      } else if (namespaceImport) {
        results.push({ file: absFile, line, importPath });
      } else {
        results.push({ file: absFile, line, importPath });
      }
      return;
    }
    if (node.type === "call_expression") {
      const fnNode = node.childForFieldName("function");
      if (fnNode?.text === "require") {
        const argsNode = node.childForFieldName("arguments");
        if (argsNode) {
          const strNode = argsNode.children.find((c) => c.type === "string");
          if (strNode) {
            const importPath = strNode.text.replace(/^['"]|['"]$/g, "");
            const line = node.startPosition.row + 1;
            results.push({ file: absFile, line, importPath });
          }
        }
      }
      return;
    }
    for (const child of node.children)
      walk(child);
  }
  walk(rootNode);
  void source;
  return results;
}
async function extractGoImportsWithSymbols(absFile, _repoRoot) {
  const parsed = await parseFile("go", absFile);
  if (!parsed)
    return null;
  const { rootNode } = parsed;
  const imports = [];
  function collectImports(node) {
    if (node.type === "import_spec") {
      const pathNode = node.childForFieldName("path");
      if (!pathNode)
        return;
      const importPath = pathNode.text.replace(/^["']|["']$/g, "");
      const line = node.startPosition.row + 1;
      const nameNode = node.childForFieldName("name");
      let localName;
      if (nameNode) {
        localName = nameNode.text;
      } else {
        const segments = importPath.split("/");
        localName = segments[segments.length - 1] ?? importPath;
      }
      if (localName === "_" || localName === ".")
        return;
      imports.push({ importPath, localName, line });
      return;
    }
    for (const child of node.children)
      collectImports(child);
  }
  collectImports(rootNode);
  const MAX_DEPTH2 = 60;
  const usedSelectors = /* @__PURE__ */ new Map();
  function addUsage(localName, symbol) {
    let s = usedSelectors.get(localName);
    if (!s) {
      s = /* @__PURE__ */ new Set();
      usedSelectors.set(localName, s);
    }
    s.add(symbol);
  }
  function walkUsages(node, depth) {
    if (depth > MAX_DEPTH2)
      return;
    if (node.type === "selector_expression") {
      const operand = node.childForFieldName("operand");
      const field = node.childForFieldName("field");
      if (operand && field && operand.type === "identifier") {
        addUsage(operand.text, field.text);
      }
    } else if (node.type === "qualified_type") {
      const pkg = node.childForFieldName("package");
      const name = node.childForFieldName("name");
      if (pkg && name) {
        addUsage(pkg.text, name.text);
      }
      return;
    }
    for (const child of node.children)
      walkUsages(child, depth + 1);
  }
  walkUsages(rootNode, 0);
  return { imports, usedSelectors };
}
async function runImportAstValidate(absFiles, repoRoot, regexFn) {
  let totalFiles = 0;
  let filesWithDiff = 0;
  let regexOnlyCount = 0;
  let astOnlyCount = 0;
  for (const absFile of absFiles) {
    let key2 = function(r) {
      return `${r.importPath}|${r.symbol ?? ""}`;
    };
    var key = key2;
    const regexResults = regexFn(absFile);
    const astResults = await extractTsImportsAst(absFile, repoRoot);
    totalFiles++;
    const regexKeys = /* @__PURE__ */ new Map();
    for (const r of regexResults)
      regexKeys.set(key2(r), r);
    const astKeys = /* @__PURE__ */ new Map();
    for (const r of astResults)
      astKeys.set(key2(r), r);
    const regexOnly = [];
    const astOnly = [];
    for (const [k, r] of regexKeys) {
      if (!astKeys.has(k))
        regexOnly.push(r);
    }
    for (const [k, r] of astKeys) {
      if (!regexKeys.has(k))
        astOnly.push(r);
    }
    if (regexOnly.length > 0 || astOnly.length > 0) {
      filesWithDiff++;
      regexOnlyCount += regexOnly.length;
      astOnlyCount += astOnly.length;
      const rel = absFile.includes(repoRoot) ? absFile.slice(repoRoot.length + 1) : absFile;
      process.stderr.write(`[EYE_AST_IMPORT_VALIDATE] DIFF in ${rel}:
`);
      for (const r of regexOnly) {
        process.stderr.write(`  regex-only  line=${r.line} ${r.importPath} symbol=${r.symbol ?? "(none)"}
`);
      }
      for (const r of astOnly) {
        process.stderr.write(`  ast-only    line=${r.line} ${r.importPath} symbol=${r.symbol ?? "(none)"}
`);
      }
    }
  }
  process.stderr.write(`[EYE_AST_IMPORT_VALIDATE] SUMMARY: files=${totalFiles} diffs=${filesWithDiff} regex-only=${regexOnlyCount} ast-only=${astOnlyCount}
`);
}
function parsePythonDunderAll(source) {
  const startMatch = source.match(/^__all__\s*=\s*\[/m);
  if (!startMatch || startMatch.index === void 0)
    return null;
  const openBracketPos = startMatch.index + startMatch[0].length - 1;
  const afterOpen = openBracketPos + 1;
  const closeBracket = source.indexOf("]", afterOpen);
  if (closeBracket === -1)
    return null;
  const inner = source.slice(afterOpen, closeBracket);
  const names = /* @__PURE__ */ new Set();
  const quoted = /['"]([A-Za-z_][A-Za-z0-9_]*)['"]/g;
  let m;
  while ((m = quoted.exec(inner)) !== null) {
    names.add(m[1]);
  }
  return names;
}
async function extractSymbolsFromFile(lang, absFile, repoRoot) {
  const parsed = await parseFile(lang, absFile);
  if (!parsed)
    return [];
  const { rootNode, source } = parsed;
  const relFile = relative4(repoRoot, absFile);
  const results = [];
  let rawSymbols = [];
  switch (lang) {
    case "cs": {
      rawSymbols = extractCSharpSymbols(rootNode, source);
      break;
    }
    case "go":
      rawSymbols = extractGoSymbols(rootNode);
      break;
    case "py":
      rawSymbols = extractPythonSymbols(rootNode);
      break;
    case "php":
      rawSymbols = extractPhpSymbols(rootNode);
      break;
    case "ts":
    case "tsx":
      rawSymbols = extractTsSymbols(rootNode);
      break;
    case "swift":
      rawSymbols = extractSwiftSymbols(rootNode);
      break;
    case "java":
      rawSymbols = extractJavaSymbols(rootNode);
      break;
    case "rs":
      rawSymbols = extractRustSymbols(rootNode);
      break;
    case "kt":
      rawSymbols = extractKotlinSymbols(rootNode);
      break;
  }
  const registryLang = lang === "tsx" ? "ts" : lang;
  let allowedNames = null;
  if (lang === "py") {
    allowedNames = parsePythonDunderAll(source);
  }
  for (const sym of rawSymbols) {
    if (allowedNames !== null && !allowedNames.has(sym.name)) {
      continue;
    }
    const exported = toRepoExport(sym, relFile, registryLang);
    if (exported)
      results.push(exported);
  }
  return results;
}
async function treeSitterExtractExports(lang, absFiles, repoRoot) {
  const results = [];
  for (const absFile of absFiles) {
    try {
      const fileResults = await extractSymbolsFromFile(lang, absFile, repoRoot);
      results.push(...fileResults);
    } catch (err) {
      process.stderr.write(`[registry-ts-bridge] warn: unexpected error for ${absFile}: ${String(err)}
`);
    }
  }
  return results;
}

// dist/registry.js
var CREATE_TABLE = `
CREATE TABLE IF NOT EXISTS cross_repo_refs (
  id            INTEGER PRIMARY KEY AUTOINCREMENT,
  symbol        TEXT NOT NULL,
  source_repo   TEXT NOT NULL,
  source_file   TEXT NOT NULL,
  consumer_repo TEXT NOT NULL,
  consumer_file TEXT NOT NULL,
  consumer_line INTEGER,
  confidence    TEXT,
  last_seen     TEXT,
  created_at    TEXT DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_crr_symbol ON cross_repo_refs (symbol);
CREATE INDEX IF NOT EXISTS idx_crr_source ON cross_repo_refs (source_repo, symbol);
CREATE INDEX IF NOT EXISTS idx_crr_consumer ON cross_repo_refs (consumer_repo);

CREATE TABLE IF NOT EXISTS exported_symbols (
  id               INTEGER PRIMARY KEY AUTOINCREMENT,
  repo             TEXT NOT NULL,
  file             TEXT NOT NULL,
  symbol           TEXT NOT NULL,
  language         TEXT NOT NULL,
  last_seen        TEXT NOT NULL,
  is_type_export   INTEGER DEFAULT 0,
  reexported_from  TEXT DEFAULT NULL,
  UNIQUE(repo, file, symbol)
);

CREATE INDEX IF NOT EXISTS idx_es_repo_symbol ON exported_symbols (repo, symbol);
`;
var CREATE_REGISTRY_META = `
CREATE TABLE IF NOT EXISTS registry_meta (
  repo_name      TEXT PRIMARY KEY,
  last_build_sha TEXT NOT NULL,
  last_build_ts  TEXT NOT NULL
);
`;
function openDb(dbPath) {
  const dir = dirname7(dbPath);
  if (!existsSync10(dir))
    mkdirSync4(dir, { recursive: true });
  const db = new Database(dbPath);
  db.pragma("journal_mode = WAL");
  db.pragma("busy_timeout = 5000");
  db.pragma("foreign_keys = ON");
  db.exec(CREATE_TABLE);
  db.exec(CREATE_REGISTRY_META);
  try {
    db.exec("ALTER TABLE exported_symbols ADD COLUMN is_type_export INTEGER DEFAULT 0");
  } catch {
  }
  try {
    db.exec("ALTER TABLE exported_symbols ADD COLUMN reexported_from TEXT DEFAULT NULL");
  } catch {
  }
  return db;
}
function walkFiles2(dir, exts) {
  return walkFiles(dir, { extensions: exts });
}
var MAX_SCAN_FILE_BYTES = 2 * 1024 * 1024;
function readFileCapped(filePath) {
  try {
    const size = statSync4(filePath).size;
    if (size > MAX_SCAN_FILE_BYTES) {
      process.stderr.write(`[eye-registry] warn: skipping large file (${size} bytes > ${MAX_SCAN_FILE_BYTES}): ${filePath}
`);
      return null;
    }
    return readFileSync7(filePath, "utf8");
  } catch {
    return null;
  }
}
function extractCsNamespaceRoots(repoPath) {
  const csFiles = walkFiles2(repoPath, /\.cs$/);
  const roots = /* @__PURE__ */ new Set();
  const nsRe = /^namespace\s+([\w.]+)/gm;
  for (const f of csFiles) {
    const content = readFileCapped(f);
    if (content === null)
      continue;
    let m;
    nsRe.lastIndex = 0;
    while ((m = nsRe.exec(content)) !== null) {
      const fullNs = m[1];
      const topLevel = fullNs.split(".")[0];
      if (topLevel)
        roots.add(topLevel);
    }
  }
  return [...roots];
}
function extractRepoMeta(repoPath, repoName) {
  const meta = { name: repoName, path: repoPath };
  const pkgJson = join9(repoPath, "package.json");
  if (existsSync10(pkgJson)) {
    try {
      const pkg = JSON.parse(readFileSync7(pkgJson, "utf8"));
      if (pkg.name)
        meta.npmName = pkg.name;
    } catch {
    }
  }
  const pyproject = join9(repoPath, "pyproject.toml");
  if (existsSync10(pyproject)) {
    try {
      const content = readFileSync7(pyproject, "utf8");
      const m = content.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
      if (m)
        meta.pyPackages = [m[1], m[1].replace(/-/g, "_")];
    } catch {
    }
  }
  const setupCfg = join9(repoPath, "setup.cfg");
  if (!meta.pyPackages && existsSync10(setupCfg)) {
    try {
      const content = readFileSync7(setupCfg, "utf8");
      const m = content.match(/^\s*name\s*=\s*(.+)/m);
      if (m) {
        const n = m[1].trim();
        meta.pyPackages = [n, n.replace(/-/g, "_")];
      }
    } catch {
    }
  }
  const goMod = join9(repoPath, "go.mod");
  if (existsSync10(goMod)) {
    try {
      const content = readFileSync7(goMod, "utf8");
      const m = content.match(/^module\s+(\S+)/m);
      if (m)
        meta.goModule = m[1];
    } catch {
    }
  }
  const csRoots = extractCsNamespaceRoots(repoPath);
  if (csRoots.length > 0) {
    meta.csNamespaceRoots = csRoots;
  }
  const phpRoots = extractPhpNamespaceRoots(repoPath);
  if (phpRoots.length > 0) {
    meta.phpNamespaceRoots = phpRoots;
  }
  const javaRoots = extractJavaPackageRoots(repoPath);
  if (javaRoots.length > 0) {
    meta.javaPackageRoots = javaRoots;
  }
  const cargoToml = join9(repoPath, "Cargo.toml");
  if (existsSync10(cargoToml)) {
    try {
      const content = readFileSync7(cargoToml, "utf8");
      const lines = content.split("\n");
      let inPackage = false;
      const pkgLines = [];
      for (const line of lines) {
        const trimmed = line.trim();
        if (!inPackage) {
          if (!trimmed.startsWith("#") && trimmed === "[package]") {
            inPackage = true;
          }
        } else {
          if (!trimmed.startsWith("#") && trimmed.startsWith("["))
            break;
          pkgLines.push(line);
        }
      }
      if (pkgLines.length > 0) {
        const pkgSection = pkgLines.join("\n");
        const m = pkgSection.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
        if (m)
          meta.rustCrateName = m[1];
      }
    } catch {
    }
  }
  const kotlinRoots = extractKotlinPackageRoots(repoPath);
  if (kotlinRoots.length > 0) {
    meta.kotlinPackageRoots = kotlinRoots;
  }
  const packageSwift = join9(repoPath, "Package.swift");
  if (existsSync10(packageSwift)) {
    try {
      const content = readFileSync7(packageSwift, "utf8");
      const m = content.match(/\.package\s*\(\s*name\s*:\s*"([^"]+)"/);
      const m2 = !m ? content.match(/name\s*:\s*"([^"]+)"/) : null;
      const swiftName = m ? m[1] : m2 ? m2[1] : null;
      if (swiftName)
        meta.swiftModuleName = swiftName;
    } catch {
    }
  }
  if (!meta.swiftModuleName) {
    const derived = repoName.split(/[-_]/).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
    if (derived)
      meta.swiftModuleName = derived;
  }
  return meta;
}
function extractTsExports(absFile, repoRoot) {
  const exports = [];
  const content = readFileCapped(absFile);
  if (content === null)
    return [];
  const relFile = relative5(repoRoot, absFile);
  const namedExport = /(?:^|;)\s*export\s+(?:async\s+)?(function|class|const|let|var|type|interface|enum)\s+([A-Za-z_$][A-Za-z0-9_$]*)/mg;
  let m;
  while ((m = namedExport.exec(content)) !== null) {
    const keyword = m[1];
    const sym = m[2];
    const isTypeExport = keyword === "type" || keyword === "interface";
    exports.push({ symbol: sym, file: relFile, language: "ts", ...isTypeExport ? { isTypeExport: true } : {} });
  }
  const reexportBlock = /(?:^|;)\s*export\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/mg;
  while ((m = reexportBlock.exec(content)) !== null) {
    const source = m[2];
    for (const part of m[1].split(",")) {
      const isTypeSpecifier = /^\s*type\s+/.test(part);
      const stripped = part.replace(/^\s*type\s+/, "");
      const alias = stripped.match(/\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*$/);
      const direct = stripped.match(/^\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*$/);
      const name = alias ? alias[1] : direct ? direct[1] : null;
      if (name && name !== "default") {
        exports.push({
          symbol: name,
          file: relFile,
          language: "ts",
          reexportedFrom: source,
          ...isTypeSpecifier ? { isTypeExport: true } : {}
        });
      }
    }
  }
  const namespaceReexport = /(?:^|;)\s*export\s+\*\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)\s+from\s+['"]([^'"]+)['"]/mg;
  while ((m = namespaceReexport.exec(content)) !== null) {
    exports.push({ symbol: m[1], file: relFile, language: "ts", reexportedFrom: m[2] });
  }
  const starReexport = /(?:^|;)\s*export\s+\*\s+from\s+['"]([^'"]+)['"]/mg;
  while ((m = starReexport.exec(content)) !== null) {
    exports.push({ symbol: "*", file: relFile, language: "ts", reexportedFrom: m[1] });
  }
  const exportBlock = /(?:^|;)\s*export\s*\{([^}]+)\}(?!\s*from)/mg;
  while ((m = exportBlock.exec(content)) !== null) {
    for (const part of m[1].split(",")) {
      const isTypeSpecifier = /^\s*type\s+/.test(part);
      const stripped = part.replace(/^\s*type\s+/, "");
      const alias = stripped.match(/\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*$/);
      const direct = stripped.match(/^\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*$/);
      const name = alias ? alias[1] : direct ? direct[1] : null;
      if (name && name !== "default") {
        exports.push({
          symbol: name,
          file: relFile,
          language: "ts",
          ...isTypeSpecifier ? { isTypeExport: true } : {}
        });
      }
    }
  }
  if (/^export\s+default\s+/m.test(content)) {
    const baseName = basename3(absFile).replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
    exports.push({ symbol: baseName, file: relFile, language: "ts" });
  }
  return exports;
}
function extractPhpNamespaceRoots(repoPath) {
  const phpFiles = walkFiles2(repoPath, /\.php$/);
  const roots = /* @__PURE__ */ new Set();
  const nsRe = /^namespace\s+([\w\\]+)/gm;
  for (const f of phpFiles) {
    const content = readFileCapped(f);
    if (content === null)
      continue;
    nsRe.lastIndex = 0;
    let m;
    while ((m = nsRe.exec(content)) !== null) {
      const fullNs = m[1];
      const topLevel = fullNs.split("\\")[0];
      if (topLevel)
        roots.add(topLevel);
    }
  }
  return [...roots];
}
function extractPhpImports(absFile) {
  const imports = [];
  const content = readFileCapped(absFile);
  if (content === null)
    return [];
  const lines = content.split("\n");
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const useMatch = line.match(/^\s*use\s+([\w\\]+)(?:\s+as\s+\w+)?\s*;/);
    if (useMatch) {
      const importPath = useMatch[1];
      const parts = importPath.split("\\");
      const symbol = parts[parts.length - 1];
      imports.push({ file: absFile, line: i + 1, importPath, symbol });
      continue;
    }
    const includeMatch = line.match(/(?:require_once|require|include_once|include)\s*[('"]([^'"()]+\.php)['"')]?\s*;/);
    if (includeMatch) {
      imports.push({ file: absFile, line: i + 1, importPath: includeMatch[1] });
    }
  }
  return imports;
}
function extractJavaImportsFromFile(absFile) {
  const imports = [];
  const content = readFileCapped(absFile);
  if (content === null)
    return [];
  const lines = content.split("\n");
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const m = line.match(/^\s*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;/);
    if (m) {
      const importPath = m[1].replace(/\.\*$/, "");
      const parts = importPath.split(".");
      const symbol = parts[parts.length - 1];
      imports.push({ file: absFile, line: i + 1, importPath, symbol });
    }
  }
  return imports;
}
function extractJavaPackageRoots(repoPath) {
  const javaFiles = walkFiles2(repoPath, /\.java$/);
  const roots = /* @__PURE__ */ new Set();
  for (const f of javaFiles) {
    const content = readFileCapped(f);
    if (content === null)
      continue;
    const m = content.match(/^\s*package\s+([\w.]+)\s*;/m);
    if (m) {
      const segments = m[1].split(".");
      const root = segments.slice(0, Math.min(2, segments.length)).join(".");
      roots.add(root);
    }
  }
  return [...roots];
}
function extractKotlinPackageRoots(repoPath) {
  const ktFiles = walkFiles2(repoPath, /\.kts?$/);
  const roots = /* @__PURE__ */ new Set();
  for (const f of ktFiles) {
    const content = readFileCapped(f);
    if (content === null)
      continue;
    const m = content.match(/^\s*package\s+([\w.]+)/m);
    if (m) {
      const segments = m[1].split(".");
      const root = segments.slice(0, Math.min(2, segments.length)).join(".");
      roots.add(root);
    }
  }
  return [...roots];
}
async function extractRepoExports(meta) {
  const allExports = [];
  const tsFiles = walkFiles2(meta.path, /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/);
  for (const f of tsFiles) {
    if (/\.(ts|tsx)$/.test(f)) {
      const astExports = await extractTsExportsAst(f, meta.path);
      allExports.push(...astExports);
    } else {
      allExports.push(...extractTsExports(f, meta.path));
    }
  }
  const pyFiles = walkFiles2(meta.path, /\.py$/);
  const goFiles = walkFiles2(meta.path, /\.go$/);
  const csFiles = walkFiles2(meta.path, /\.cs$/);
  const phpFiles = walkFiles2(meta.path, /\.php$/);
  const swiftFiles = walkFiles2(meta.path, /\.swift$/);
  const javaFiles = walkFiles2(meta.path, /\.java$/);
  const rsFiles = walkFiles2(meta.path, /\.rs$/);
  const ktFiles = walkFiles2(meta.path, /\.kts?$/);
  const langTasks = [];
  if (pyFiles.length > 0)
    langTasks.push({ lang: "py", files: pyFiles, promise: treeSitterExtractExports("py", pyFiles, meta.path) });
  if (goFiles.length > 0)
    langTasks.push({ lang: "go", files: goFiles, promise: treeSitterExtractExports("go", goFiles, meta.path) });
  if (csFiles.length > 0)
    langTasks.push({ lang: "cs", files: csFiles, promise: treeSitterExtractExports("cs", csFiles, meta.path) });
  if (phpFiles.length > 0)
    langTasks.push({ lang: "php", files: phpFiles, promise: treeSitterExtractExports("php", phpFiles, meta.path) });
  if (swiftFiles.length > 0)
    langTasks.push({ lang: "swift", files: swiftFiles, promise: treeSitterExtractExports("swift", swiftFiles, meta.path) });
  if (javaFiles.length > 0)
    langTasks.push({ lang: "java", files: javaFiles, promise: treeSitterExtractExports("java", javaFiles, meta.path) });
  if (rsFiles.length > 0)
    langTasks.push({ lang: "rs", files: rsFiles, promise: treeSitterExtractExports("rs", rsFiles, meta.path) });
  if (ktFiles.length > 0)
    langTasks.push({ lang: "kt", files: ktFiles, promise: treeSitterExtractExports("kt", ktFiles, meta.path) });
  const settled = await Promise.allSettled(langTasks.map((t) => t.promise));
  for (let i = 0; i < settled.length; i++) {
    const result = settled[i];
    if (result.status === "fulfilled") {
      allExports.push(...result.value);
    } else {
      process.stderr.write(`[extractRepoExports] warn: ${langTasks[i].lang} export extraction failed: ${String(result.reason)}
`);
    }
  }
  return allExports;
}
function importPathMatchesRepo(importPath, sourceMeta) {
  if (sourceMeta.npmName) {
    if (importPath === sourceMeta.npmName || importPath.startsWith(sourceMeta.npmName + "/")) {
      return true;
    }
  }
  if (sourceMeta.pyPackages) {
    for (const pkg of sourceMeta.pyPackages) {
      if (importPath === pkg || importPath.startsWith(pkg + ".") || importPath.startsWith(pkg + "/")) {
        return true;
      }
    }
  }
  if (sourceMeta.goModule) {
    if (importPath === sourceMeta.goModule || importPath.startsWith(sourceMeta.goModule + "/")) {
      return true;
    }
  }
  if (sourceMeta.csNamespaceRoots) {
    for (const root of sourceMeta.csNamespaceRoots) {
      if (importPath === root || importPath.startsWith(root + ".")) {
        return true;
      }
    }
  }
  if (sourceMeta.phpNamespaceRoots) {
    const normImport = importPath.replace(/\//g, "\\");
    for (const root of sourceMeta.phpNamespaceRoots) {
      if (normImport === root || normImport.startsWith(root + "\\")) {
        return true;
      }
    }
  }
  if (sourceMeta.swiftModuleName) {
    if (importPath === sourceMeta.swiftModuleName) {
      return true;
    }
  }
  if (sourceMeta.javaPackageRoots) {
    for (const root of sourceMeta.javaPackageRoots) {
      if (importPath === root || importPath.startsWith(root + ".")) {
        return true;
      }
    }
  }
  if (sourceMeta.rustCrateName) {
    const normCrate = sourceMeta.rustCrateName.replace(/-/g, "_");
    const firstSegment = importPath.split("::")[0].replace(/-/g, "_");
    if (firstSegment === normCrate) {
      return true;
    }
  }
  if (sourceMeta.kotlinPackageRoots) {
    for (const root of sourceMeta.kotlinPackageRoots) {
      if (importPath === root || importPath.startsWith(root + ".")) {
        return true;
      }
    }
  }
  return false;
}
function extractTsImports(absFile) {
  const imports = [];
  const content = readFileCapped(absFile);
  if (content === null)
    return [];
  const logicalImports = [];
  const lines = content.split("\n");
  let i = 0;
  while (i < lines.length) {
    const line = lines[i];
    if (/^\s*import\s/.test(line)) {
      const startLine = i + 1;
      if (/\bfrom\s+['"][^'"]+['"]/.test(line)) {
        logicalImports.push({ logical: line, startLine });
        i++;
      } else {
        const parts = [line];
        i++;
        let closed = false;
        while (i < lines.length && !closed) {
          const nextLine = lines[i];
          parts.push(nextLine);
          if (/\bfrom\s+['"][^'"]+['"]/.test(nextLine) || /^\s*;?\s*$/.test(nextLine) && parts.length > 1 && /;\s*$/.test(parts[parts.length - 2])) {
            closed = true;
          }
          if (/\bfrom\s+['"][^'"]+['"]/.test(nextLine)) {
            closed = true;
          }
          i++;
          if (!closed && /;\s*$/.test(nextLine) && !/\bfrom\s+/.test(nextLine)) {
            closed = true;
          }
        }
        const logical = parts.join(" ");
        logicalImports.push({ logical, startLine });
      }
    } else {
      i++;
    }
  }
  for (const { logical, startLine } of logicalImports) {
    const m = logical.match(/^\s*import\s+(?:type\s+)?.*?\s+from\s+['"]([^'"]+)['"]/);
    if (m) {
      const importPath = m[1];
      const namedMatch = logical.match(/\{([^}]+)\}/);
      if (namedMatch) {
        for (const part of namedMatch[1].split(",")) {
          const stripped = part.replace(/^\s*type\s+/, "");
          const sym = stripped.trim().split(/\s+as\s+/)[0].trim();
          if (sym && !sym.startsWith("...")) {
            imports.push({ file: absFile, line: startLine, importPath, symbol: sym });
          }
        }
      } else {
        imports.push({ file: absFile, line: startLine, importPath });
      }
    }
  }
  for (let j = 0; j < lines.length; j++) {
    const req = lines[j].match(/require\(['"]([^'"]+)['"]\)/);
    if (req) {
      imports.push({ file: absFile, line: j + 1, importPath: req[1] });
    }
  }
  return imports;
}
function extractPyImports(absFile) {
  const imports = [];
  const content = readFileCapped(absFile);
  if (content === null)
    return [];
  const lines = content.split("\n");
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const fromMatch = line.match(/^\s*from\s+([\w.]+)\s+import\s+(.*)/);
    if (fromMatch) {
      const importPath = fromMatch[1];
      let symbolSource = fromMatch[2].trim();
      if (symbolSource.startsWith("(") && !symbolSource.includes(")")) {
        const parts = [symbolSource.slice(1)];
        let j = i + 1;
        while (j < lines.length) {
          const nextLine = lines[j];
          const closingIdx = nextLine.indexOf(")");
          if (closingIdx !== -1) {
            parts.push(nextLine.slice(0, closingIdx));
            break;
          }
          parts.push(nextLine);
          j++;
        }
        symbolSource = parts.join(",");
      } else if (symbolSource.startsWith("(")) {
        symbolSource = symbolSource.replace(/^\(/, "").replace(/\).*$/, "");
      }
      for (const part of symbolSource.split(",")) {
        const sym = part.trim().split(/\s+as\s+/)[0].trim();
        if (sym && sym !== "*") {
          imports.push({ file: absFile, line: i + 1, importPath, symbol: sym });
        }
      }
      continue;
    }
    const impMatch = line.match(/^\s*import\s+([\w.]+)/);
    if (impMatch && !line.match(/^\s*from\s+/)) {
      imports.push({ file: absFile, line: i + 1, importPath: impMatch[1] });
    }
  }
  return imports;
}
function extractGoImports(absFile) {
  const imports = [];
  const content = readFileCapped(absFile);
  if (content === null)
    return [];
  const lines = content.split("\n");
  const importLineIndices = /* @__PURE__ */ new Set();
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    if (/^\s*import\s+(?:\w+\s+)?["']/.test(line)) {
      importLineIndices.add(i);
      continue;
    }
    if (/^\s*import\s*\(/.test(line)) {
      let j = i + 1;
      while (j < lines.length && !/^\s*\)/.test(lines[j])) {
        importLineIndices.add(j);
        j++;
      }
      i = j;
    }
  }
  for (const idx of importLineIndices) {
    const line = lines[idx];
    const m = line.match(/^\s*(?:\w+\s+)?["']([^"']+)["']/);
    if (m) {
      imports.push({ file: absFile, line: idx + 1, importPath: m[1] });
    }
  }
  return imports;
}
function extractCsImports(absFile) {
  const imports = [];
  const content = readFileCapped(absFile);
  if (content === null)
    return [];
  const lines = content.split("\n");
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const aliasMatch = line.match(/^\s*using\s+(\w+)\s*=\s*([\w.]+)\s*;/);
    if (aliasMatch) {
      const alias = aliasMatch[1];
      const resolvedPath = aliasMatch[2];
      imports.push({ file: absFile, line: i + 1, importPath: resolvedPath, symbol: alias });
      const parts = resolvedPath.split(".");
      const lastPart = parts[parts.length - 1];
      if (/^[A-Z]/.test(lastPart) && lastPart !== alias) {
        imports.push({ file: absFile, line: i + 1, importPath: resolvedPath, symbol: lastPart });
      }
      continue;
    }
    const m = line.match(/^\s*using\s+(?:static\s+)?([\w.]+)\s*;/);
    if (m) {
      const importPath = m[1];
      const parts = importPath.split(".");
      const lastPart = parts[parts.length - 1];
      const symbol = /^[A-Z]/.test(lastPart) ? lastPart : void 0;
      imports.push({ file: absFile, line: i + 1, importPath, symbol });
    }
  }
  return imports;
}
async function scanConsumerImports(consumerMeta, sourceMeta, sourceExportSet, tsFiles, pyFiles, goFiles, csFiles, phpFiles = [], javaFiles = []) {
  const now = (/* @__PURE__ */ new Date()).toISOString();
  const rows = [];
  for (const f of tsFiles) {
    let rawImports;
    try {
      rawImports = await extractTsImportsAst(f, consumerMeta.path);
    } catch (err) {
      process.stderr.write(`[registry] warn: AST import extraction failed for ${f}, falling back to regex: ${String(err)}
`);
      rawImports = extractTsImports(f);
    }
    for (const imp of rawImports) {
      if (!importPathMatchesRepo(imp.importPath, sourceMeta))
        continue;
      const consumerFile = relative5(consumerMeta.path, f);
      if (imp.symbol && sourceExportSet.has(imp.symbol)) {
        rows.push({
          symbol: imp.symbol,
          source_repo: sourceMeta.name,
          source_file: sourceExportSet.get(imp.symbol),
          consumer_repo: consumerMeta.name,
          consumer_file: consumerFile,
          consumer_line: imp.line,
          confidence: crossRefConfidence("ts", true),
          last_seen: now
        });
      } else if (!imp.symbol) {
        rows.push({
          symbol: "*",
          source_repo: sourceMeta.name,
          source_file: "",
          consumer_repo: consumerMeta.name,
          consumer_file: consumerFile,
          consumer_line: imp.line,
          confidence: "low",
          last_seen: now
        });
      }
    }
  }
  for (const f of pyFiles) {
    const rawImports = extractPyImports(f);
    for (const imp of rawImports) {
      if (!importPathMatchesRepo(imp.importPath, sourceMeta))
        continue;
      const consumerFile = relative5(consumerMeta.path, f);
      if (imp.symbol && sourceExportSet.has(imp.symbol)) {
        rows.push({
          symbol: imp.symbol,
          source_repo: sourceMeta.name,
          source_file: sourceExportSet.get(imp.symbol),
          consumer_repo: consumerMeta.name,
          consumer_file: consumerFile,
          consumer_line: imp.line,
          confidence: crossRefConfidence("py", true),
          last_seen: now
        });
      }
    }
  }
  for (const f of goFiles) {
    const consumerFile = relative5(consumerMeta.path, f);
    const goData = await extractGoImportsWithSymbols(f, consumerMeta.path);
    if (goData === null) {
      const rawImports = extractGoImports(f);
      for (const imp of rawImports) {
        if (!importPathMatchesRepo(imp.importPath, sourceMeta))
          continue;
        if (sourceExportSet.size > 0) {
          for (const [sym, srcFile] of sourceExportSet) {
            rows.push({
              symbol: sym,
              source_repo: sourceMeta.name,
              source_file: srcFile,
              consumer_repo: consumerMeta.name,
              consumer_file: consumerFile,
              consumer_line: imp.line,
              confidence: crossRefConfidence("go", true),
              last_seen: now
            });
          }
        } else {
          rows.push({
            symbol: "*",
            source_repo: sourceMeta.name,
            source_file: "",
            consumer_repo: consumerMeta.name,
            consumer_file: consumerFile,
            consumer_line: imp.line,
            confidence: "low",
            last_seen: now
          });
        }
      }
      continue;
    }
    for (const imp of goData.imports) {
      if (!importPathMatchesRepo(imp.importPath, sourceMeta))
        continue;
      if (sourceExportSet.size === 0) {
        rows.push({
          symbol: "*",
          source_repo: sourceMeta.name,
          source_file: "",
          consumer_repo: consumerMeta.name,
          consumer_file: consumerFile,
          consumer_line: imp.line,
          confidence: "low",
          last_seen: now
        });
        continue;
      }
      const used = goData.usedSelectors.get(imp.localName);
      if (!used || used.size === 0) {
        continue;
      }
      for (const sym of used) {
        if (!sourceExportSet.has(sym))
          continue;
        rows.push({
          symbol: sym,
          source_repo: sourceMeta.name,
          source_file: sourceExportSet.get(sym),
          consumer_repo: consumerMeta.name,
          consumer_file: consumerFile,
          consumer_line: imp.line,
          confidence: "high",
          last_seen: now
        });
      }
    }
  }
  for (const f of csFiles) {
    const rawImports = extractCsImports(f);
    for (const imp of rawImports) {
      if (!importPathMatchesRepo(imp.importPath, sourceMeta))
        continue;
      const consumerFile = relative5(consumerMeta.path, f);
      if (imp.symbol && sourceExportSet.has(imp.symbol)) {
        rows.push({
          symbol: imp.symbol,
          source_repo: sourceMeta.name,
          source_file: sourceExportSet.get(imp.symbol),
          consumer_repo: consumerMeta.name,
          consumer_file: consumerFile,
          consumer_line: imp.line,
          confidence: crossRefConfidence("cs", !!imp.symbol),
          last_seen: now
        });
      }
    }
  }
  for (const f of phpFiles) {
    const rawImports = extractPhpImports(f);
    for (const imp of rawImports) {
      if (!importPathMatchesRepo(imp.importPath, sourceMeta))
        continue;
      const consumerFile = relative5(consumerMeta.path, f);
      if (imp.symbol && sourceExportSet.has(imp.symbol)) {
        rows.push({
          symbol: imp.symbol,
          source_repo: sourceMeta.name,
          source_file: sourceExportSet.get(imp.symbol),
          consumer_repo: consumerMeta.name,
          consumer_file: consumerFile,
          consumer_line: imp.line,
          confidence: crossRefConfidence("php", true),
          last_seen: now
        });
      } else if (!imp.symbol) {
        rows.push({
          symbol: "*",
          source_repo: sourceMeta.name,
          source_file: "",
          consumer_repo: consumerMeta.name,
          consumer_file: consumerFile,
          consumer_line: imp.line,
          confidence: crossRefConfidence("php", false),
          last_seen: now
        });
      }
    }
  }
  for (const f of javaFiles) {
    const rawImports = extractJavaImportsFromFile(f);
    for (const imp of rawImports) {
      if (!importPathMatchesRepo(imp.importPath, sourceMeta))
        continue;
      const consumerFile = relative5(consumerMeta.path, f);
      if (imp.symbol && sourceExportSet.has(imp.symbol)) {
        rows.push({
          symbol: imp.symbol,
          source_repo: sourceMeta.name,
          source_file: sourceExportSet.get(imp.symbol),
          consumer_repo: consumerMeta.name,
          consumer_file: consumerFile,
          consumer_line: imp.line,
          confidence: crossRefConfidence("java", true),
          last_seen: now
        });
      } else if (!imp.symbol) {
        rows.push({
          symbol: "*",
          source_repo: sourceMeta.name,
          source_file: "",
          consumer_repo: consumerMeta.name,
          consumer_file: consumerFile,
          consumer_line: imp.line,
          confidence: crossRefConfidence("java", false),
          last_seen: now
        });
      }
    }
  }
  return rows;
}
function scanLoaderPatterns(scanDir, patterns) {
  const phpFiles = walkFiles2(scanDir, /\.php$/);
  const hits = [];
  for (const f of phpFiles) {
    const content = readFileCapped(f);
    if (content === null)
      continue;
    for (const pattern of patterns) {
      const re = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags);
      re.lastIndex = 0;
      let m;
      while ((m = re.exec(content)) !== null) {
        const viewName = m[1];
        if (viewName) {
          hits.push({ callerFile: f, viewName });
        }
        if (re.lastIndex === 0)
          break;
      }
    }
  }
  return hits;
}
function resolveViewPath(viewName, viewsDirAbs) {
  const nameNorm = viewName.replace(/\\/g, "/");
  const nameFinal = /\.\w+$/.test(nameNorm) ? nameNorm : `${nameNorm}.php`;
  const resolved = resolve6(viewsDirAbs, nameFinal);
  const guardPrefix = viewsDirAbs.endsWith(sep) ? viewsDirAbs : viewsDirAbs + sep;
  if (!resolved.startsWith(guardPrefix)) {
    process.stderr.write(`[eye-registry] warn: view path traversal blocked \u2014 "${viewName}" resolves outside viewsDir (${viewsDirAbs})
`);
    return null;
  }
  if (!existsSync10(resolved))
    return null;
  return resolved;
}
function buildViewEdges(hits, viewsDirAbs, repoMeta) {
  const now = (/* @__PURE__ */ new Date()).toISOString();
  const rows = [];
  for (const { callerFile, viewName } of hits) {
    const absViewFile = resolveViewPath(viewName, viewsDirAbs);
    if (!absViewFile)
      continue;
    const content = readFileCapped(absViewFile);
    if (content === null)
      continue;
    const relViewFile = relative5(repoMeta.path, absViewFile);
    const relCallerFile = relative5(repoMeta.path, callerFile);
    const symbolRe = /^\s*(?:(?:abstract|final)\s+)?(?:class|function|interface|trait)\s+([A-Za-z_][A-Za-z0-9_]*)/gm;
    const symbols = [];
    let m;
    symbolRe.lastIndex = 0;
    while ((m = symbolRe.exec(content)) !== null) {
      if (m[1])
        symbols.push(m[1]);
    }
    const effectiveSymbols = symbols.length > 0 ? symbols : [relViewFile];
    for (const sym of effectiveSymbols) {
      rows.push({
        symbol: sym,
        source_repo: repoMeta.name,
        source_file: relViewFile,
        consumer_repo: repoMeta.name,
        consumer_file: relCallerFile,
        consumer_line: 0,
        confidence: "low",
        last_seen: now
      });
    }
  }
  return rows;
}
function crossRefConfidence(lang, hasSymbol) {
  if (lang === "ts" && hasSymbol)
    return "high";
  if (lang === "ts")
    return "medium";
  if (lang === "go")
    return "medium";
  if (lang === "cs" && hasSymbol)
    return "high";
  if (lang === "cs")
    return "medium";
  if (lang === "php" && hasSymbol)
    return "high";
  if (lang === "php")
    return "medium";
  if (lang === "java" && hasSymbol)
    return "high";
  if (lang === "java")
    return "medium";
  return "low";
}
async function buildRegistryIncremental(opts) {
  const t0 = Date.now();
  const db = openDb(opts.dbPath);
  const metaStmt = db.prepare("SELECT repo_name, last_build_sha FROM registry_meta WHERE repo_name = ?");
  const reposToRebuild = [];
  const skippedRepos = [];
  for (const meta of opts.repoPaths) {
    const currentHead = getRepoHead(meta.path);
    const stored = metaStmt.get(meta.name);
    if (stored && stored.last_build_sha && stored.last_build_sha === currentHead) {
      skippedRepos.push(meta);
    } else {
      reposToRebuild.push(meta);
    }
  }
  db.close();
  if (reposToRebuild.length === 0) {
    return {
      reposScanned: 0,
      crossRefsFound: 0,
      durationMs: Date.now() - t0
    };
  }
  const result = await buildRegistry({ ...opts, repoPaths: reposToRebuild, incremental: false });
  const db2 = openDb(opts.dbPath);
  try {
    const now = (/* @__PURE__ */ new Date()).toISOString();
    const updateMetaStmt2 = db2.prepare("INSERT OR REPLACE INTO registry_meta (repo_name, last_build_sha, last_build_ts) VALUES (?, ?, ?)");
    const updateAll = db2.transaction(() => {
      for (const meta of reposToRebuild) {
        const sha = getRepoHead(meta.path);
        updateMetaStmt2.run(meta.name, sha, now);
      }
    });
    updateAll();
  } finally {
    db2.close();
  }
  return {
    reposScanned: result.reposScanned,
    crossRefsFound: result.crossRefsFound,
    durationMs: Date.now() - t0
  };
}
async function buildRegistry(opts) {
  const t0 = Date.now();
  if (opts.incremental) {
    return buildRegistryIncremental(opts);
  }
  const db = openDb(opts.dbPath);
  try {
    const repoExports = /* @__PURE__ */ new Map();
    for (const meta of opts.repoPaths) {
      repoExports.set(meta.name, await extractRepoExports(meta));
    }
    const rows = [];
    for (const consumerMeta of opts.repoPaths) {
      const tsFiles = walkFiles2(consumerMeta.path, /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/);
      const pyFiles = walkFiles2(consumerMeta.path, /\.py$/);
      const goFiles = walkFiles2(consumerMeta.path, /\.go$/);
      const csFiles = walkFiles2(consumerMeta.path, /\.cs$/);
      const phpFiles = walkFiles2(consumerMeta.path, /\.php$/);
      const javaFiles = walkFiles2(consumerMeta.path, /\.java$/);
      if (process.env["EYE_AST_IMPORT_VALIDATE"] === "1") {
        await runImportAstValidate(tsFiles, consumerMeta.path, extractTsImports);
      }
      for (const sourceMeta of opts.repoPaths) {
        if (sourceMeta.name === consumerMeta.name)
          continue;
        const sourceExportSet = /* @__PURE__ */ new Map();
        const sourceExports = repoExports.get(sourceMeta.name) ?? [];
        for (const exp of sourceExports) {
          if (exp.symbol === "*") {
            if (!exp.reexportedFrom)
              continue;
            const barrelDir = dirname7(exp.file);
            const resolved = join9(barrelDir, exp.reexportedFrom).replace(/\\/g, "/");
            const targetBase = resolved.replace(/\.(js|jsx|mjs|cjs)$/, "");
            for (const src of sourceExports) {
              if (src.symbol === "*")
                continue;
              const srcBase = src.file.replace(/\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, "");
              if (srcBase !== targetBase)
                continue;
              if (!sourceExportSet.has(src.symbol)) {
                sourceExportSet.set(src.symbol, src.file);
              } else if (!src.reexportedFrom) {
                sourceExportSet.set(src.symbol, src.file);
              }
            }
            continue;
          }
          if (!sourceExportSet.has(exp.symbol)) {
            sourceExportSet.set(exp.symbol, exp.file);
          } else if (!exp.reexportedFrom) {
            sourceExportSet.set(exp.symbol, exp.file);
          }
        }
        const newRows = await scanConsumerImports(consumerMeta, sourceMeta, sourceExportSet, tsFiles, pyFiles, goFiles, csFiles, phpFiles, javaFiles);
        rows.push(...newRows);
      }
    }
    if (opts.loaderPatterns && opts.loaderPatterns.length > 0 && opts.viewsDir) {
      const viewsDirAbs = resolve6(opts.viewsDir);
      for (const meta of opts.repoPaths) {
        const hits = scanLoaderPatterns(meta.path, opts.loaderPatterns);
        const viewEdges = buildViewEdges(hits, viewsDirAbs, meta);
        rows.push(...viewEdges);
      }
    }
    const seen = /* @__PURE__ */ new Set();
    const dedupedRows = rows.filter((r) => {
      const key = `${r.symbol}|${r.source_repo}|${r.consumer_repo}|${r.consumer_file}|${r.consumer_line}`;
      if (seen.has(key))
        return false;
      seen.add(key);
      return true;
    });
    {
      const repoNames = [...new Set(opts.repoPaths.map((r) => r.name))];
      const placeholders = repoNames.map(() => "?").join(",");
      const deleteStmt = db.prepare(`DELETE FROM cross_repo_refs WHERE source_repo IN (${placeholders}) OR consumer_repo IN (${placeholders})`);
      const insert = db.prepare(`
      INSERT INTO cross_repo_refs
        (symbol, source_repo, source_file, consumer_repo, consumer_file, consumer_line, confidence, last_seen)
      VALUES
        (@symbol, @source_repo, @source_file, @consumer_repo, @consumer_file, @consumer_line, @confidence, @last_seen)
    `);
      const deleteExportedSymbols = db.prepare(`DELETE FROM exported_symbols WHERE repo = ?`);
      const insertExportedSymbol = db.prepare(`
      INSERT OR REPLACE INTO exported_symbols (repo, file, symbol, language, last_seen, is_type_export, reexported_from)
      VALUES (?, ?, ?, ?, ?, ?, ?)
    `);
      const now = (/* @__PURE__ */ new Date()).toISOString();
      const upsertAll = db.transaction((items) => {
        deleteStmt.run(...repoNames, ...repoNames);
        if (items.length > 0) {
          for (const row of items)
            insert.run(row);
        }
        for (const meta of opts.repoPaths) {
          deleteExportedSymbols.run(meta.name);
          const exports = repoExports.get(meta.name) ?? [];
          for (const exp of exports) {
            insertExportedSymbol.run(meta.name, exp.file, exp.symbol, exp.language, now, exp.isTypeExport ? 1 : 0, exp.reexportedFrom ?? null);
          }
        }
      });
      upsertAll(dedupedRows);
    }
    {
      const now = (/* @__PURE__ */ new Date()).toISOString();
      const upsertMeta = db.prepare("INSERT OR REPLACE INTO registry_meta (repo_name, last_build_sha, last_build_ts) VALUES (?, ?, ?)");
      const updateAllMeta = db.transaction(() => {
        for (const meta of opts.repoPaths) {
          const sha = getRepoHead(meta.path);
          upsertMeta.run(meta.name, sha, now);
        }
      });
      updateAllMeta();
    }
    return {
      reposScanned: opts.repoPaths.length,
      crossRefsFound: dedupedRows.length,
      durationMs: Date.now() - t0
    };
  } finally {
    db.close();
  }
}
function confidenceRank(confidence) {
  if (confidence === "high")
    return 2;
  if (confidence === "medium")
    return 1;
  return 0;
}
function queryRegistry(opts) {
  const db = openDb(opts.dbPath);
  try {
    const sourceRow = db.prepare(`SELECT source_file FROM cross_repo_refs WHERE symbol=? AND source_repo=? LIMIT 1`).get(opts.symbol, opts.sourceRepo);
    const sourceFile = sourceRow?.source_file ?? "";
    const exactRows = db.prepare(`
      SELECT consumer_repo, consumer_file, consumer_line, confidence
      FROM cross_repo_refs
      WHERE symbol=? AND source_repo=?
      ORDER BY consumer_repo, consumer_file, consumer_line
    `).all(opts.symbol, opts.sourceRepo);
    const namespaceRows = db.prepare(`
      SELECT consumer_repo, consumer_file, consumer_line, confidence
      FROM cross_repo_refs
      WHERE symbol='*' AND source_repo=?
      ORDER BY consumer_repo, consumer_file, consumer_line
    `).all(opts.sourceRepo);
    const exactConsumers = exactRows.map((r) => ({
      repo: r.consumer_repo,
      file: r.consumer_file,
      line: r.consumer_line ?? 0,
      import_path: "",
      confidence: r.confidence ?? "low"
    }));
    const namespaceConsumers = namespaceRows.map((r) => ({
      repo: r.consumer_repo,
      file: r.consumer_file,
      line: r.consumer_line ?? 0,
      import_path: "namespace import (whole package)",
      confidence: r.confidence ?? "low"
    }));
    const seen = /* @__PURE__ */ new Map();
    for (const row of [...exactConsumers, ...namespaceConsumers]) {
      const key = `${row.repo}:${row.file}:${row.line}`;
      const existing = seen.get(key);
      if (!existing || confidenceRank(row.confidence) > confidenceRank(existing.confidence)) {
        seen.set(key, row);
      }
    }
    const deduped = [...seen.values()];
    return {
      symbol: opts.symbol,
      source_repo: opts.sourceRepo,
      source_file: sourceFile,
      consumers: deduped,
      total_consumers: deduped.length
    };
  } finally {
    db.close();
  }
}
function queryDeadSymbols(opts) {
  const db = openDb(opts.dbPath);
  try {
    const countRow = db.prepare("SELECT COUNT(*) as cnt FROM exported_symbols").get();
    if (countRow.cnt === 0) {
      throw new EyeError("no registry data: run 'eye registry build --org <name>' first", 4);
    }
    const minConfidence = opts.minConfidence ?? "low";
    let confidences;
    if (minConfidence === "high") {
      confidences = ["high"];
    } else if (minConfidence === "medium") {
      confidences = ["medium", "high"];
    } else {
      confidences = ["low", "medium", "high"];
    }
    const confPlaceholders = confidences.map(() => "?").join(",");
    const extraConditions = [];
    const extraParams = [];
    if (opts.repo) {
      extraConditions.push("AND es.repo = ?");
      extraParams.push(opts.repo);
    }
    if (opts.lang) {
      extraConditions.push("AND es.language = ?");
      extraParams.push(opts.lang);
    }
    if (opts.skipTypeExports) {
      extraConditions.push("AND (es.is_type_export IS NULL OR es.is_type_export = 0)");
    }
    const sql = `
      SELECT es.repo, es.file, es.symbol, es.language, es.last_seen
      FROM exported_symbols es
      WHERE NOT EXISTS (
        SELECT 1 FROM cross_repo_refs cr
        WHERE cr.symbol = es.symbol
          AND cr.source_repo = es.repo
          AND cr.confidence IN (${confPlaceholders})
      )
      AND es.symbol NOT LIKE '%#%'
      AND es.symbol NOT LIKE '%:%'
      ${extraConditions.join(" ")}
      ORDER BY es.repo, es.file, es.symbol
    `;
    const params = [...confidences, ...extraParams];
    const rows = db.prepare(sql).all(...params);
    return {
      dead_symbols: rows,
      total: rows.length,
      db: opts.dbPath,
      min_confidence: minConfidence
    };
  } finally {
    db.close();
  }
}
async function fetchOrgRepos(giteaBaseUrl, token, org) {
  const PAGE_LIMIT = 50;
  const MAX_PAGES = 200;
  const allRepos = [];
  let page = 1;
  let hasMore = true;
  while (hasMore) {
    if (page > MAX_PAGES) {
      process.stderr.write(`[eye-registry] WARNING: fetchOrgRepos exceeded MAX_PAGES=${MAX_PAGES}, stopping
`);
      break;
    }
    const url = `${giteaBaseUrl}/api/v1/orgs/${org}/repos?limit=${PAGE_LIMIT}&page=${page}`;
    const resp = await fetch(url, {
      headers: { Authorization: `token ${token}`, "Content-Type": "application/json" }
    });
    if (!resp.ok)
      throw new Error(`Gitea API error: ${resp.status} ${await resp.text()}`);
    const data = await resp.json();
    if (!Array.isArray(data) || data.length === 0) {
      hasMore = false;
      break;
    }
    allRepos.push(...data);
    if (data.length < PAGE_LIMIT) {
      hasMore = false;
    } else {
      page++;
    }
  }
  return allRepos;
}
function parseRegistryBuildArgs(args, defaults = {}) {
  const opts = {
    org: defaults.org ?? process.env.GITEA_ORG ?? "hurricanetech",
    output: defaults.output ?? process.env.EYE_BOT_DB_PATH ?? getDefaultDbPath(),
    giteaBaseUrl: defaults.giteaBaseUrl ?? process.env.GITEA_BASE_URL ?? "http://host.docker.internal:3033",
    giteaToken: defaults.giteaToken ?? process.env.GITEA_TOKEN ?? "",
    cloneBase: defaults.cloneBase ?? process.env.EYE_BOT_CLONE_DIR ?? join9(tmpdir2(), "eye-bot-repos"),
    repoOverrides: defaults.repoOverrides,
    incremental: defaults.incremental ?? false,
    loaderPatternStrings: defaults.loaderPatternStrings ?? [],
    viewsDir: defaults.viewsDir
  };
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    if ((a === "--org" || a === "-o") && args[i + 1])
      opts.org = args[++i];
    else if (a.startsWith("--org="))
      opts.org = a.slice("--org=".length);
    else if ((a === "--output" || a === "-d") && args[i + 1])
      opts.output = args[++i];
    else if (a.startsWith("--output="))
      opts.output = a.slice("--output=".length);
    else if (a === "--repos" && args[i + 1])
      opts.repoOverrides = args[++i];
    else if (a.startsWith("--repos="))
      opts.repoOverrides = a.slice("--repos=".length);
    else if (a === "--incremental")
      opts.incremental = true;
    else if (a === "--loader-pattern" && args[i + 1]) {
      if (!opts.loaderPatternStrings)
        opts.loaderPatternStrings = [];
      opts.loaderPatternStrings.push(args[++i]);
    } else if (a.startsWith("--loader-pattern=")) {
      if (!opts.loaderPatternStrings)
        opts.loaderPatternStrings = [];
      opts.loaderPatternStrings.push(a.slice("--loader-pattern=".length));
    } else if (a === "--views-dir" && args[i + 1])
      opts.viewsDir = args[++i];
    else if (a.startsWith("--views-dir="))
      opts.viewsDir = a.slice("--views-dir=".length);
  }
  return opts;
}
function validateAndCompileLoaderPatterns(loaderPatternStrings, viewsDir) {
  const patterns = [];
  const multiGroupWarnings = [];
  for (const patStr of loaderPatternStrings ?? []) {
    if (patStr.length > 1e3) {
      return {
        ok: false,
        error: `--loader-pattern is too long (${patStr.length} chars, max 1000). Refusing to compile.`
      };
    }
    let compiled;
    try {
      compiled = new RegExp(patStr, "g");
    } catch (e) {
      return {
        ok: false,
        error: `invalid --loader-pattern "${patStr}": ${e instanceof Error ? e.message : String(e)}`
      };
    }
    const groupCount = new RegExp(patStr + "|").exec("").length - 1;
    if (groupCount > 1) {
      multiGroupWarnings.push({ patStr, groupCount });
    }
    patterns.push(compiled);
  }
  if (patterns.length > 0 && !viewsDir) {
    return {
      ok: false,
      error: "--loader-pattern requires --views-dir. Please specify the absolute path to the views directory with --views-dir <path>."
    };
  }
  return { ok: true, patterns, multiGroupWarnings };
}
async function runRegistryBuild(args) {
  const opts = parseRegistryBuildArgs(args);
  const lpValidation = validateAndCompileLoaderPatterns(opts.loaderPatternStrings, opts.viewsDir);
  if (!lpValidation.ok) {
    process.stderr.write(`[registry] error: ${lpValidation.error}
`);
    process.exit(2);
  }
  for (const { patStr, groupCount } of lpValidation.multiGroupWarnings) {
    process.stderr.write(`[registry] warn: --loader-pattern "${patStr}" has ${groupCount} capture groups; only capture group 1 is used as the view path. Extra groups are ignored.
`);
  }
  const loaderPatterns = lpValidation.patterns;
  process.stderr.write(`[registry] build started: org=${opts.org} db=${opts.output}
`);
  let repoMetas;
  if (process.env.GITEA_TOKEN !== void 0 && process.env.GITEA_TOKEN.trim() === "") {
    process.stderr.write("[registry] warn: GITEA_TOKEN is set but empty\n");
  }
  const { giteaToken } = opts;
  if (giteaToken) {
    let giteaRepos;
    try {
      giteaRepos = await fetchOrgRepos(opts.giteaBaseUrl, opts.giteaToken, opts.org);
    } catch (err) {
      process.stderr.write(`[registry] failed to fetch repos from Gitea: ${err}
`);
      process.exit(4);
    }
    const scannable = giteaRepos.filter((r) => !r.archived && !r.empty);
    repoMetas = [];
    const cloneTimeoutMs = parseInt(process.env.REGISTRY_CLONE_TIMEOUT_MS ?? "240000", 10);
    const _registryConcurrency = parseInt(process.env.EYE_REGISTRY_CONCURRENCY ?? "3", 10);
    const semaphore = new Semaphore(isNaN(_registryConcurrency) || _registryConcurrency <= 0 ? 3 : _registryConcurrency);
    const cloneTasks = scannable.map(async (gr) => {
      await semaphore.acquire();
      const destPath = join9(opts.cloneBase, gr.name);
      try {
        await Promise.race([
          cloneOrPull(gr.clone_url, destPath, opts.giteaToken),
          new Promise((_, reject) => setTimeout(() => reject(new Error(`clone timeout after ${cloneTimeoutMs}ms: ${gr.clone_url}`)), cloneTimeoutMs))
        ]);
        return extractRepoMeta(destPath, gr.name);
      } catch (err) {
        process.stderr.write(`[registry] clone/pull failed for ${gr.name}: ${err}
`);
        return null;
      } finally {
        semaphore.release();
      }
    });
    const cloneResults = await Promise.all(cloneTasks);
    for (const meta of cloneResults) {
      if (meta)
        repoMetas.push(meta);
    }
  } else {
    process.stderr.write(`[registry] no GITEA_TOKEN; scanning cloned repos in ${opts.cloneBase}
`);
    repoMetas = [];
    let rawEntries = [];
    try {
      rawEntries = readdirSync4(opts.cloneBase, { withFileTypes: true });
    } catch {
    }
    for (const e of rawEntries) {
      const name = e.name;
      if (e.isDirectory() && existsSync10(join9(opts.cloneBase, name, ".git"))) {
        const p = join9(opts.cloneBase, name);
        repoMetas.push(extractRepoMeta(p, name));
      }
    }
  }
  if (repoMetas.length === 0) {
    process.stderr.write("[registry] no repos to scan \u2014 nothing written\n");
    process.exit(0);
  }
  const result = await buildRegistry({
    dbPath: opts.output,
    repoPaths: repoMetas,
    incremental: opts.incremental,
    ...loaderPatterns.length > 0 ? { loaderPatterns, viewsDir: opts.viewsDir } : {}
  });
  process.stdout.write(JSON.stringify({
    repos_scanned: result.reposScanned,
    cross_refs_found: result.crossRefsFound,
    duration_ms: result.durationMs,
    db: opts.output
  }, null, 2) + "\n");
  process.stderr.write(`[registry] build complete: repos=${result.reposScanned} cross_refs=${result.crossRefsFound} elapsed=${(result.durationMs / 1e3).toFixed(1)}s
`);
}
function parseRegistryQueryArgs(args, defaults = {}) {
  const opts = {
    symbol: defaults.symbol ?? "",
    sourceRepo: defaults.sourceRepo ?? "",
    dbPath: defaults.dbPath ?? process.env.EYE_BOT_DB_PATH ?? getDefaultDbPath(),
    human: defaults.human ?? false
  };
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    if ((a === "--symbol" || a === "-s") && args[i + 1])
      opts.symbol = args[++i];
    else if (a.startsWith("--symbol="))
      opts.symbol = a.slice("--symbol=".length);
    else if ((a === "--source-repo" || a === "-r") && args[i + 1])
      opts.sourceRepo = args[++i];
    else if (a.startsWith("--source-repo="))
      opts.sourceRepo = a.slice("--source-repo=".length);
    else if ((a === "--db" || a === "-d") && args[i + 1])
      opts.dbPath = args[++i];
    else if (a.startsWith("--db="))
      opts.dbPath = a.slice("--db=".length);
    else if (a === "--human")
      opts.human = true;
  }
  return opts;
}
var COL_REPO = 40;
var COL_FILE = 60;
var COL_LINE = 6;
var COL_CONF = 10;
function padEnd(s, n) {
  return s.length >= n ? s.slice(0, n) : s + " ".repeat(n - s.length);
}
function formatRegistryQueryHuman(result) {
  const lines = [];
  lines.push(`symbol: ${result.symbol}  source: ${result.source_repo}  file: ${result.source_file}`);
  lines.push(`consumers (${result.total_consumers}):`);
  if (result.total_consumers === 0) {
    lines.push("no consumers found");
    return lines.join("\n");
  }
  const sep2 = "\u2500".repeat(COL_REPO + 2 + COL_FILE + 2 + COL_LINE + 2 + COL_CONF);
  const header = padEnd("repo", COL_REPO) + "  " + padEnd("file", COL_FILE) + "  " + padEnd("line", COL_LINE) + "  " + padEnd("confidence", COL_CONF);
  lines.push(sep2);
  lines.push(header);
  lines.push(sep2);
  for (const c of result.consumers) {
    const row = padEnd(c.repo, COL_REPO) + "  " + padEnd(c.file, COL_FILE) + "  " + padEnd(String(c.line), COL_LINE) + "  " + padEnd(c.confidence, COL_CONF);
    lines.push(row);
  }
  lines.push(sep2);
  return lines.join("\n");
}
function runRegistryQuery(args) {
  const opts = parseRegistryQueryArgs(args);
  if (!opts.symbol || !opts.sourceRepo) {
    process.stderr.write("usage: eye registry query --symbol <name> --source-repo <name> [--db <path>] [--human]\n");
    process.exit(2);
  }
  if (!existsSync10(opts.dbPath)) {
    process.stderr.write(`warn: no registry DB found at ${opts.dbPath}; run 'eye registry build' first \u2014 results may be empty
`);
    const empty = {
      symbol: opts.symbol,
      source_repo: opts.sourceRepo,
      source_file: "",
      consumers: [],
      total_consumers: 0
    };
    if (opts.human) {
      process.stdout.write(formatRegistryQueryHuman(empty) + "\n");
    } else {
      process.stdout.write(JSON.stringify(empty, null, 2) + "\n");
    }
    process.exit(0);
  }
  const result = queryRegistry(opts);
  if (opts.human) {
    process.stdout.write(formatRegistryQueryHuman(result) + "\n");
  } else {
    process.stdout.write(JSON.stringify(result, null, 2) + "\n");
  }
  process.exit(0);
}

// dist/dead-code.js
import Database2 from "better-sqlite3";
import { existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
import { dirname as dirname8, resolve as resolve7 } from "path";
function computeSeverityResults(opts) {
  const baseResult = queryDeadSymbols(opts);
  const dir = dirname8(opts.dbPath);
  if (!existsSync11(dir))
    mkdirSync5(dir, { recursive: true });
  const db = new Database2(opts.dbPath, { readonly: true });
  let enrichedSymbols;
  try {
    const isTypeExportMap = /* @__PURE__ */ new Map();
    const allExported = db.prepare("SELECT repo, symbol, is_type_export FROM exported_symbols").all();
    for (const row of allExported) {
      isTypeExportMap.set(`${row.repo}::${row.symbol}`, row.is_type_export === 1);
    }
    enrichedSymbols = baseResult.dead_symbols.map((sym) => {
      const key = `${sym.repo}::${sym.symbol}`;
      const isTypeExport = isTypeExportMap.get(key) ?? false;
      const severity = isTypeExport ? "warning" : "critical";
      return { ...sym, severity };
    });
    const minConfidence = opts.minConfidence ?? "low";
    let confidences;
    if (minConfidence === "high") {
      confidences = ["high"];
    } else if (minConfidence === "medium") {
      confidences = ["medium", "high"];
    } else {
      confidences = ["low", "medium", "high"];
    }
    const confPlaceholders = confidences.map(() => "?").join(",");
    const extraConditions = [];
    const extraParams = [];
    if (opts.repo) {
      extraConditions.push("AND es.repo = ?");
      extraParams.push(opts.repo);
    }
    if (opts.lang) {
      extraConditions.push("AND es.language = ?");
      extraParams.push(opts.lang);
    }
    if (opts.skipTypeExports) {
      extraConditions.push("AND (es.is_type_export IS NULL OR es.is_type_export = 0)");
    }
    const infoSql = `
      SELECT es.repo, es.file, es.symbol, es.language, es.last_seen,
             COUNT(cr.id) as caller_count
      FROM exported_symbols es
      LEFT JOIN cross_repo_refs cr
        ON cr.symbol = es.symbol
        AND cr.source_repo = es.repo
        AND cr.confidence IN (${confPlaceholders})
      WHERE (es.is_type_export IS NULL OR es.is_type_export = 0)
        AND es.symbol NOT LIKE '%#%'
        AND es.symbol NOT LIKE '%:%'
        ${extraConditions.join(" ")}
      GROUP BY es.repo, es.file, es.symbol, es.language, es.last_seen
      HAVING caller_count >= 1 AND caller_count <= 2
      ORDER BY es.repo, es.file, es.symbol
    `;
    const infoParams = [...confidences, ...extraParams];
    const infoRows = db.prepare(infoSql).all(...infoParams);
    const infoSymbols = infoRows.map((row) => ({
      repo: row.repo,
      file: row.file,
      symbol: row.symbol,
      language: row.language,
      last_seen: row.last_seen,
      severity: "info"
    }));
    enrichedSymbols = [...enrichedSymbols, ...infoSymbols];
  } finally {
    db.close();
  }
  const severityFilter = opts.severity ?? "all";
  const filteredSymbols = severityFilter === "all" ? enrichedSymbols : enrichedSymbols.filter((s) => s.severity === severityFilter);
  return {
    dead_symbols: filteredSymbols,
    total: filteredSymbols.length,
    db: opts.dbPath,
    min_confidence: opts.minConfidence ?? "low",
    severity_filter: severityFilter
  };
}
function parseDeadCodeArgs(args) {
  const opts = {
    dbPath: process.env.EYE_BOT_DB_PATH ?? getDefaultDbPath(),
    minConfidence: "low",
    severity: "all"
  };
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    if ((a === "--db" || a === "-d") && args[i + 1]) {
      opts.dbPath = args[++i];
    } else if (a.startsWith("--db=")) {
      opts.dbPath = a.slice("--db=".length);
    } else if (a === "--min-confidence" && args[i + 1]) {
      const val = args[++i];
      if (val === "low" || val === "medium" || val === "high") {
        opts.minConfidence = val;
      } else {
        process.stderr.write(`error: --min-confidence must be low, medium, or high
`);
        process.exit(2);
      }
    } else if (a.startsWith("--min-confidence=")) {
      const val = a.slice("--min-confidence=".length);
      if (val === "low" || val === "medium" || val === "high") {
        opts.minConfidence = val;
      } else {
        process.stderr.write(`error: --min-confidence must be low, medium, or high
`);
        process.exit(2);
      }
    } else if (a === "--repo" && args[i + 1]) {
      opts.repo = args[++i];
    } else if (a.startsWith("--repo=")) {
      opts.repo = a.slice("--repo=".length);
    } else if (a === "--lang" && args[i + 1]) {
      const val = args[++i];
      if (!["ts", "py", "go", "cs", "php", "swift", "java", "css", "scss"].includes(val)) {
        process.stderr.write(`error: --lang must be one of ts|py|go|cs|php|swift|java|css|scss
`);
        process.exit(2);
      }
      opts.lang = val;
    } else if (a.startsWith("--lang=")) {
      const val = a.slice("--lang=".length);
      if (!["ts", "py", "go", "cs", "php", "swift", "java", "css", "scss"].includes(val)) {
        process.stderr.write(`error: --lang must be one of ts|py|go|cs|php|swift|java|css|scss
`);
        process.exit(2);
      }
      opts.lang = val;
    } else if (a === "--dir" && args[i + 1]) {
      opts.dir = args[++i];
    } else if (a.startsWith("--dir=")) {
      opts.dir = a.slice("--dir=".length);
    } else if (a === "--skip-type-exports") {
      opts.skipTypeExports = true;
    } else if (a === "--severity" && args[i + 1]) {
      const val = args[++i];
      if (val === "critical" || val === "warning" || val === "info" || val === "all") {
        opts.severity = val;
      } else {
        process.stderr.write(`error: --severity must be critical, warning, info, or all
`);
        process.exit(2);
      }
    } else if (a.startsWith("--severity=")) {
      const val = a.slice("--severity=".length);
      if (val === "critical" || val === "warning" || val === "info" || val === "all") {
        opts.severity = val;
      } else {
        process.stderr.write(`error: --severity must be critical, warning, info, or all
`);
        process.exit(2);
      }
    }
  }
  return opts;
}
async function runDeadCode(args) {
  const opts = parseDeadCodeArgs(args);
  if (opts.lang === "css" || opts.lang === "scss") {
    const targetDir = resolve7(process.cwd(), opts.dir ?? ".");
    try {
      const result = await scanCssDeadCode(targetDir);
      process.stdout.write(JSON.stringify(result, null, 2) + "\n");
    } catch (err) {
      process.stderr.write("error: CSS dead-code scan failed: " + String(err) + "\n");
      process.exit(1);
    }
    return;
  }
  try {
    const result = computeSeverityResults(opts);
    process.stdout.write(JSON.stringify(result, null, 2) + "\n");
  } catch (err) {
    if (err instanceof EyeError) {
      process.stderr.write("error: " + err.message + "\n");
      process.exit(err.code ?? 1);
    }
    process.stderr.write("error: internal error\n");
    process.exit(1);
  }
}

// dist/scan.js
import { readFileSync as readFileSync8, statSync as statSync5 } from "fs";
import { resolve as resolve8, relative as relative6, dirname as dirname9 } from "path";
import { fileURLToPath as fileURLToPath3 } from "url";

// dist/format-sarif.js
var RISK_TO_LEVEL = {
  high: "error",
  medium: "warning",
  low: "note"
};
function riskToLevel(risk) {
  return RISK_TO_LEVEL[risk] ?? "note";
}
var SARIF_RULES = [
  {
    id: "eye-risk-high",
    name: "HighRiskSymbol",
    shortDescription: { text: "Symbol with high caller count \u2014 high blast radius change risk" }
  },
  {
    id: "eye-risk-medium",
    name: "MediumRiskSymbol",
    shortDescription: { text: "Symbol with medium caller count \u2014 medium blast radius change risk" }
  },
  {
    id: "eye-risk-low",
    name: "LowRiskSymbol",
    shortDescription: { text: "Symbol with low caller count \u2014 low blast radius change risk" }
  }
];
function formatSarif(entries, eyeVersion) {
  const results = entries.map((entry) => ({
    ruleId: `eye-risk-${entry.risk}`,
    level: riskToLevel(entry.risk),
    message: {
      text: `${entry.symbol} \u2014 ${entry.callers} caller(s)`
    },
    locations: [
      {
        physicalLocation: {
          artifactLocation: {
            uri: entry.file,
            uriBaseId: "%SRCROOT%"
          }
        }
      }
    ]
  }));
  return {
    $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
    version: "2.1.0",
    runs: [
      {
        tool: {
          driver: {
            name: "eye",
            version: eyeVersion,
            informationUri: "https://github.com/hurricanetech/eye",
            rules: SARIF_RULES
          }
        },
        results
      }
    ]
  };
}
function formatSarifJson(entries, eyeVersion) {
  return JSON.stringify(formatSarif(entries, eyeVersion), null, 2);
}

// dist/scan.js
var __dirname = dirname9(fileURLToPath3(import.meta.url));
function getScanVersion() {
  try {
    const pkgPath = resolve8(__dirname, "..", "package.json");
    const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
    return pkg.version;
  } catch {
    return "unknown";
  }
}
var SCAN_EXTENSIONS = /\.(ts|tsx|js|jsx|py|go|cs|php|swift|java|rs|kt|kts|css|scss)$/;
var LANG_EXTENSIONS = {
  ts: /\.(ts|tsx)$/,
  py: /\.py$/,
  go: /\.go$/,
  cs: /\.cs$/,
  php: /\.php$/,
  swift: /\.swift$/,
  java: /\.java$/,
  rs: /\.rs$/,
  kt: /\.(kt|kts)$/,
  css: /\.(css|scss)$/
};
var VALID_LANGS = Object.keys(LANG_EXTENSIONS);
function walkScanFiles(dir, maxDepth, lang, eyeIgnore) {
  const extensions = lang ? LANG_EXTENSIONS[lang] : SCAN_EXTENSIONS;
  return walkFiles(dir, {
    extensions,
    ...maxDepth !== void 0 ? { maxDepth } : {},
    ...eyeIgnore ? { eyeIgnore } : {}
  });
}
function extractFileSymbols(absFile) {
  let content;
  try {
    content = readFileSync8(absFile, "utf8");
  } catch {
    return [];
  }
  const symbols = [];
  if (absFile.endsWith(".ts") || absFile.endsWith(".tsx") || absFile.endsWith(".js") || absFile.endsWith(".jsx")) {
    const namedRe = /^export\s+(?:async\s+)?(?:function|class|const|let|var|type|interface|enum)\s+([A-Za-z_$][A-Za-z0-9_$]*)/mg;
    let m;
    while ((m = namedRe.exec(content)) !== null)
      symbols.push(m[1]);
    const blockRe = /^export\s*\{([^}]+)\}(?!\s*from\s*['"])/mg;
    while ((m = blockRe.exec(content)) !== null) {
      for (const part of m[1].split(",")) {
        const alias = part.match(/\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*$/);
        const direct = part.match(/^\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*$/);
        const name = alias ? alias[1] : direct ? direct[1] : null;
        if (name && name !== "default")
          symbols.push(name);
      }
    }
  } else if (absFile.endsWith(".py")) {
    const re = /^(?:def|class|async def)\s+([A-Za-z_][A-Za-z0-9_]*)/mg;
    let m;
    while ((m = re.exec(content)) !== null) {
      if (!m[1].startsWith("_"))
        symbols.push(m[1]);
    }
  } else if (absFile.endsWith(".go")) {
    const re = /^(?:func|type|var|const)\s+([A-Z][A-Za-z0-9_]*)/mg;
    let m;
    while ((m = re.exec(content)) !== null)
      symbols.push(m[1]);
  } else if (absFile.endsWith(".cs")) {
    const typeRe = /\bpublic\s+(?:(?:abstract|sealed|partial|static|readonly)\s+)*(?:class|interface|enum|record)\s+([A-Z][A-Za-z0-9_]*)/g;
    let m;
    while ((m = typeRe.exec(content)) !== null)
      symbols.push(m[1]);
    const staticMemberRe = /\bpublic\s+static\s+(?:(?:async|readonly|new|override|virtual|abstract)\s+)*\S+\s+([A-Z][A-Za-z0-9_]*)\s*[(<]/g;
    while ((m = staticMemberRe.exec(content)) !== null)
      symbols.push(m[1]);
    const efSymbols = augmentCSharpSymbolsFromSource(content, []);
    for (const s of efSymbols)
      symbols.push(s.name);
  } else if (absFile.endsWith(".php")) {
    const topFuncRe = /^function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/mg;
    let m;
    while ((m = topFuncRe.exec(content)) !== null)
      symbols.push(m[1]);
    const classRe = /^(?:(?:abstract|final)\s+)?class\s+([A-Za-z_][A-Za-z0-9_]*)/mg;
    while ((m = classRe.exec(content)) !== null)
      symbols.push(m[1]);
    const publicMethodRe = /^\s+public\s+(?:static\s+)?function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/mg;
    while ((m = publicMethodRe.exec(content)) !== null) {
      if (!m[1].startsWith("__"))
        symbols.push(m[1]);
    }
  } else if (absFile.endsWith(".swift")) {
    let m;
    const typeRe = /\bpublic\s+(?:(?:final|class|private\(set\))\s+)*(?:class|struct|enum|actor)\s+([A-Z][A-Za-z0-9_]*)/mg;
    while ((m = typeRe.exec(content)) !== null)
      symbols.push(m[1]);
    const openTypeRe = /\bopen\s+(?:(?:final|class)\s+)*(?:class|struct|enum|actor)\s+([A-Z][A-Za-z0-9_]*)/mg;
    while ((m = openTypeRe.exec(content)) !== null)
      symbols.push(m[1]);
    const funcRe = /\b(?:public|open)\s+(?:(?:static|class|mutating|override|final)\s+)*func\s+([A-Za-z_][A-Za-z0-9_]*)/mg;
    while ((m = funcRe.exec(content)) !== null)
      symbols.push(m[1]);
    const protoRe = /\bprotocol\s+([A-Z][A-Za-z0-9_]*)/mg;
    while ((m = protoRe.exec(content)) !== null)
      symbols.push(m[1]);
  } else if (absFile.endsWith(".java")) {
    let m;
    const typeRe = /\bpublic\s+(?:(?:abstract|final|static|strictfp)\s+)*(?:class|interface|enum|record)\s+([A-Za-z_$][A-Za-z0-9_$]*)/mg;
    while ((m = typeRe.exec(content)) !== null)
      symbols.push(m[1]);
    const methodRe = /^\s*public\s+(?:(?:static|final|abstract|synchronized|native|default|strictfp)\s+)*(?:<[^>]+>\s+)?(?:[A-Za-z_$][A-Za-z0-9_$<>[\],\s]*?)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/mg;
    while ((m = methodRe.exec(content)) !== null)
      symbols.push(m[1]);
  } else if (absFile.endsWith(".rs")) {
    let m;
    const pubFnRe = /\bpub\s+(?:async\s+)?fn\s+([A-Za-z_][A-Za-z0-9_]*)/mg;
    while ((m = pubFnRe.exec(content)) !== null)
      symbols.push(m[1]);
    const pubTypeRe = /\bpub\s+(?:(?:unsafe|auto)\s+)?(?:struct|enum|trait)\s+([A-Za-z_][A-Za-z0-9_]*)/mg;
    while ((m = pubTypeRe.exec(content)) !== null)
      symbols.push(m[1]);
  } else if (absFile.endsWith(".css") || absFile.endsWith(".scss")) {
    return extractCssFileSymbols(absFile);
  } else if (absFile.endsWith(".kt") || absFile.endsWith(".kts")) {
    let m;
    const funRe = /^(?!.*\b(?:private|protected|internal)\b)(?:(?:suspend|inline|operator|infix|tailrec|external)\s+)*fun\s+(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*[(<]/mg;
    while ((m = funRe.exec(content)) !== null)
      symbols.push(m[1]);
    const typeRe = /^(?!.*\b(?:private|protected|internal)\b)(?:(?:abstract|sealed|open|data|value|inner|companion)\s+)*(?:class|object|interface)\s+([A-Za-z_][A-Za-z0-9_]*)/mg;
    while ((m = typeRe.exec(content)) !== null)
      symbols.push(m[1]);
  }
  return [...new Set(symbols)];
}
var COMMON_NAMES = /* @__PURE__ */ new Set([
  "session",
  "db",
  "ctx",
  "req",
  "res",
  "result",
  "data",
  "item",
  "value",
  "self",
  "cls",
  "conn",
  "tx",
  "logger",
  "config",
  "cache"
]);
var COMMON_NAME_THRESHOLD_DEFAULT = 500;
async function runScan(args) {
  let targetDir = process.cwd();
  let humanMode = false;
  let sarifMode = false;
  let quiet = false;
  let minCallers = 0;
  let maxFiles;
  let lang;
  let incrementalMode = false;
  const envThreshold = parseInt(process.env.EYE_COMMON_NAME_THRESHOLD ?? "", 10);
  let commonNameThreshold = isNaN(envThreshold) ? COMMON_NAME_THRESHOLD_DEFAULT : envThreshold;
  const unknownFlags = [];
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === "--human") {
      humanMode = true;
    } else if (arg === "--format") {
      const val = args[i + 1];
      if (!val || val.startsWith("--")) {
        process.stderr.write("error: --format requires a value: json|sarif|human\n");
        process.exit(2);
        return;
      }
      if (val === "sarif") {
        sarifMode = true;
      } else if (val === "human") {
        humanMode = true;
      } else if (val !== "json") {
        process.stderr.write(`error: --format must be json, sarif, or human
`);
        process.exit(2);
        return;
      }
      i++;
    } else if (arg.startsWith("--format=")) {
      const val = arg.slice("--format=".length);
      if (val === "sarif") {
        sarifMode = true;
      } else if (val === "human") {
        humanMode = true;
      } else if (val !== "json") {
        process.stderr.write(`error: --format must be json, sarif, or human
`);
        process.exit(2);
        return;
      }
    } else if (arg === "--quiet") {
      quiet = true;
    } else if (arg === "--min-callers") {
      const val = args[i + 1];
      if (!val || val.startsWith("--")) {
        process.stderr.write("error: --min-callers requires a non-negative integer\n");
        process.exit(2);
        return;
      }
      const n = parseInt(val, 10);
      if (isNaN(n) || n < 0) {
        process.stderr.write("error: --min-callers must be a non-negative integer\n");
        process.exit(2);
        return;
      }
      minCallers = n;
      i++;
    } else if (arg.startsWith("--min-callers=")) {
      const val = arg.slice("--min-callers=".length);
      const n = parseInt(val, 10);
      if (isNaN(n) || n < 0) {
        process.stderr.write("error: --min-callers must be a non-negative integer\n");
        process.exit(2);
        return;
      }
      minCallers = n;
    } else if (arg === "--lang") {
      const val = args[i + 1];
      if (!val || val.startsWith("--")) {
        process.stderr.write(`error: --lang must be one of ${VALID_LANGS.join("|")}
`);
        process.exit(2);
        return;
      }
      if (!VALID_LANGS.includes(val)) {
        process.stderr.write(`error: --lang must be one of ${VALID_LANGS.join("|")}
`);
        process.exit(2);
        return;
      }
      lang = val;
      i++;
    } else if (arg.startsWith("--lang=")) {
      const val = arg.slice("--lang=".length);
      if (!VALID_LANGS.includes(val)) {
        process.stderr.write(`error: --lang must be one of ${VALID_LANGS.join("|")}
`);
        process.exit(2);
        return;
      }
      lang = val;
    } else if (arg === "--common-name-threshold") {
      const val = args[i + 1];
      if (!val || val.startsWith("--")) {
        process.stderr.write("error: --common-name-threshold requires a non-negative integer\n");
        process.exit(2);
        return;
      }
      const n = parseInt(val, 10);
      if (isNaN(n) || n < 0) {
        process.stderr.write("error: --common-name-threshold must be a non-negative integer\n");
        process.exit(2);
        return;
      }
      commonNameThreshold = n;
      i++;
    } else if (arg.startsWith("--common-name-threshold=")) {
      const val = arg.slice("--common-name-threshold=".length);
      const n = parseInt(val, 10);
      if (isNaN(n) || n < 0) {
        process.stderr.write("error: --common-name-threshold must be a non-negative integer\n");
        process.exit(2);
        return;
      }
      commonNameThreshold = n;
    } else if (arg === "--max-files") {
      const val = args[i + 1];
      if (!val || val.startsWith("--")) {
        process.stderr.write("error: --max-files requires a positive integer\n");
        process.exit(2);
        return;
      }
      const n = parseInt(val, 10);
      if (isNaN(n) || n <= 0) {
        process.stderr.write("error: --max-files requires a positive integer\n");
        process.exit(2);
        return;
      }
      maxFiles = n;
      i++;
    } else if (arg.startsWith("--max-files=")) {
      const val = arg.slice("--max-files=".length);
      const n = parseInt(val, 10);
      if (isNaN(n) || n <= 0) {
        process.stderr.write("error: --max-files requires a positive integer\n");
        process.exit(2);
        return;
      }
      maxFiles = n;
    } else if (arg === "--incremental") {
      incrementalMode = true;
    } else if (arg.startsWith("--")) {
      unknownFlags.push(arg);
    } else {
      targetDir = resolve8(process.cwd(), arg);
    }
  }
  if (unknownFlags.length > 0) {
    process.stderr.write(`[scan] warn: unknown flags ignored: ${unknownFlags.join(", ")}
`);
  }
  let stat;
  try {
    stat = statSync5(targetDir);
  } catch {
    process.stderr.write(`error: directory not found: ${targetDir}
`);
    process.exit(2);
    return;
  }
  if (!stat.isDirectory()) {
    process.stderr.write(`error: not a directory: ${targetDir}
`);
    process.exit(2);
    return;
  }
  const _eyeIgnoreRoot = findRepoRoot(targetDir) ?? targetDir;
  const eyeIgnore = loadEyeIgnore(_eyeIgnoreRoot);
  let files = walkScanFiles(targetDir, void 0, lang, eyeIgnore);
  let currentHead = null;
  let lastHead = null;
  if (incrementalMode) {
    const repoRoot = findRepoRoot(targetDir);
    if (repoRoot) {
      currentHead = getRepoHead(repoRoot);
      lastHead = readScanHead(repoRoot);
      if (lastHead && currentHead) {
        const changedFiles = getChangedFilesSince(repoRoot, lastHead);
        if (changedFiles !== null) {
          const changedSet = new Set(changedFiles.map((f) => resolve8(repoRoot, f)));
          const filtered = files.filter((f) => changedSet.has(f));
          if (filtered.length === 0) {
            process.stderr.write(`eye scan --incremental: no changed files since ${lastHead.slice(0, 8)}
`);
            process.stdout.write(JSON.stringify({ results: [], errors: 0 }, null, 2) + "\n");
            process.exit(0);
            return;
          }
          files = filtered;
          process.stderr.write(`eye scan --incremental: scanning ${files.length} changed file(s)
`);
        } else {
          process.stderr.write(`eye scan --incremental: git diff failed, falling back to full scan
`);
        }
      } else {
        process.stderr.write(`eye scan --incremental: no prior HEAD recorded, running full scan
`);
      }
    } else {
      process.stderr.write(`eye scan --incremental: not a git repo, falling back to full scan
`);
    }
  }
  if (maxFiles !== void 0 && files.length > maxFiles) {
    process.stderr.write(`eye scan: --max-files ${maxFiles}: scanning ${maxFiles} of ${files.length} files
`);
    files = files.slice(0, maxFiles);
  }
  const workItems = [];
  for (const absFile of files) {
    const relFile = relative6(targetDir, absFile);
    const symbols = extractFileSymbols(absFile);
    for (const sym of symbols) {
      workItems.push({ absFile, relFile, sym });
    }
  }
  const INDEX_MAX_BYTES = 256 * 1024 * 1024;
  let invertedIndex;
  const repoRootForIndex = findRepoRoot(targetDir) ?? targetDir;
  const indexDisabled = process.env.EYE_SCAN_NO_INDEX === "1";
  if (workItems.length > 0 && !indexDisabled) {
    const excludeGlobs = buildExcludeGlobs(eyeIgnore);
    const langToExts = {
      ts: ["ts", "tsx", "js", "jsx"],
      py: ["py"],
      go: ["go"],
      cs: ["cs"],
      php: ["php"],
      swift: ["swift"],
      java: ["java"],
      rs: ["rs"],
      kt: ["kt", "kts"],
      css: ["css", "scss"],
      scss: ["css", "scss"]
    };
    const indexExts = lang ? langToExts[lang] ?? ["ts", "tsx", "js", "jsx", "py", "go", "cs", "php", "swift", "java", "rs"] : ["ts", "tsx", "js", "jsx", "py", "go", "cs", "php", "swift", "java", "rs"];
    const includeGlobs = indexExts.map((e) => `*.${e}`);
    try {
      const hits = rgLines(repoRootForIndex, "[A-Za-z_$][A-Za-z0-9_$]*", {
        includeGlobs,
        excludeGlobs,
        noIgnore: true,
        maxBuffer: INDEX_MAX_BYTES
      });
      if (hits.length > 0) {
        const idx = /* @__PURE__ */ new Map();
        for (const hit of hits) {
          const tokens = hit.preview.match(/[A-Za-z_$][A-Za-z0-9_$]*/g);
          if (!tokens)
            continue;
          const seenInLine = /* @__PURE__ */ new Set();
          for (const tok of tokens) {
            if (seenInLine.has(tok))
              continue;
            seenInLine.add(tok);
            let bucket = idx.get(tok);
            if (!bucket) {
              bucket = [];
              idx.set(tok, bucket);
            }
            bucket.push(hit);
          }
        }
        invertedIndex = idx;
        if (!quiet)
          process.stderr.write(`eye scan: index built (${idx.size} tokens)
`);
      } else {
        process.stderr.write(`eye scan: rg output empty or >256 MB, falling back to per-symbol grep
`);
      }
    } catch {
      process.stderr.write(`eye scan: index build failed, falling back to per-symbol grep
`);
    }
  }
  const parseCacheDisabled = process.env.EYE_SCAN_NO_PARSE_CACHE === "1";
  const parseCache = parseCacheDisabled ? void 0 : /* @__PURE__ */ new Map();
  const debugParseCache = process.env.EYE_DEBUG_PARSE_CACHE === "1";
  if (!quiet && parseCache !== void 0) {
    process.stderr.write(`eye scan: parse cache enabled (file-level AST reuse)
`);
  }
  let crossFileEventListeners;
  const crossFileEventsDisabled = process.env.EYE_SCAN_NO_CROSS_FILE_EVENTS === "1";
  const allPyFiles = files.filter((f) => f.endsWith(".py"));
  if (allPyFiles.length > 0 && !crossFileEventsDisabled) {
    const t0 = Date.now();
    let eventListenerFiles;
    try {
      const rgRelative = rgFiles(repoRootForIndex, {
        patterns: ["event.listen"],
        includeGlobs: ["*.py"],
        excludeGlobs: buildExcludeGlobs(eyeIgnore),
        noIgnore: true
      });
      const allPySet = new Set(allPyFiles);
      eventListenerFiles = rgRelative.map((rel) => resolve8(repoRootForIndex, rel)).filter((abs) => allPySet.has(abs));
      if (!quiet) {
        process.stderr.write(`eye scan: Phase 1c rg pre-filter: ${eventListenerFiles.length}/${allPyFiles.length} .py files contain event.listen
`);
      }
    } catch {
      eventListenerFiles = allPyFiles;
      if (!quiet) {
        process.stderr.write(`eye scan: Phase 1c rg pre-filter failed, falling back to full .py scan
`);
      }
    }
    crossFileEventListeners = await buildCrossFileEventListenerMap(eventListenerFiles);
    const dtMs = Date.now() - t0;
    if (!quiet) {
      process.stderr.write(`eye scan: cross-file event listener map built (${crossFileEventListeners.size} handlers, ${eventListenerFiles.length} py files parsed, ${dtMs}ms)
`);
    }
  }
  const validateIndex = process.env.EYE_SCAN_INDEX_VALIDATE === "1" && invertedIndex !== void 0;
  const _scanConcurrency = parseInt(process.env.EYE_SCAN_CONCURRENCY ?? "4", 10);
  const sem = new Semaphore(isNaN(_scanConcurrency) || _scanConcurrency <= 0 ? 4 : _scanConcurrency);
  const results = [];
  let errorCount = 0;
  let successCount = 0;
  const totalSymbols = workItems.length;
  let analyzedCount = 0;
  const progressInterval = Math.max(1, Math.min(200, Math.ceil(totalSymbols / 10)));
  const tasks = workItems.map(({ absFile, relFile, sym }) => (async () => {
    await sem.acquire();
    try {
      const result = await analyze({ file: absFile, symbol: sym, options: { noCache: validateIndex ? true : false }, invertedIndex, parseCache, eyeIgnore, crossFileEventListeners });
      if (result.skipped) {
        return { type: "ok", entry: { symbol: sym, file: relFile, callers: 0, risk: "low", confidence: "low" } };
      }
      if (validateIndex) {
        try {
          const grepResult = await analyze({ file: absFile, symbol: sym, options: { noCache: true } });
          if (!grepResult.skipped) {
            const idxKey = (r) => `${r.file}:${r.line}:${r.preview}`;
            const idxSet = new Set((result.refs ?? []).map(idxKey));
            const grepSet = new Set((grepResult.refs ?? []).map(idxKey));
            for (const k of idxSet) {
              if (!grepSet.has(k))
                process.stderr.write(`[validate] ONLY-IN-INDEX ${relFile}:${sym} \u2192 ${k}
`);
            }
            for (const k of grepSet) {
              if (!idxSet.has(k))
                process.stderr.write(`[validate] ONLY-IN-GREP  ${relFile}:${sym} \u2192 ${k}
`);
            }
          }
        } catch {
        }
      }
      const meaningfulKinds = /* @__PURE__ */ new Set(["caller", "ref", "type-ref", "reexport"]);
      const meaningfulCount = (result.refs ?? []).filter((r) => meaningfulKinds.has(r.kind)).length;
      const callers = meaningfulCount;
      const risk = riskLevel(callers);
      let confidence = "low";
      const cs = result.confidence_summary;
      if (cs) {
        if (cs.high > 0)
          confidence = "high";
        else if (cs.medium > 0)
          confidence = "medium";
      }
      if (callers === 0 && (confidence === "high" || confidence === "medium")) {
        confidence = "isolated";
      }
      const entry = { symbol: sym, file: relFile, callers, risk, confidence };
      if (COMMON_NAMES.has(sym) && callers > commonNameThreshold) {
        entry.common_name_warning = true;
      }
      return { type: "ok", entry };
    } catch (err) {
      if (!quiet)
        process.stderr.write(`eye scan: failed to analyze ${relFile}:${sym}: ${String(err)}
`);
      return { type: "err" };
    } finally {
      analyzedCount++;
      if (!quiet && totalSymbols > 0 && analyzedCount % progressInterval === 0 && analyzedCount < totalSymbols) {
        process.stderr.write(`eye scan: analyzed ${analyzedCount}/${totalSymbols} symbols...
`);
      }
      sem.release();
    }
  })());
  const settled = await Promise.allSettled(tasks);
  for (const outcome of settled) {
    if (outcome.status === "rejected") {
      errorCount++;
    } else if (outcome.value.type === "ok") {
      results.push(outcome.value.entry);
      successCount++;
    } else {
      errorCount++;
    }
  }
  const filteredResults = minCallers > 0 ? results.filter((r) => r.callers >= minCallers) : results;
  if (debugParseCache && parseCache !== void 0) {
    const { hits, misses } = getAndResetParseCacheStats();
    const total = hits + misses;
    process.stderr.write(`eye scan: parse-cache stats: ${misses} misses (actual parses) + ${hits} hits (reused) = ${total} total workItems | unique files cached: ${parseCache.size}
`);
  }
  if (successCount === 0 && errorCount > 0) {
    process.stderr.write(`eye scan: all symbols failed to analyze (${errorCount} errors)
`);
    process.stdout.write(JSON.stringify({ results: [], errors: errorCount }, null, 2) + "\n");
    process.exit(4);
    return;
  }
  if (sarifMode) {
    const version = getScanVersion();
    process.stdout.write(formatSarifJson(filteredResults, version) + "\n");
    process.exit(0);
    return;
  }
  if (humanMode) {
    let truncate2 = function(s, max) {
      return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
    };
    var truncate = truncate2;
    const riskOrder = { high: 3, medium: 2, low: 1 };
    const sorted = [...filteredResults].sort((a, b) => {
      const riskDiff = (riskOrder[b.risk] ?? 0) - (riskOrder[a.risk] ?? 0);
      if (riskDiff !== 0)
        return riskDiff;
      if (b.callers !== a.callers)
        return b.callers - a.callers;
      return a.symbol.localeCompare(b.symbol);
    });
    const MAX_SYM = 40;
    const MAX_FILE = 60;
    const symWidth = Math.min(MAX_SYM, Math.max(6, ...sorted.map((r) => r.symbol.length)));
    const fileWidth = Math.min(MAX_FILE, Math.max(4, ...sorted.map((r) => r.file.length)));
    const callersHeader = "callers";
    const riskHeader = "risk";
    const header = `${"symbol".padEnd(symWidth)}  ${"file".padEnd(fileWidth)}  ${callersHeader.padEnd(7)}  ${riskHeader}`;
    const separator = "-".repeat(header.length);
    const lines = [header, separator];
    for (const r of sorted) {
      const warn = r.common_name_warning ? " \u26A0 common name" : "";
      lines.push(`${truncate2(r.symbol, symWidth).padEnd(symWidth)}  ${truncate2(r.file, fileWidth).padEnd(fileWidth)}  ${String(r.callers).padEnd(7)}  ${r.risk}${warn}`);
    }
    if (errorCount > 0) {
      lines.push(`
(${errorCount} symbol(s) failed analysis)`);
    }
    process.stdout.write(lines.join("\n") + "\n");
  } else {
    process.stdout.write(JSON.stringify({ results: filteredResults, errors: errorCount }, null, 2) + "\n");
  }
  if (incrementalMode && currentHead) {
    const repoRoot = findRepoRoot(targetDir);
    if (repoRoot)
      writeScanHead(repoRoot, currentHead);
  }
  process.exit(0);
}

// dist/explain.js
import { readFileSync as readFileSync9, statSync as statSync6 } from "fs";
import { relative as relative7 } from "path";
var SCAN_EXTS = /\.(ts|tsx|js|jsx|py|go|cs|php|swift|java)$/;
function findCallers(symbol, rootDir) {
  let files;
  try {
    const stat = statSync6(rootDir);
    if (!stat.isDirectory())
      return [];
    files = walkFiles(rootDir, { extensions: SCAN_EXTS });
  } catch {
    return [];
  }
  const callers = [];
  const pattern = new RegExp(`(?<![A-Za-z0-9_$])${escapeRegex3(symbol)}(?![A-Za-z0-9_$])`);
  for (const absFile of files) {
    let content;
    try {
      content = readFileSync9(absFile, "utf8");
    } catch {
      continue;
    }
    const lines = content.split("\n");
    for (let i = 0; i < lines.length; i++) {
      if (pattern.test(lines[i])) {
        callers.push({
          file: relative7(rootDir, absFile),
          line: i + 1,
          hop: 1
        });
      }
    }
  }
  return callers;
}
function escapeRegex3(s) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function findCallees(symbol, rootDir) {
  let files;
  try {
    const stat = statSync6(rootDir);
    if (!stat.isDirectory())
      return [];
    files = walkFiles(rootDir, { extensions: SCAN_EXTS });
  } catch {
    return [];
  }
  const defPattern = new RegExp(`(?:function\\s+${escapeRegex3(symbol)}|(?:const|let|var|async)\\s+${escapeRegex3(symbol)}\\s*=\\s*(?:async\\s*)?(?:function|\\())`);
  const callPattern = /(?<![A-Za-z0-9_$])([A-Za-z_$][A-Za-z0-9_$]*)(?:\s*)\(/g;
  const callees = [];
  const seen = /* @__PURE__ */ new Set();
  for (const absFile of files) {
    let content;
    try {
      content = readFileSync9(absFile, "utf8");
    } catch {
      continue;
    }
    const lines = content.split("\n");
    let defLine = -1;
    for (let i = 0; i < lines.length; i++) {
      if (defPattern.test(lines[i])) {
        defLine = i;
        break;
      }
    }
    if (defLine === -1)
      continue;
    let braceDepth = 0;
    let inBody = false;
    for (let i = defLine; i < lines.length; i++) {
      const line = lines[i];
      for (const ch of line) {
        if (ch === "{") {
          braceDepth++;
          inBody = true;
        } else if (ch === "}") {
          braceDepth--;
        }
      }
      if (inBody && i > defLine) {
        let match;
        callPattern.lastIndex = 0;
        while ((match = callPattern.exec(line)) !== null) {
          const callee = match[1];
          if (callee === symbol)
            continue;
          if (/^(if|for|while|switch|catch|function|return|typeof|instanceof|new|void|delete|await|async)$/.test(callee))
            continue;
          const key = `${callee}:${relative7(rootDir, absFile)}:${i + 1}`;
          if (!seen.has(key)) {
            seen.add(key);
            callees.push({ name: callee, file: relative7(rootDir, absFile), line: i + 1 });
          }
        }
      }
      if (inBody && braceDepth === 0)
        break;
    }
  }
  return callees;
}
async function runEyeExplain(opts) {
  const { symbol, format = "json", jsonPretty = false, hops = 1, callees: includeCallees = false } = opts;
  const rootDir = opts.rootDir ?? process.cwd();
  if (!symbol || symbol.startsWith("--")) {
    process.stderr.write("error: explain requires a symbol name\n");
    process.stderr.write("usage: eye explain <symbol> [--format json|human] [--hops 1..5] [--callees] [--json-pretty]\n");
    process.exit(2);
    return;
  }
  const callers1 = findCallers(symbol, rootDir);
  let allCallers = callers1;
  if (hops >= 2 && callers1.length > 0) {
    const seenKeys = new Set(callers1.map((c) => `${c.file}:${c.line}`));
    let prevHopFiles = [...new Set(callers1.map((c) => c.file))];
    for (let h = 2; h <= hops; h++) {
      if (prevHopFiles.length === 0)
        break;
      const hopCallers = [];
      const hopFiles = /* @__PURE__ */ new Set();
      for (const relFile of prevHopFiles) {
        const baseName = relFile.replace(/\.[^.]+$/, "").replace(/.*[\\/]/, "");
        if (!baseName)
          continue;
        const found = findCallers(baseName, rootDir);
        for (const c of found) {
          const key = `${c.file}:${c.line}`;
          if (!seenKeys.has(key)) {
            seenKeys.add(key);
            hopCallers.push({ ...c, hop: h });
            hopFiles.add(c.file);
          }
        }
      }
      allCallers = [...allCallers, ...hopCallers];
      prevHopFiles = [...hopFiles];
    }
  }
  if (allCallers.length === 0) {
    if (symbol.includes(":") || symbol.includes("/") || symbol.includes("\\")) {
      process.stderr.write(`error: eye explain accepts a bare symbol name (e.g. 'eye explain foo'), not file:symbol
hint: use 'eye <file>:<symbol>' for blast-radius analysis
`);
    } else {
      process.stderr.write(`error: symbol "${symbol}" not found in ${rootDir}
`);
    }
    process.exit(2);
    return;
  }
  const result = {
    symbol,
    callers: allCallers,
    summary: {
      totalCallers: allCallers.length,
      uniqueFiles: new Set(allCallers.map((c) => c.file)).size
    }
  };
  if (includeCallees) {
    result.callees = findCallees(symbol, rootDir);
  }
  if (format === "human") {
    outputHuman(result);
  } else {
    const indent = jsonPretty ? 2 : 0;
    process.stdout.write(JSON.stringify(result, null, indent) + "\n");
  }
}
function outputHuman(result) {
  const lines = [];
  lines.push(`\u2500\u2500 eye explain: ${result.symbol} \u2500\u2500`);
  lines.push(`  callers: ${result.summary.totalCallers}  unique files: ${result.summary.uniqueFiles}`);
  lines.push("");
  const maxHop = result.callers.reduce((m, c) => Math.max(m, c.hop), 0);
  for (let h = 1; h <= maxHop; h++) {
    const hopCallers = result.callers.filter((c) => c.hop === h);
    if (hopCallers.length > 0) {
      if (h > 1)
        lines.push("");
      lines.push(`  ${h}-hop callers:`);
      for (const c of hopCallers) {
        lines.push(`    ${c.file}:${c.line}`);
      }
    }
  }
  if (result.callees !== void 0) {
    lines.push("");
    lines.push("  callees:");
    if (result.callees.length === 0) {
      lines.push("    (none found)");
    } else {
      for (const callee of result.callees) {
        lines.push(`    ${callee.name}  ${callee.file}:${callee.line}`);
      }
    }
  }
  process.stdout.write(lines.join("\n") + "\n");
}

// dist/doctor.js
import { execSync } from "child_process";
import { existsSync as existsSync12, accessSync, mkdirSync as mkdirSync6, constants, readFileSync as readFileSync10 } from "fs";
import { resolve as resolve9, dirname as dirname10 } from "path";
import { fileURLToPath as fileURLToPath4 } from "url";
import { createRequire as createRequire5 } from "module";
var __dirname2 = dirname10(fileURLToPath4(import.meta.url));
function parseSemver(v) {
  const m = v.match(/(\d+)\.(\d+)\.?(\d+)?/);
  if (!m)
    return [0, 0, 0];
  return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3] ?? "0", 10)];
}
function semverGte(v, min) {
  for (let i = 0; i < 3; i++) {
    if (v[i] > min[i])
      return true;
    if (v[i] < min[i])
      return false;
  }
  return true;
}
function checkNodeVersion() {
  const raw = process.version;
  const vStr = raw.replace(/^v/, "");
  const parsed = parseSemver(vStr);
  const min = [18, 0, 0];
  if (semverGte(parsed, min)) {
    return { name: "Node.js version", status: "ok", message: `Node ${raw} (>= 18.0.0 required)` };
  }
  return { name: "Node.js version", status: "fail", message: `Node ${raw} is below minimum 18.0.0 \u2014 upgrade Node.js` };
}
function checkGitBinary() {
  let raw;
  try {
    raw = execSync("git --version", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
  } catch {
    return { name: "git binary", status: "fail", message: "git not found \u2014 install git (https://git-scm.com)" };
  }
  const m = raw.match(/git version (\d+\.\d+[.\d]*)/);
  if (!m) {
    return { name: "git binary", status: "warn", message: `git found but version unparseable: ${raw}` };
  }
  const parsed = parseSemver(m[1]);
  const min = [2, 20, 0];
  if (semverGte(parsed, min)) {
    return { name: "git binary", status: "ok", message: `${raw} (>= 2.20 required)` };
  }
  return { name: "git binary", status: "fail", message: `${raw} \u2014 below minimum 2.20, please upgrade git` };
}
function checkEyeVersion() {
  try {
    const pkgPath = resolve9(__dirname2, "..", "package.json");
    const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
    const v = pkg.version ?? "";
    if (/^\d+\.\d+\.\d+/.test(v)) {
      return { name: "eye version", status: "ok", message: `eye v${v} (${pkgPath})` };
    }
    return { name: "eye version", status: "fail", message: `package.json version "${v}" is not valid semver` };
  } catch (err) {
    return { name: "eye version", status: "fail", message: `Cannot read package.json: ${err}` };
  }
}
function checkDbPath(_accessFn = accessSync, _mkdirFn = mkdirSync6) {
  const envVal = process.env.EYE_BOT_DB_PATH;
  if (!envVal) {
    const defaultPath = getDefaultDbPath();
    const dir2 = resolve9(defaultPath, "..");
    try {
      _mkdirFn(dir2, { recursive: true });
      _accessFn(dir2, constants.W_OK);
      return {
        name: "EYE_BOT_DB_PATH",
        status: "ok",
        message: `Not set \u2014 \u5C07\u4F7F\u7528\u9810\u8A2D\u8DEF\u5F91 "${defaultPath}"`
      };
    } catch {
      return {
        name: "EYE_BOT_DB_PATH",
        status: "fail",
        message: `Not set \u2014 \u9810\u8A2D\u8DEF\u5F91\u7236\u76EE\u9304\u4E0D\u53EF\u5BEB: "${dir2}" (\u8ACB\u8A2D\u5B9A EYE_BOT_DB_PATH \u6216\u78BA\u8A8D\u76EE\u9304\u6B0A\u9650)`
      };
    }
  }
  const dir = resolve9(envVal, "..");
  try {
    _accessFn(dir, constants.W_OK);
    return { name: "EYE_BOT_DB_PATH", status: "ok", message: `${envVal} (parent directory is writable)` };
  } catch {
    return { name: "EYE_BOT_DB_PATH", status: "fail", message: `${envVal} \u2014 parent directory not writable: ${dir}` };
  }
}
function checkTreeSitter() {
  try {
    const req = createRequire5(import.meta.url);
    req("tree-sitter-typescript");
    return { name: "tree-sitter WASM", status: "ok", message: "tree-sitter-typescript importable" };
  } catch (err) {
    return {
      name: "tree-sitter WASM",
      status: "fail",
      message: `tree-sitter-typescript not importable: ${err} \u2014 run npm install`
    };
  }
}
function checkEyeCache(cwd = process.cwd(), _mkdirFn = mkdirSync6, _existsFn = existsSync12) {
  const cacheDir = resolve9(cwd, ".eye-cache");
  if (_existsFn(cacheDir)) {
    try {
      accessSync(cacheDir, constants.W_OK);
      return { name: ".eye-cache/ directory", status: "ok", message: `${cacheDir} exists and is writable` };
    } catch {
      return {
        name: ".eye-cache/ directory",
        status: "fail",
        message: `${cacheDir} exists but is NOT writable \u2014 check permissions`
      };
    }
  }
  try {
    _mkdirFn(cacheDir, { recursive: true });
    return { name: ".eye-cache/ directory", status: "ok", message: `${cacheDir} created successfully` };
  } catch (err) {
    return { name: ".eye-cache/ directory", status: "fail", message: `Cannot create ${cacheDir}: ${err}` };
  }
}
function runEyeDoctor(opts = {}) {
  const format = opts.format ?? "human";
  const cwd = opts.cwd ?? process.cwd();
  const checks = [
    checkNodeVersion(),
    checkGitBinary(),
    checkEyeVersion(),
    checkDbPath(),
    checkTreeSitter(),
    checkEyeCache(cwd, opts._mkdirFn, opts._existsFn)
  ];
  const overall = checks.some((c) => c.status === "fail") ? "fail" : "ok";
  const result = { checks, overall };
  if (format === "json") {
    process.stdout.write(JSON.stringify(result, null, 2) + "\n");
  } else {
    renderHuman(result);
  }
  process.exit(overall === "ok" ? 0 : 1);
}
function renderHuman(result) {
  process.stdout.write("eye doctor \u2014 environment health check\n");
  process.stdout.write("\u2500".repeat(45) + "\n");
  for (const c of result.checks) {
    const icon = c.status === "ok" ? "[OK]  " : c.status === "warn" ? "[WARN]" : "[FAIL]";
    process.stdout.write(`${icon}  ${c.name}
`);
    process.stdout.write(`       ${c.message}
`);
  }
  process.stdout.write("\u2500".repeat(45) + "\n");
  if (result.overall === "ok") {
    process.stdout.write("Overall: OK \u2014 all checks passed\n");
  } else {
    process.stdout.write("Overall: FAIL \u2014 one or more checks failed\n");
  }
}

// dist/cycles.js
import { existsSync as existsSync13 } from "fs";
import { resolve as resolve10, relative as relative8, dirname as dirname11, join as join10, isAbsolute } from "path";
function resolveImportPath(fromFile, importPath, repoRoot) {
  if (!importPath.startsWith("./") && !importPath.startsWith("../")) {
    return null;
  }
  const fromDir = dirname11(fromFile);
  let base = importPath;
  if (base.endsWith(".js")) {
    base = base.slice(0, -3);
  } else if (base.endsWith(".jsx")) {
    base = base.slice(0, -4);
  }
  const candidates = [
    resolve10(fromDir, base),
    // exact (after strip)
    resolve10(fromDir, base + ".ts"),
    resolve10(fromDir, base + ".tsx"),
    resolve10(fromDir, join10(base, "index.ts")),
    resolve10(fromDir, join10(base, "index.tsx"))
  ];
  for (const candidate of candidates) {
    if (existsSync13(candidate)) {
      const rel = relative8(repoRoot, candidate);
      if (!rel.startsWith("..") && !isAbsolute(rel)) {
        return candidate;
      }
    }
  }
  return null;
}
async function buildFileImportGraphDetailed(repoRoot, files) {
  const allGraph = /* @__PURE__ */ new Map();
  const valueGraph = /* @__PURE__ */ new Map();
  const edgeTypeMap = /* @__PURE__ */ new Map();
  for (const f of files) {
    allGraph.set(f, /* @__PURE__ */ new Set());
    valueGraph.set(f, /* @__PURE__ */ new Set());
  }
  const fileSet = new Set(files);
  for (const absFile of files) {
    let rawImports;
    try {
      rawImports = await extractTsImportsAst(absFile, repoRoot);
    } catch {
      continue;
    }
    const seenPaths = /* @__PURE__ */ new Map();
    for (const raw of rawImports) {
      const resolved = resolveImportPath(absFile, raw.importPath, repoRoot);
      if (!resolved)
        continue;
      if (!fileSet.has(resolved))
        continue;
      const isTypeOnlyImport = raw.isTypeOnly === true;
      if (!seenPaths.has(resolved)) {
        seenPaths.set(resolved, !isTypeOnlyImport);
      } else {
        if (!isTypeOnlyImport) {
          seenPaths.set(resolved, true);
        }
      }
    }
    for (const [resolved, hasValueImport] of seenPaths) {
      allGraph.get(absFile).add(resolved);
      const edgeKey = `${absFile}\0${resolved}`;
      edgeTypeMap.set(edgeKey, { isTypeOnly: !hasValueImport });
      if (hasValueImport) {
        valueGraph.get(absFile).add(resolved);
      }
    }
  }
  return { allGraph, valueGraph, edgeTypeMap };
}
function tarjanScc(graph) {
  const nodes = Array.from(graph.keys());
  const state = /* @__PURE__ */ new Map();
  const stack = [];
  const onStack = /* @__PURE__ */ new Set();
  const sccs = [];
  let indexCounter = 0;
  for (const startNode of nodes) {
    if (state.has(startNode))
      continue;
    const callStack = [];
    callStack.push({
      node: startNode,
      neighbours: Array.from(graph.get(startNode) ?? []),
      neighbourIdx: 0
    });
    state.set(startNode, { index: indexCounter, lowlink: indexCounter, onStack: true });
    indexCounter++;
    stack.push(startNode);
    onStack.add(startNode);
    while (callStack.length > 0) {
      const frame = callStack[callStack.length - 1];
      const { node } = frame;
      if (frame.neighbourIdx < frame.neighbours.length) {
        const neighbour = frame.neighbours[frame.neighbourIdx];
        frame.neighbourIdx++;
        if (!state.has(neighbour)) {
          state.set(neighbour, { index: indexCounter, lowlink: indexCounter, onStack: true });
          indexCounter++;
          stack.push(neighbour);
          onStack.add(neighbour);
          callStack.push({
            node: neighbour,
            neighbours: Array.from(graph.get(neighbour) ?? []),
            neighbourIdx: 0
          });
        } else if (onStack.has(neighbour)) {
          const nodeState = state.get(node);
          const neighbourState = state.get(neighbour);
          if (neighbourState.index < nodeState.lowlink) {
            nodeState.lowlink = neighbourState.index;
          }
        }
      } else {
        callStack.pop();
        const nodeState = state.get(node);
        if (callStack.length > 0) {
          const parentFrame = callStack[callStack.length - 1];
          const parentState = state.get(parentFrame.node);
          if (nodeState.lowlink < parentState.lowlink) {
            parentState.lowlink = nodeState.lowlink;
          }
        }
        if (nodeState.lowlink === nodeState.index) {
          const scc = [];
          let w;
          do {
            w = stack.pop();
            onStack.delete(w);
            const wState = state.get(w);
            wState.onStack = false;
            scc.push(w);
          } while (w !== node);
          if (scc.length >= 2) {
            sccs.push(scc);
          }
        }
      }
    }
  }
  return sccs;
}
function parseCyclesArgs(rawArgs) {
  let repoPath = "";
  let json = false;
  let human = false;
  let lang = "ts";
  let skipTypeImports = false;
  const exclude = [];
  for (let i = 0; i < rawArgs.length; i++) {
    const a = rawArgs[i];
    if (a === "--json") {
      json = true;
    } else if (a === "--human") {
      human = true;
    } else if (a === "--skip-type-imports") {
      skipTypeImports = true;
    } else if (a === "--lang" && rawArgs[i + 1]) {
      lang = rawArgs[++i];
    } else if (a.startsWith("--lang=")) {
      lang = a.slice("--lang=".length);
    } else if (a === "--exclude" && rawArgs[i + 1]) {
      exclude.push(rawArgs[++i]);
    } else if (a.startsWith("--exclude=")) {
      exclude.push(a.slice("--exclude=".length));
    } else if (!a.startsWith("--")) {
      repoPath = a;
    }
  }
  if (!repoPath) {
    return null;
  }
  return { repoPath, json, human, lang, skipTypeImports, exclude };
}
async function runCycles(rawArgs) {
  const args = parseCyclesArgs(rawArgs);
  if (!args) {
    process.stderr.write("usage: eye cycles <repo-path> [--json] [--human] [--lang ts] [--skip-type-imports] [--exclude <dir>]\n");
    process.exit(2);
    return;
  }
  if (args.lang !== "ts") {
    process.stderr.write(`error: --lang ${args.lang} is not supported in v1; only 'ts' is available
`);
    process.exit(2);
    return;
  }
  const repoRoot = resolve10(args.repoPath);
  if (!existsSync13(repoRoot)) {
    process.stderr.write(`error: path not found: ${repoRoot}
`);
    process.exit(2);
    return;
  }
  const startMs = Date.now();
  const eyeIgnore = loadEyeIgnore(repoRoot);
  for (const dir of args.exclude) {
    eyeIgnore.exactDirs.add(dir);
  }
  const files = walkFiles(repoRoot, {
    extensions: /\.(ts|tsx)$/,
    eyeIgnore
  });
  const { allGraph, valueGraph, edgeTypeMap } = await buildFileImportGraphDetailed(repoRoot, files);
  const activeGraph = args.skipTypeImports ? valueGraph : allGraph;
  const rawCycles = tarjanScc(activeGraph);
  const cycles = rawCycles.map((scc) => {
    const relPaths = scc.map((f) => relative8(repoRoot, f)).sort();
    const absPaths = scc.slice();
    let allEdgesTypeOnly = true;
    for (const absFile of absPaths) {
      const neighbours = allGraph.get(absFile);
      if (!neighbours)
        continue;
      for (const neighbour of neighbours) {
        if (!absPaths.includes(neighbour))
          continue;
        const edgeKey = `${absFile}\0${neighbour}`;
        const edgeInfo = edgeTypeMap.get(edgeKey);
        if (!edgeInfo || !edgeInfo.isTypeOnly) {
          allEdgesTypeOnly = false;
          break;
        }
      }
      if (!allEdgesTypeOnly)
        break;
    }
    const result = { path: relPaths, length: relPaths.length };
    if (allEdgesTypeOnly) {
      result.isTypeOnly = true;
    }
    return result;
  });
  cycles.sort((a, b) => {
    if (b.length !== a.length)
      return b.length - a.length;
    return a.path[0].localeCompare(b.path[0]);
  });
  const durationMs = Date.now() - startMs;
  const output = {
    cycles,
    total: cycles.length,
    root: repoRoot,
    filesScanned: files.length,
    durationMs
  };
  if (args.human) {
    emitHuman(output);
  } else {
    process.stdout.write(JSON.stringify(output, null, 2) + "\n");
  }
  process.exit(0);
}
function emitHuman(output) {
  if (output.total === 0) {
    process.stdout.write(`No circular imports found in ${output.root} (${output.filesScanned} files scanned, ${output.durationMs}ms)
`);
    return;
  }
  process.stdout.write(`Found ${output.total} cycle(s) in ${output.root} (${output.filesScanned} files scanned, ${output.durationMs}ms)

`);
  for (let i = 0; i < output.cycles.length; i++) {
    const cycle = output.cycles[i];
    const typeLabel = cycle.isTypeOnly ? " [type-only]" : "";
    process.stdout.write(`  Cycle ${i + 1} (${cycle.length} files)${typeLabel}:
`);
    for (let j = 0; j < cycle.path.length; j++) {
      if (j === 0) {
        process.stdout.write(`    ${cycle.path[j]}
`);
      } else {
        process.stdout.write(`    -> ${cycle.path[j]}
`);
      }
    }
    process.stdout.write(`    -> ${cycle.path[0]}
`);
    process.stdout.write("\n");
  }
}

// dist/license.js
import * as crypto from "crypto";
import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7, renameSync, copyFileSync, unlinkSync as unlinkSync2, chmodSync } from "fs";
import { homedir as homedir2 } from "os";
import { join as join11 } from "path";
var FREE_DAILY_QUOTA = 10;
var PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEACHFNkcq/e9BjC9sKLXypD3C4JlYkXuVMH+6O45PwXmg=
-----END PUBLIC KEY-----`;
function getUserDataDir() {
  const dataHome = process.env.XDG_DATA_HOME ?? join11(homedir2(), ".local", "share");
  return join11(dataHome, "eye");
}
function getStateDir() {
  const stateHome = process.env.XDG_STATE_HOME ?? join11(homedir2(), ".local", "state");
  const dir = join11(stateHome, "eye");
  mkdirSync7(dir, { recursive: true, mode: 448 });
  try {
    chmodSync(dir, 448);
  } catch {
  }
  return dir;
}
function getTrialPath() {
  return join11(getStateDir(), ".trial");
}
function getUsagePath() {
  return join11(getStateDir(), ".usage");
}
function getLicensePath() {
  return join11(getStateDir(), "license.lic");
}
function maybeMigrateLicenseFile() {
  const newPath = getLicensePath();
  if (existsSync14(newPath))
    return;
  const oldPath = join11(getUserDataDir(), "license.lic");
  if (!existsSync14(oldPath))
    return;
  try {
    try {
      renameSync(oldPath, newPath);
    } catch {
      const tmpPath = newPath + ".tmp";
      copyFileSync(oldPath, tmpPath);
      try {
        chmodSync(tmpPath, 384);
      } catch {
      }
      renameSync(tmpPath, newPath);
      try {
        unlinkSync2(oldPath);
      } catch {
      }
    }
  } catch {
  }
}
function maybeMigrateTrialFile() {
  const newPath = getTrialPath();
  if (existsSync14(newPath))
    return;
  const oldPath = join11(getUserDataDir(), ".trial");
  if (!existsSync14(oldPath))
    return;
  try {
    try {
      renameSync(oldPath, newPath);
    } catch {
      const tmpPath = newPath + ".tmp";
      copyFileSync(oldPath, tmpPath);
      try {
        chmodSync(tmpPath, 384);
      } catch {
      }
      renameSync(tmpPath, newPath);
      try {
        unlinkSync2(oldPath);
      } catch {
      }
    }
  } catch {
  }
}
function verifyLicense(licText, publicKeyPem = PUBLIC_KEY_PEM) {
  const text = licText.trim();
  const dotIdx = text.indexOf(".");
  if (dotIdx === -1) {
    return { valid: false, reason: "invalid license format: missing separator" };
  }
  const payloadB64 = text.slice(0, dotIdx);
  const sigB64 = text.slice(dotIdx + 1);
  if (!payloadB64 || !sigB64) {
    return { valid: false, reason: "invalid license format: empty payload or signature" };
  }
  let sigOk;
  try {
    sigOk = crypto.verify(null, Buffer.from(payloadB64), { key: publicKeyPem, dsaEncoding: "ieee-p1363" }, Buffer.from(sigB64, "base64url"));
  } catch {
    return { valid: false, reason: "signature verification error" };
  }
  if (!sigOk) {
    return { valid: false, reason: "invalid signature" };
  }
  let payload;
  try {
    const jsonStr = Buffer.from(payloadB64, "base64url").toString("utf8");
    payload = JSON.parse(jsonStr);
  } catch {
    return { valid: false, reason: "invalid payload: JSON parse error" };
  }
  if (payload.ver !== 1) {
    return { valid: false, reason: `unsupported license version: ${payload.ver}` };
  }
  if (!payload.customer || !payload.email || !payload.issued_at) {
    return { valid: false, reason: "missing required payload fields" };
  }
  if (payload.expires_at !== null && payload.expires_at !== void 0) {
    let expiresAtMs;
    if (typeof payload.expires_at === "number") {
      expiresAtMs = payload.expires_at * 1e3;
    } else {
      expiresAtMs = new Date(payload.expires_at).getTime();
    }
    if (isNaN(expiresAtMs)) {
      return { valid: false, reason: "invalid expires_at date" };
    }
    if (Date.now() > expiresAtMs) {
      return { valid: false, reason: `license expired on ${new Date(expiresAtMs).toISOString()}` };
    }
  }
  return { valid: true, payload };
}
var TRIAL_DAYS = 14;
var ROLLBACK_TOLERANCE_MS = 60 * 1e3;
var MAX_ROLLBACK_COUNT = 3;
function checkTrial() {
  maybeMigrateTrialFile();
  const trialPath = getTrialPath();
  const now = /* @__PURE__ */ new Date();
  const nowMs = now.getTime();
  if (!existsSync14(trialPath)) {
    const record2 = {
      first_run: now.toISOString(),
      last_seen: now.toISOString(),
      trial_days: TRIAL_DAYS,
      rollback_count: 0
    };
    try {
      writeFileSync3(trialPath, JSON.stringify(record2, null, 2), { encoding: "utf8", mode: 384 });
      try {
        chmodSync(trialPath, 384);
      } catch {
      }
    } catch {
    }
    return { ok: true, daysLeft: TRIAL_DAYS };
  }
  let record;
  try {
    record = JSON.parse(readFileSync11(trialPath, "utf8"));
  } catch {
    return { ok: false, reason: "trial record corrupt or tampered" };
  }
  const firstRun = new Date(record.first_run);
  const lastSeen = new Date(record.last_seen);
  if (isNaN(firstRun.getTime()) || isNaN(lastSeen.getTime())) {
    return { ok: false, reason: "trial record corrupt" };
  }
  const rollbackCount = record.rollback_count ?? 0;
  let newRollbackCount = rollbackCount;
  if (nowMs < lastSeen.getTime() - ROLLBACK_TOLERANCE_MS) {
    newRollbackCount = rollbackCount + 1;
    process.stderr.write(`eye: warning: clock rollback detected (rollback count: ${newRollbackCount})
`);
    if (newRollbackCount > MAX_ROLLBACK_COUNT) {
      return { ok: false, reason: "trial expired (clock rollback detected)" };
    }
  }
  const trialDays = record.trial_days ?? TRIAL_DAYS;
  const elapsedMs = nowMs - firstRun.getTime();
  const elapsedDays = elapsedMs / (1e3 * 60 * 60 * 24);
  const daysLeft = Math.max(0, Math.ceil(trialDays - elapsedDays));
  if (elapsedDays >= trialDays) {
    return { ok: false, reason: "trial period expired" };
  }
  const updated = {
    ...record,
    last_seen: now.toISOString(),
    rollback_count: newRollbackCount
  };
  try {
    writeFileSync3(trialPath, JSON.stringify(updated, null, 2), { encoding: "utf8", mode: 384 });
    try {
      chmodSync(trialPath, 384);
    } catch {
    }
  } catch {
  }
  return { ok: true, daysLeft };
}
function checkDailyQuota() {
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
  const usagePath = getUsagePath();
  let count = 0;
  if (existsSync14(usagePath)) {
    try {
      const rec = JSON.parse(readFileSync11(usagePath, "utf8"));
      if (rec.date === today)
        count = rec.count ?? 0;
    } catch {
    }
  }
  const remaining = Math.max(0, FREE_DAILY_QUOTA - count);
  if (remaining <= 0) {
    return { ok: false, remaining: 0, reason: `\u4ECA\u65E5\u514D\u8CBB\u984D\u5EA6\u5DF2\u7528\u5B8C\uFF08${count}/${FREE_DAILY_QUOTA}\uFF09` };
  }
  return { ok: true, remaining };
}
function incrementDailyUsage() {
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
  const usagePath = getUsagePath();
  const tmpPath = usagePath + ".tmp";
  let count = 0;
  if (existsSync14(usagePath)) {
    try {
      const rec = JSON.parse(readFileSync11(usagePath, "utf8"));
      if (rec.date === today)
        count = rec.count ?? 0;
    } catch {
    }
  }
  const updated = { date: today, count: count + 1 };
  try {
    writeFileSync3(tmpPath, JSON.stringify(updated), { encoding: "utf8", mode: 384 });
    try {
      chmodSync(tmpPath, 384);
    } catch {
    }
    renameSync(tmpPath, usagePath);
  } catch {
  }
}
function readLicenseFile() {
  const envPath = process.env.EYE_LICENSE;
  if (envPath) {
    try {
      return readFileSync11(envPath, "utf8");
    } catch {
      return null;
    }
  }
  maybeMigrateLicenseFile();
  const licPath = getLicensePath();
  if (!existsSync14(licPath)) {
    return null;
  }
  try {
    return readFileSync11(licPath, "utf8");
  } catch {
    return null;
  }
}
function checkLicense(publicKeyPem = PUBLIC_KEY_PEM) {
  const licText = readLicenseFile();
  if (licText) {
    const result = verifyLicense(licText, publicKeyPem);
    if (result.valid && result.payload) {
      const tier = result.payload.tier ?? "starter";
      void tier;
      return {};
    }
    process.stderr.write(`eye: license invalid: ${result.reason ?? "unknown"}
eye: falling back to free tier
`);
  }
  const trial = checkTrial();
  if (trial.ok) {
    if (trial.daysLeft !== void 0 && trial.daysLeft <= 3) {
      process.stderr.write(`eye: trial expires in ${trial.daysLeft} day(s). Visit https://hurricanetech.com/eye to purchase.
`);
    }
    return {};
  }
  const quota = checkDailyQuota();
  if (quota.ok) {
    process.stderr.write(`[eye] free tier: \u4ECA\u65E5\u5269\u9918 ${quota.remaining}/${FREE_DAILY_QUOTA} \u6B21
`);
    return { postScan: () => incrementDailyUsage() };
  }
  process.stderr.write(`
eye: ${quota.reason}

\u4ECA\u65E5\u514D\u8CBB\u984D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u660E\u5929\u518D\u8A66\u6216\u8CFC\u8CB7\u6388\u6B0A\uFF1A
  https://hurricanetech.com/eye

\u5DF2\u6709\u6388\u6B0A\uFF1F\u57F7\u884C\uFF1Aeye activate <license-file>
`);
  process.exit(1);
}
function getLicenseStatus(publicKeyPem = PUBLIC_KEY_PEM) {
  const licText = readLicenseFile();
  if (licText) {
    const result = verifyLicense(licText, publicKeyPem);
    if (result.valid && result.payload) {
      return {
        mode: "licensed",
        customer: result.payload.customer,
        email: result.payload.email,
        tier: result.payload.tier,
        expires_at: result.payload.expires_at
      };
    }
  }
  const trialPath = getTrialPath();
  if (!existsSync14(trialPath)) {
    return { mode: "trial", daysLeft: TRIAL_DAYS, trialDays: TRIAL_DAYS };
  }
  try {
    const record = JSON.parse(readFileSync11(trialPath, "utf8"));
    const firstRun = new Date(record.first_run);
    const trialDays = record.trial_days ?? TRIAL_DAYS;
    const elapsedMs = Date.now() - firstRun.getTime();
    const elapsedDays = elapsedMs / (1e3 * 60 * 60 * 24);
    const daysLeft = Math.max(0, Math.ceil(trialDays - elapsedDays));
    if (elapsedDays >= trialDays) {
      const quota = checkDailyQuota();
      return { mode: "free", usedToday: FREE_DAILY_QUOTA - quota.remaining, quota: FREE_DAILY_QUOTA };
    }
    return { mode: "trial", daysLeft, trialDays };
  } catch {
    const quota = checkDailyQuota();
    return { mode: "free", usedToday: FREE_DAILY_QUOTA - quota.remaining, quota: FREE_DAILY_QUOTA };
  }
}
function activateLicense(licFilePath) {
  let licText;
  try {
    licText = readFileSync11(licFilePath, "utf8");
  } catch {
    process.stderr.write(`eye activate: cannot read file: ${licFilePath}
`);
    process.exit(1);
  }
  const result = verifyLicense(licText);
  if (!result.valid) {
    process.stderr.write(`eye activate: license invalid: ${result.reason ?? "unknown"}
`);
    process.exit(1);
  }
  const payload = result.payload;
  getStateDir();
  const destPath = getLicensePath();
  try {
    writeFileSync3(destPath, licText.trim(), { encoding: "utf8", mode: 384 });
    chmodSync(destPath, 384);
  } catch {
    process.stderr.write(`eye activate: failed to write license to ${destPath}
`);
    process.exit(1);
  }
  let expiresDisplay;
  if (payload.expires_at === null || payload.expires_at === void 0) {
    expiresDisplay = "never (perpetual)";
  } else if (typeof payload.expires_at === "number") {
    expiresDisplay = new Date(payload.expires_at * 1e3).toISOString();
  } else {
    expiresDisplay = payload.expires_at;
  }
  process.stdout.write(`eye: license activated successfully
  Customer : ${payload.customer}
  Email    : ${payload.email}
  Tier     : ${payload.tier}
  Expires  : ${expiresDisplay}
`);
}

// dist/cli.js
import { dirname as dirname12, resolve as resolve11 } from "path";
import { fileURLToPath as fileURLToPath5 } from "url";
import { readFileSync as readFileSync12 } from "fs";
var __dirname3 = dirname12(fileURLToPath5(import.meta.url));
function getVersion() {
  let version = "unknown";
  try {
    const pkgPath = resolve11(__dirname3, "..", "package.json");
    const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
    version = pkg.version;
  } catch {
    return "unknown";
  }
  try {
    const metaPath = resolve11(__dirname3, "version-meta.json");
    const meta = JSON.parse(readFileSync12(metaPath, "utf8"));
    if (meta.sha && meta.builtAt) {
      return `${version} (git: ${meta.sha}, built: ${meta.builtAt})`;
    }
  } catch {
  }
  return version;
}
var HELP = `Eye \u2014 symbol blast-radius CLI
Usage:
  eye [--human] [--no-cache] [--backend ts-morph|tree-sitter|auto] <file>[:<symbol>]
  eye cycles <repo-path> [--json] [--human] [--lang ts]
  eye detect-changes [--from <ref>] [--to <ref>] [--base <ref>] [--staged] [--json] [--max-files N]
  eye breaking-changes [--from <ref>] [--to <ref>] [--staged] [--severity critical|warning|all] [--no-exit-code]
  eye diff <from>..<to> [--format json|human]
  eye registry build [--org <name>] [--output <db-path>]
  eye registry query --symbol <name> --source-repo <name> [--db <path>]
  eye dead-code [--db <path>] [--min-confidence low|medium|high] [--repo <name>] [--lang ts|py|go|cs|php|swift|java|css|scss] [--dir <path>]
  eye scan [<dir>] [--human] [--lang ts|py|go|cs|php|swift|css|scss] [--min-callers N] [--max-files N]
  eye explain <symbol> [--format json|human] [--hops 1..5] [--callees] [--json-pretty] [--dir <path>]
  eye cache clean [--older-than N]
  eye doctor [--format human|json]
  eye activate <license-file>
  eye license
  eye --version
  eye --help

Commands:
  <file>[:<symbol>]    blast radius\uFF1A\u8AB0\u547C\u53EB\uFF0Fimport \u9019\u500B\u6A94\u6848\u6216\u7B26\u865F
  cycles               \u627E intra-repo file-level \u5FAA\u74B0 import\uFF08TS-first\uFF09
  detect-changes       \u5206\u6790\u5169 git ref \u9593\u8B8A\u52D5\u7684\u7B26\u865F
  breaking-changes     \u5075\u6E2C API \u7C3D\u7AE0\u7834\u58DE\u6027\u8B8A\u66F4
  diff                 git \u7BC4\u570D\u7684\u8B8A\u66F4\u5206\u6790
  registry build       \u8DE8 repo \u4F9D\u8CF4\u5EFA\u8868\uFF08\u6383 org \u4E26\u5BEB\u5165 SQLite\uFF09
  registry query       \u8DE8 repo \u4F9D\u8CF4\u67E5\u8A62\uFF08\u67E5\u67D0 symbol \u88AB\u54EA\u4E9B repo \u5F15\u7528\uFF09
  dead-code            \u627E\u96F6 caller \u7684\u6B7B\u78BC
  scan                 \u6383\u76EE\u9304\u7684 symbol\uFF0Fcaller\uFF0F\u6B7B\u78BC\u98A8\u96AA
  explain              \u591A\u8DF3\u547C\u53EB\u93C8\uFF08\u8AB0\u547C\u53EB X\u3001X \u547C\u53EB\u8AB0\uFF09
  cache clean          \u6E05\u5FEB\u53D6
  doctor               \u74B0\u5883\u5065\u6AA2
  activate             \u555F\u7528 license \u6587\u4EF6\uFF08Ed25519 \u7C3D\u7AE0\u9A57\u8B49\uFF09
  license              \u986F\u793A\u76EE\u524D license / \u8A66\u7528\u72C0\u614B
  --version            \u986F\u793A\u7248\u672C\u865F
  --help               \u986F\u793A\u6B64\u8AAA\u660E

Options:
  --human                    Human-readable output instead of JSON
  --no-cache                 Skip cache, force re-analysis
  --no-color                 Suppress ANSI colour codes (also respects NO_COLOR env var)
  --json-pretty              Pretty-print JSON output (2-space indent)
  --format github-annotation GitHub Actions annotation output (::warning file=...,line=...::)
  --max-blast-radius N       Exit 1 if blast-radius total exceeds N callers
  --backend ts-morph|tree-sitter|auto
                             Analysis backend (default: auto)
                             auto: .ts/.tsx \u2192 ts-morph; .py \u2192 tree-sitter (AST); .cs/.go \u2192 tree-sitter
                             tree-sitter: use tree-sitter for all supported languages
                             ts-morph: use ts-morph for TS (fallback to tree-sitter for C#/Go)
  --version    Print version
  --help       Show this help

detect-changes options:
  --from <ref>          Base commit ref (default: HEAD~1); two-dot diff
  --to <ref>            Head commit ref (default: HEAD); two-dot diff
  --base <ref>          Fork-point diff: git merge-base <ref> HEAD \u2192 diff from there to HEAD.
                        Shows only changes introduced on the current branch since it diverged
                        from <ref>; excludes new commits that landed on <ref> after the fork.
                        Mutually exclusive with --from/--to semantics (--base takes priority).
                        diff_range output: merge-base(<ref>,HEAD)=<sha>..HEAD
                        Fallback: if merge-base fails (detached HEAD/shallow clone), falls back
                        to two-dot diff with a stderr warning.
  --staged              Analyse git staged changes (git diff --cached)
  --json                Output JSON (default: human-readable)
  --max-files N         Max files to analyse (default: 50); priority given to auth/db/payment files
  --max-blast-radius N  Exit 1 if any changed symbol's blast-radius exceeds N
  --no-cache            bypass the analysis cache (default)
  --cache               use cached results if available

  Env vars:
  EYE_DETECT_CONCURRENCY  Number of parallel file analysis workers (default: 4)

registry build options:
  --org <name>              Gitea org to scan (default: hurricanetech or GITEA_ORG env)
  --output <path>           SQLite DB path (default: ~/.local/share/eye/eye_scan.db or EYE_BOT_DB_PATH env)
  --repos all               Which repos to include (default: all non-archived)
  --loader-pattern <regex>  Regex with capture group 1 yielding a view path/name, e.g.
                            "load->view(['"]([ ^'"]+)['"]" (may repeat to add multiple patterns)
                            WARNING: avoid nested quantifiers (e.g. (a+)+, (.+)*) \u2014 they cause
                            catastrophic backtracking (ReDoS). Pattern length is capped at 1000 chars.
  --views-dir <path>        Absolute path to views directory; required when --loader-pattern is used.
                            Detected view files are resolved here and their symbols injected as
                            virtual caller edges (confidence=low) so dead-code does not flag them.

cache clean options:
  --older-than N    Remove entries older than N days (default: 7)
  EYE_CACHE_DIR     Env var: use this directory directly, skipping git-root detection

registry query options:
  --symbol <name>       Symbol name to look up
  --source-repo <name>  Repo that exports the symbol
  --db <path>           SQLite DB path (default: ~/.local/share/eye/eye_scan.db or EYE_BOT_DB_PATH env)
  --human               Human-readable table output instead of JSON

breaking-changes options:
  --from <ref>          Base ref (default: HEAD~1)
  --to <ref>            Target ref (default: HEAD)
  --staged              Compare staged changes
  --severity            critical|warning|all \u2014 filter output (default: all)
  --no-exit-code        Always exit 0 (informational mode)
  --human               Human-readable output: per-file headings, one line per change, summary

dead-code options:
  --db <path>           Path to eye_scan.db (default: ~/.local/share/eye/eye_scan.db or EYE_BOT_DB_PATH env)
  --min-confidence      low|medium|high \u2014 confidence level that counts as "has consumers" (default: low)
  --repo <name>         Scope to a single source repo
  --lang ts|py|go|cs|php|swift|java|css|scss  Scope to a single language
                        css/scss: bypass registry DB, scan CSS/SCSS files directly for 0-static-caller symbols
  --dir <path>          [CSS/SCSS mode only] Directory to scan for CSS/SCSS files (default: cwd)
  --skip-type-exports   Exclude type-only exports (export type / export interface) from results

scan options:
  <dir>                 Directory to scan (default: cwd)
  --human               Output sorted table: symbol | file | callers | risk level
                        (default: JSON { results: [...], errors: N })
  --lang ts|py|go|cs|php|swift|java|rs|kt|css|scss
                        Restrict scan to files matching the given language extension
                        (ts \u2192 .ts/.tsx, py \u2192 .py, go \u2192 .go, cs \u2192 .cs, php \u2192 .php,
                         swift \u2192 .swift, java \u2192 .java, rs \u2192 .rs, kt \u2192 .kt/.kts,
                         css \u2192 .css/.scss, scss \u2192 .css/.scss)
                        Invalid value \u2192 exit 2
  --min-callers N       Only output symbols with callers >= N (default: 0 = all symbols)
  --max-files N         Limit scan to the first N files; truncation warning emitted on stderr
  --quiet               Suppress stderr progress lines (per-symbol errors, progress counters);
                        fatal errors (exit 2/4) still appear on stderr

  Output shape (JSON mode):
    { results: [{ symbol, file, callers, risk, confidence }], errors: N }
    errors: number of symbols that failed analysis

  confidence values:
    "high"      tool found callers with high confidence
    "medium"    tool found callers with medium confidence
    "low"       tool uncertain (callers=0 but may have missed some)
    "isolated"  callers=0 AND tool is confident \u2014 symbol is genuinely unused

  Env vars:
  EYE_SCAN_CONCURRENCY  Number of parallel symbol analysis workers (default: 4)

  Exit codes:
    0  At least one symbol analysed successfully
    2  Usage error (dir not found or not a directory)
    4  All symbol analyses failed (errors > 0, results empty)

Exit codes:
  0  Success
  2  Usage error (bad args, file not found)
  3  Required tool missing (node, python3)
  4  Analysis error

diff options:
  <from>..<to>          Commit range (e.g. HEAD~3..HEAD)
  --format json|human   Output format (default: json)

explain options:
  <symbol>              Symbol name to look up (required)
  --format json|human   Output format (default: json)
  --hops 1..5           Caller graph depth 1-5 (default: 1); >5 is an error
  --callees             Also show what the symbol calls (grep-based, best-effort)
  --json-pretty         Pretty-print JSON output
  --dir <path>          Directory to search (default: cwd)

  Output shape (JSON mode):
    { symbol, callers: [{ file, line, hop, repo? }], summary: { totalCallers, uniqueFiles }, callees?: [{ name, file, line }] }

  Exit codes:
    0  Symbol found, callers listed
    2  Usage error or symbol not found

Examples:
  eye src/auth.ts:verifyToken
  eye --human src/services/user.ts
  eye api/routes.py:create_user
  eye detect-changes --staged --json
  eye detect-changes --from HEAD~3 --to HEAD --json
  eye detect-changes --base main --json
  eye diff HEAD~3..HEAD
  eye diff HEAD~3..HEAD --format human
  eye registry build --org hurricanetech --output ~/.local/share/eye/eye_scan.db
  eye registry query --symbol verifyToken --source-repo LocalIdentityServer
  eye explain analyze
  eye explain analyze --format human
  eye explain verifyToken --hops 2 --format human
  eye doctor
  eye doctor --format json

doctor options:
  --format human|json   Output format (default: human)

  Checks performed:
    1. Node.js version   >= 18.0.0
    2. git binary        available and >= 2.20
    3. eye version       package.json readable with valid semver
    4. EYE_BOT_DB_PATH   if set: parent dir writable; if not set: WARNING with default path
    5. tree-sitter WASM  tree-sitter-typescript importable
    6. .eye-cache/       directory in cwd creatable/writable

  Exit codes:
    0  All checks passed (or warnings only)
    1  One or more checks failed
`;
async function main() {
  const args = process.argv.slice(2);
  if (args.includes("--no-color")) {
    setNoColorFlag(true);
  }
  const jsonPretty = args.includes("--json-pretty");
  if (args.length === 0) {
    process.stderr.write(HELP);
    process.exit(2);
  }
  if (args.includes("--help") || args.includes("-h")) {
    process.stdout.write(HELP);
    process.exit(0);
  }
  if (args.includes("--version") || args.includes("-v")) {
    process.stdout.write(`${getVersion()}
`);
    process.exit(0);
  }
  const EXEMPT_COMMANDS = /* @__PURE__ */ new Set(["license", "activate", "doctor"]);
  const isExempt = EXEMPT_COMMANDS.has(args[0]);
  if (args[0] === "activate") {
    const licFile = args[1];
    if (!licFile) {
      process.stderr.write("usage: eye activate <license-file>\n");
      process.exit(2);
    }
    activateLicense(licFile);
    return;
  }
  if (args[0] === "license") {
    const status = getLicenseStatus();
    if (status.mode === "licensed") {
      let expiresDisplay;
      if (status.expires_at === null || status.expires_at === void 0) {
        expiresDisplay = "never (perpetual)";
      } else if (typeof status.expires_at === "number") {
        expiresDisplay = new Date(status.expires_at * 1e3).toISOString();
      } else {
        expiresDisplay = status.expires_at;
      }
      process.stdout.write(`eye: licensed
  Customer : ${status.customer}
  Email    : ${status.email}
  Tier     : ${status.tier}
  Expires  : ${expiresDisplay}
`);
    } else if (status.mode === "trial") {
      process.stdout.write(`eye: trial mode
  Days remaining : ${status.daysLeft} / ${status.trialDays}
  Purchase       : https://hurricanetech.com/eye
`);
    } else if (status.mode === "free") {
      process.stdout.write(`eye: free tier
  Today: ${status.usedToday}/${status.quota} scans used
  Purchase: https://hurricanetech.com/eye
`);
    } else {
      process.stdout.write(`eye: expired
  Reason  : ${status.reason}
  Purchase: https://hurricanetech.com/eye
`);
    }
    return;
  }
  let licResult = {};
  if (!isExempt) {
    licResult = checkLicense();
  }
  if (args[0] === "cache") {
    const sub = args[1];
    if (sub === "clean") {
      let olderThan = 7;
      for (let i = 2; i < args.length; i++) {
        if (args[i] === "--older-than" && args[i + 1]) {
          olderThan = parseInt(args[++i], 10);
          if (isNaN(olderThan) || olderThan < 0) {
            process.stderr.write("error: --older-than must be a non-negative integer\n");
            process.exit(2);
          }
        } else if (args[i].startsWith("--older-than=")) {
          olderThan = parseInt(args[i].slice("--older-than=".length), 10);
          if (isNaN(olderThan) || olderThan < 0) {
            process.stderr.write("error: --older-than must be a non-negative integer\n");
            process.exit(2);
          }
        }
      }
      let cacheDir;
      if (process.env.EYE_CACHE_DIR) {
        cacheDir = process.env.EYE_CACHE_DIR;
      } else {
        const repoRoot2 = findRepoRoot(process.cwd());
        if (!repoRoot2) {
          process.stderr.write("error: not inside a git repository (set EYE_CACHE_DIR to override)\n");
          process.exit(2);
        }
        cacheDir = getCacheDir(repoRoot2);
      }
      const deleted = cleanCache(cacheDir, olderThan);
      process.stdout.write(`cache clean: removed ${deleted} entries older than ${olderThan} day(s)
`);
      process.exit(0);
    } else {
      process.stderr.write("usage: eye cache clean [--older-than N]\n");
      process.exit(2);
    }
  }
  if (args[0] === "cycles") {
    await runCycles(args.slice(1));
    licResult.postScan?.();
    return;
  }
  if (args[0] === "scan") {
    await runScan(args.slice(1));
    licResult.postScan?.();
    return;
  }
  if (args[0] === "detect-changes") {
    await runDetectChanges(args.slice(1));
    licResult.postScan?.();
    return;
  }
  if (args[0] === "diff") {
    await runDiff(args.slice(1));
    licResult.postScan?.();
    return;
  }
  if (args[0] === "registry") {
    const sub = args[1];
    if (sub === "build") {
      await runRegistryBuild(args.slice(2));
      licResult.postScan?.();
      return;
    } else if (sub === "query") {
      runRegistryQuery(args.slice(2));
      licResult.postScan?.();
      return;
    } else {
      process.stderr.write("usage: eye registry <build|query> [...options]\n");
      process.exit(2);
    }
  }
  if (args[0] === "dead-code") {
    await runDeadCode(args.slice(1));
    licResult.postScan?.();
    return;
  }
  if (args[0] === "breaking-changes") {
    await runBreakingChanges(args.slice(1));
    licResult.postScan?.();
    return;
  }
  if (args[0] === "explain") {
    const explainArgs = args.slice(1);
    if (explainArgs.includes("--help") || explainArgs.includes("-h")) {
      process.stdout.write(`usage: eye explain <symbol> [--format json|human] [--hops 1..5] [--callees] [--json-pretty] [--dir <path>]

  <symbol>              Symbol name to look up
  --format json|human   Output format (default: json)
  --hops 1..5           Caller graph depth 1-5 (default: 1)
  --callees             Also show what the symbol calls (grep-based, best-effort)
  --json-pretty         Pretty-print JSON
  --dir <path>          Directory to search (default: cwd)
`);
      process.exit(0);
      return;
    }
    let symbol2 = "";
    let format = "json";
    let jsonPretty2 = false;
    let hops = 1;
    let callees = false;
    let rootDir = process.cwd();
    for (let i = 0; i < explainArgs.length; i++) {
      const a = explainArgs[i];
      if (a === "--format" && explainArgs[i + 1]) {
        format = explainArgs[++i];
      } else if (a === "--hops" && explainArgs[i + 1]) {
        const hopsVal = parseInt(explainArgs[++i], 10);
        if (isNaN(hopsVal) || hopsVal < 1 || hopsVal > 5) {
          process.stderr.write("error: --hops must be between 1 and 5\n");
          process.exit(2);
          return;
        }
        hops = hopsVal;
      } else if (a === "--callees") {
        callees = true;
      } else if (a === "--json-pretty") {
        jsonPretty2 = true;
      } else if (a === "--dir" && explainArgs[i + 1]) {
        rootDir = explainArgs[++i];
      } else if (!a.startsWith("--")) {
        symbol2 = a;
      }
    }
    if (!symbol2) {
      process.stderr.write("error: explain requires a symbol name\n");
      process.stderr.write("usage: eye explain <symbol> [--format json|human] [--hops 1..5] [--callees] [--json-pretty] [--dir <path>]\n");
      process.exit(2);
      return;
    }
    if (symbol2.includes(":") || symbol2.includes("/") || symbol2.includes("\\")) {
      process.stderr.write(`error: eye explain accepts a bare symbol name (e.g. 'eye explain foo'), not file:symbol
hint: use 'eye <file>:<symbol>' for blast-radius analysis
`);
      process.exit(2);
      return;
    }
    await runEyeExplain({ symbol: symbol2, format, jsonPretty: jsonPretty2, hops, callees, rootDir });
    licResult.postScan?.();
    return;
  }
  if (args[0] === "doctor") {
    const doctorArgs = args.slice(1);
    if (doctorArgs.includes("--help") || doctorArgs.includes("-h")) {
      process.stdout.write(`usage: eye doctor [--format human|json]

  Runs environment health checks and reports [OK]/[WARN]/[FAIL] for each.

  Checks:
    1. Node.js version   >= 18.0.0
    2. git binary        available and >= 2.20
    3. eye version       package.json readable with valid semver
    4. EYE_BOT_DB_PATH   if set: parent dir writable; if not set: WARNING
    5. tree-sitter WASM  tree-sitter-typescript importable
    6. .eye-cache/       directory in cwd creatable/writable

  Exit codes:
    0  All checks passed (or warnings only)
    1  One or more checks failed
`);
      process.exit(0);
      return;
    }
    let doctorFormat = "human";
    for (let i = 0; i < doctorArgs.length; i++) {
      if (doctorArgs[i] === "--format" && doctorArgs[i + 1]) {
        const v = doctorArgs[++i];
        if (v === "human" || v === "json") {
          doctorFormat = v;
        } else {
          process.stderr.write("error: --format must be human or json\n");
          process.exit(2);
          return;
        }
      }
    }
    runEyeDoctor({ format: doctorFormat });
    return;
  }
  let human = false;
  let noCache = false;
  let target = "";
  let backend = "auto";
  let outputFormat = "default";
  let maxBlastRadius = null;
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === "--human") {
      human = true;
    } else if (arg === "--no-cache") {
      noCache = true;
    } else if (arg === "--format" && args[i + 1]) {
      const fmt = args[++i];
      if (fmt === "github-annotation") {
        outputFormat = "github-annotation";
      } else {
        process.stderr.write(`error: --format must be github-annotation (or omit for default)
`);
        process.exit(2);
      }
    } else if (arg.startsWith("--format=")) {
      const fmt = arg.slice("--format=".length);
      if (fmt === "github-annotation") {
        outputFormat = "github-annotation";
      } else {
        process.stderr.write(`error: --format must be github-annotation (or omit for default)
`);
        process.exit(2);
      }
    } else if (arg === "--max-blast-radius" && args[i + 1]) {
      const n = parseInt(args[++i], 10);
      if (isNaN(n) || n < 0) {
        process.stderr.write("error: --max-blast-radius must be a non-negative integer\n");
        process.exit(2);
      }
      maxBlastRadius = n;
    } else if (arg.startsWith("--max-blast-radius=")) {
      const n = parseInt(arg.slice("--max-blast-radius=".length), 10);
      if (isNaN(n) || n < 0) {
        process.stderr.write("error: --max-blast-radius must be a non-negative integer\n");
        process.exit(2);
      }
      maxBlastRadius = n;
    } else if (arg === "--backend") {
      const val = args[i + 1];
      if (val === "ts-morph" || val === "tree-sitter" || val === "auto") {
        backend = val;
        i++;
      } else {
        process.stderr.write(`error: --backend must be ts-morph, tree-sitter, or auto
`);
        process.exit(2);
      }
    } else if (arg.startsWith("--backend=")) {
      const val = arg.slice("--backend=".length);
      if (val === "ts-morph" || val === "tree-sitter" || val === "auto") {
        backend = val;
      } else {
        process.stderr.write(`error: --backend must be ts-morph, tree-sitter, or auto
`);
        process.exit(2);
      }
    } else if (!arg.startsWith("--")) {
      target = arg;
    }
  }
  if (!target) {
    process.stderr.write("usage: eye [--human] [--no-cache] <file>[:<symbol>]\n");
    process.exit(2);
  }
  const { file, symbol } = parseFileSymbol(target);
  const absFile = resolve11(process.cwd(), file);
  const repoRoot = findRepoRoot(dirname12(absFile)) ?? dirname12(absFile);
  const eyeIgnore = loadEyeIgnore(repoRoot);
  try {
    const result = await analyze({ file, symbol, options: { noCache, human }, backend, eyeIgnore });
    if (outputFormat === "github-annotation") {
      const targetDisplay = symbol || file;
      const defRef = result.refs.find((r) => r.kind === "def");
      const targetLine = defRef?.line ?? 1;
      process.stdout.write(`::notice file=${file},line=${targetLine},col=1::eye: ${targetDisplay} has ${result.total} callers (blast-radius)
`);
      for (const ref of result.refs) {
        if (ref.kind === "def")
          continue;
        const hop = ref.hop ?? 0;
        process.stdout.write(`::warning file=${ref.file},line=${ref.line},col=1::eye: ${targetDisplay} blast-radius caller (hop=${hop})
`);
      }
    } else if (human) {
      process.stdout.write(formatHuman(result) + "\n");
    } else {
      process.stdout.write(formatJson(result, jsonPretty) + "\n");
    }
    if (maxBlastRadius !== null && result.total > maxBlastRadius) {
      process.stderr.write(`eye: blast-radius exceeded threshold (${result.total} > ${maxBlastRadius}) \u2014 ${file}:${symbol || "(file)"}
`);
      licResult.postScan?.();
      process.exit(1);
    }
    licResult.postScan?.();
    process.exit(0);
  } catch (err) {
    if (err instanceof EyeError) {
      process.stderr.write(`${err.message}
`);
      process.exit(err.code);
    }
    process.stderr.write(`error: ${String(err)}
`);
    process.exit(4);
  }
}
main().catch((err) => {
  process.stderr.write(`fatal: ${String(err)}
`);
  process.exit(4);
});
