#
tokens: 19469/50000 18/18 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── .xray
│   ├── xray.db
│   ├── xray.db-shm
│   └── xray.db-wal
├── getting_started.md
├── install.sh
├── LICENSE
├── mcp-config-generator.py
├── pyproject.toml
├── README.md
├── src
│   └── xray
│       ├── __init__.py
│       ├── core
│       │   ├── __init__.py
│       │   └── indexer.py
│       ├── lsp_config.json
│       └── mcp_server.py
├── test_samples
│   ├── test_class_expression.js
│   ├── test.go
│   ├── test.js
│   ├── test.py
│   └── test.ts
├── tests
│   └── __init__.py
└── uninstall.sh
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Build artifacts
build/
dist/
*.egg-info/

# Test artifacts
.pytest_cache/

# Node.js dependencies
node_modules/

# Python cache
__pycache__/
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# XRAY MCP - Progressive Code Intelligence for AI Assistants

[![Python](https://img.shields.io/badge/Python-3.10+-green)](https://python.org) [![MCP](https://img.shields.io/badge/MCP-Compatible-purple)](https://modelcontextprotocol.io) [![ast-grep](https://img.shields.io/badge/Powered_by-ast--grep-orange)](https://ast-grep.github.io)

## ❌ Without XRAY

AI assistants struggle with codebase understanding. You get:

- ❌ "I can't see your code structure"
- ❌ "I don't know what depends on this function"
- ❌ Generic refactoring advice without impact analysis
- ❌ No understanding of symbol relationships

## ✅ With XRAY

XRAY gives AI assistants code navigation capabilities. Add `use XRAY tools` to your prompt:

```txt
Analyze the UserService class and show me what would break if I change the authenticate method. use XRAY tools
```

```txt
Find all functions that call validate_user and show their dependencies. use XRAY tools
```

XRAY provides three focused tools:

- 🗺️ **Map** (`explore_repo`) - See project structure with symbol skeletons
- 🔍 **Find** (`find_symbol`) - Locate functions and classes with fuzzy search
- 💥 **Impact** (`what_breaks`) - Find where a symbol is referenced

## 🚀 Quick Install

### Modern Install with uv (Recommended)

```bash
# Install uv if you don't have it
curl -LsSf https://astral.sh/uv/install.sh | sh

# Clone and install XRAY
git clone https://github.com/srijanshukla18/xray.git
cd xray
uv tool install .
```

### Automated Install with uv

For the quickest setup, this script automates the `uv` installation process.

```bash
curl -fsSL https://raw.githubusercontent.com/srijanshukla18/xray/main/install.sh | bash
```

### Generate Config

```bash
# Get config for your tool
python mcp-config-generator.py cursor local_python
python mcp-config-generator.py claude docker  
python mcp-config-generator.py vscode source
```

## Language Support

XRAY uses [ast-grep](https://ast-grep.github.io), a tree-sitter powered structural search tool, providing accurate parsing for:
- **Python** - Functions, classes, methods, async functions
- **JavaScript** - Functions, classes, arrow functions, imports
- **TypeScript** - All JavaScript features plus interfaces, type aliases
- **Go** - Functions, structs, interfaces, methods

ast-grep ensures structural accuracy - it understands code syntax, not just text patterns.

## The XRAY Workflow - Progressive Discovery

### 1. Map - Start Simple, Then Zoom In
```python
# First: Get the big picture (directories only)
tree = explore_repo("/path/to/project")
# Returns:
# /path/to/project/
# ├── src/
# ├── tests/
# ├── docs/
# └── config/

# Then: Zoom into areas of interest with full details
tree = explore_repo("/path/to/project", focus_dirs=["src"], include_symbols=True)
# Returns:
# /path/to/project/
# └── src/
#     ├── auth.py
#     │   ├── class AuthService: # Handles user authentication
#     │   ├── def authenticate(username, password): # Validates user credentials
#     │   └── def logout(session_id): # Ends user session
#     └── models.py
#         ├── class User(BaseModel): # User account model
#         └── ... and 3 more

# Or: Limit depth for large codebases
tree = explore_repo("/path/to/project", max_depth=2, include_symbols=True)
```

### 2. Find - Locate Specific Symbols
```python
# Find symbols matching "authenticate" (fuzzy search)
symbols = find_symbol("/path/to/project", "authenticate")
# Returns list of exact symbol objects with name, type, path, line numbers
```

### 3. Impact - See What Would Break
```python
# Find where authenticate_user is used
symbol = symbols[0]  # From find_symbol
result = what_breaks(symbol)
# Returns: {"references": [...], "total_count": 12, 
#          "note": "Found 12 potential references based on text search..."}
```


## Architecture

```
FastMCP Server (mcp_server.py)
    ↓
Core Engine (src/xray/core/)
    └── indexer.py      # Orchestrates ast-grep for structural analysis
    ↓
ast-grep (external binary)
    └── Tree-sitter powered structural search
```

**Stateless design** - No database, no persistent index. Each operation runs fresh ast-grep queries for real-time accuracy.

## Why ast-grep?

Traditional grep searches text. ast-grep searches code structure:

- **grep**: Finds "authenticate" in function names, variables, comments, strings
- **ast-grep**: Finds only `def authenticate()` or `function authenticate()` definitions

This structural approach provides clean, accurate results essential for reliable code intelligence.

## Performance Characteristics

- **Startup**: Fast - launches ast-grep subprocess
- **File tree**: Python directory traversal
- **Symbol search**: Runs multiple ast-grep patterns, speed depends on codebase size
- **Impact analysis**: Name-based search across all files
- **Memory**: Minimal - no persistent state

## What Makes This Practical

1. **Progressive Discovery** - Start with directories, add symbols only where needed
2. **Smart Caching** - Symbol extraction cached per git commit for instant re-runs
3. **Flexible Focus** - Use `focus_dirs` to zoom into specific parts of large codebases
4. **Enhanced Symbols** - See function signatures and docstrings, not just names
5. **Based on tree-sitter** - ast-grep provides accurate structural analysis

XRAY helps AI assistants avoid information overload while providing deep code intelligence where needed.

## Stateless Design

XRAY performs on-demand structural analysis using ast-grep. There's no database to manage, no index to build, and no state to maintain. Each query runs fresh against your current code.

## Getting Started

1. **Install**: See [`getting_started.md`](getting_started.md) for modern installation
2. **Map the terrain**: `explore_repo("/path/to/project")`
3. **Find your target**: `find_symbol("/path/to/project", "UserService")`
4. **Assess impact**: `what_breaks(symbol)`

## The XRAY Philosophy

XRAY bridges the gap between simple text search and complex LSP servers:

- **More than grep** - Matches code syntax patterns, not just text
- **Less than LSP** - No language servers or complex setup
- **Practical for AI** - Provides structured data about code relationships

A simple tool that helps AI assistants navigate codebases more effectively than text search alone.

## Architectural Journey & Design Rationale

The current implementation of XRAY is the result of a rigorous evaluation of multiple code analysis methodologies. My journey involved prototyping and assessing several distinct approaches, each with its own set of trade-offs. Below is a summary of the considered architectures and the rationale for my final decision.

1.  **Naive Grep-Based Analysis**: I initially explored a baseline approach using standard `grep` for symbol identification. While expedient, this method proved fundamentally inadequate due to its inability to differentiate between syntactical constructs and simple text occurrences (e.g., comments, strings, variable names). The high signal-to-noise ratio rendered it impractical for reliable code intelligence.

2.  **Tree-Sitter Native Integration**: A direct integration with `tree-sitter` was evaluated to leverage its powerful parsing capabilities. However, this path was fraught with significant implementation complexities, including intractable errors within the parser generation and binding layers. The maintenance overhead and steep learning curve for custom grammar development were deemed prohibitive for a lean, multi-language tool.

3.  **Language Server Protocol (LSP)**: I considered leveraging the Language Server Protocol for its comprehensive, standardized approach to code analysis. This was ultimately rejected due to the excessive operational burden it would impose on the end-user, requiring them to install, configure, and manage separate LSPs for each language in their environment. This friction conflicted with my goal of a lightweight, zero-configuration user experience.

4.  **Comby-Based Structural Search**: `Comby` was explored for its structural search and replacement capabilities. Despite its promising feature set, I encountered significant runtime instability and idiosyncratic behavior that undermined its reliability for mission-critical code analysis. The tool's performance and consistency did not meet my stringent requirements for a production-ready system.

5.  **ast-grep as the Core Engine**: My final and current architecture is centered on `ast-grep`. This tool provides the optimal balance of structural awareness, performance, and ease of integration. By leveraging `tree-sitter` internally, it offers robust, syntactically-aware code analysis without the complexities of direct `tree-sitter` integration or the overhead of LSPs. Its reliability and rich feature set for structural querying made it the unequivocal choice for XRAY's core engine.

---

# Getting Started with XRAY - Modern Installation with uv

XRAY is a minimal-dependency code intelligence system that enhances AI assistants' understanding of codebases. This guide shows how to install and use XRAY with the modern `uv` package manager.

## Prerequisites

- Python 3.10 or later
- [uv](https://docs.astral.sh/uv/) - Fast Python package manager

### Installing uv

```bash
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"

# Or with pip
pip install uv
```

## Installation Options

### Option 1: Automated Install (Easiest)

For the quickest setup, use the one-line installer from the `README.md`. This will handle everything for you.

```bash
curl -fsSL https://raw.githubusercontent.com/srijanshukla18/xray/main/install.sh | bash
```

### Option 2: Quick Try with uvx (Recommended for Testing)

Run XRAY directly without installation using `uvx`:

```bash
# Clone the repository
git clone https://github.com/srijanshukla18/xray.git
cd xray

# Run XRAY directly with uvx
uvx --from . xray-mcp
```

### Option 3: Install as a Tool (Recommended for Regular Use)

Install XRAY as a persistent tool:

```bash
# Clone and install
git clone https://github.com/srijanshukla18/xray.git
cd xray

# Install with uv
uv tool install .

# Now you can run xray-mcp from anywhere
xray-mcp
```

### Option 4: Development Installation

For contributing or modifying XRAY:

```bash
# Clone the repository
git clone https://github.com/srijanshukla18/xray.git
cd xray

# Create and activate virtual environment with uv
uv venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install in editable mode
uv pip install -e .

# Run the server
python -m xray.mcp_server
```

## Configure Your AI Assistant

After installation, configure your AI assistant to use XRAY:

### Using the MCP Config Generator (Recommended)

For easier configuration, use the `mcp-config-generator.py` script located in the XRAY repository. This script can generate the correct JSON configuration for various AI assistants and installation methods.

To use it:

1.  Navigate to the XRAY repository root:
    ```bash
    cd /path/to/xray
    ```
2.  Run the script with your desired tool and installation method. For example, to get the configuration for Claude Desktop with an installed `xray-mcp` script:
    ```bash
    python mcp-config-generator.py claude installed_script
    ```
    Or for VS Code with a local Python installation:
    ```bash
    python mcp-config-generator.py vscode local_python
    ```
    The script will print the JSON configuration and instructions on where to add it.

    Available tools: `cursor`, `claude`, `vscode`
    Available methods: `local_python`, `docker`, `source`, `installed_script` (method availability varies by tool)

### Manual Configuration (Advanced)

If you prefer to configure manually, here are examples for common AI assistants:

#### Claude CLI (Claude Code)

For Claude CLI users, simply run:

```bash
claude mcp add xray xray-mcp -s local
```

Then verify it's connected:

```bash
claude mcp list | grep xray
```

#### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):

```json
{
  "mcpServers": {
    "xray": {
      "command": "uvx",
      "args": ["--from", "/path/to/xray", "xray-mcp"]
    }
  }
}
```

Or if installed as a tool:

```json
{
  "mcpServers": {
    "xray": {
      "command": "xray-mcp"
    }
  }
}
```

#### Cursor

Settings → Cursor Settings → MCP → Add new global MCP server:

```json
{
  "mcpServers": {
    "xray": {
      "command": "xray-mcp"
    }
  }
}
```

## Minimal Dependencies

One of XRAY's best features is its minimal dependency profile. You don't need to install a suite of language servers. XRAY uses:

- **ast-grep**: A single, fast binary for structural code analysis.
- **Python**: For the server and core logic.

This means you can start using XRAY immediately after installation with no complex setup!

## Verify Installation

### 1. Check XRAY is accessible

```bash
# If installed as tool
xray-mcp --version

# If using uvx
uvx --from /path/to/xray xray-mcp --version
```

### 2. Test basic functionality

Create a test file `test_xray.py`:

```python
def hello_world():
    print("Hello from XRAY test!")

def calculate_sum(a, b):
    return a + b

class Calculator:
    def multiply(self, x, y):
        return x * y
```

### 3. In your AI assistant, test these commands:

```
Build the index for the current directory. use XRAY tools
```

Expected: Success message with files indexed

```
Find all functions containing "hello". use XRAY tools
```

Expected: Should find `hello_world` function

```
What would break if I change the multiply method? use XRAY tools
```

Expected: Impact analysis showing any dependencies

## Usage Examples

Once configured, use XRAY by adding "use XRAY tools" to your prompts:

```
# Index a codebase
"Index the src/ directory for analysis. use XRAY tools"

# Find symbols
"Find all classes that contain 'User' in their name. use XRAY tools"

# Impact analysis
"What breaks if I change the authenticate method in UserService? use XRAY tools"

# Dependency tracking
"What does the PaymentProcessor class depend on? use XRAY tools"

# Location queries
"What function is defined at line 125 in main.py? use XRAY tools"
```

## Troubleshooting

### uv not found

Make sure uv is in your PATH:

```bash
# Add to ~/.bashrc or ~/.zshrc
export PATH="$HOME/.cargo/bin:$PATH"
```

### Permission denied

On macOS/Linux, you might need to make the script executable:

```bash
chmod +x ~/.local/bin/xray-mcp
```

### Python version issues

XRAY requires Python 3.10+. Check your version:

```bash
python --version

# If needed, install Python 3.10+ with uv
uv python install 3.10
```

### MCP connection issues

1. Check XRAY is running: `xray-mcp --test`
2. Verify your MCP config JSON is valid
3. Restart your AI assistant after config changes

## Advanced Configuration

### Custom Database Location

Set the `XRAY_DB_PATH` environment variable:

```bash
export XRAY_DB_PATH="$HOME/.xray/databases"
```

### Debug Mode

Enable debug logging:

```bash
export XRAY_DEBUG=1
```

## What's Next?

1. **Index your first repository**: In your AI assistant, ask it to "Build the index for my project. use XRAY tools"

2. **Explore the tools**:
   - `build_index` - Visual file tree of your repository
   - `find_symbol` - Fuzzy search for functions, classes, and methods
   - `what_breaks` - Find what code depends on a symbol (reverse dependencies)
   - `what_depends` - Find what a symbol depends on (calls and imports)
   
   Note: Results may include matches from comments or strings. The AI assistant will intelligently filter based on context.

3. **Read the documentation**: Check out the [README](README.md) for detailed examples and API reference

## Why XRAY Uses a Minimal Dependency Approach

XRAY is designed for simplicity and ease of use. It relies on:

- **ast-grep**: A powerful and fast single-binary tool for code analysis.
- **Python**: For its robust standard library and ease of scripting.

This approach avoids the complexity of setting up and managing multiple language servers, while still providing accurate, structural code intelligence.

## Benefits of Using uv

- **10-100x faster** than pip for installations
- **No virtual environment hassles** - uv manages everything
- **Reproducible installs** - uv.lock ensures consistency
- **Built-in Python management** - install any Python version
- **Global tool management** - like pipx but faster

Happy coding with XRAY! 🚀

```

--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------

```python
"""Tests for XRAY."""
```

--------------------------------------------------------------------------------
/src/xray/core/__init__.py:
--------------------------------------------------------------------------------

```python
"""Core XRAY functionality."""
```

--------------------------------------------------------------------------------
/test_samples/test_class_expression.js:
--------------------------------------------------------------------------------

```javascript



const myClass = class MyClass {};
```

--------------------------------------------------------------------------------
/src/xray/__init__.py:
--------------------------------------------------------------------------------

```python
"""XRAY: Fast code intelligence for AI assistants."""

__version__ = "0.1.0"
```

--------------------------------------------------------------------------------
/test_samples/test.js:
--------------------------------------------------------------------------------

```javascript
// JavaScript test file
import { readFile } from 'fs';
import axios from 'axios';

// Function declaration
function processData(data) {
    console.log('Processing data:', data);
    return data.map(item => item * 2);
}

// Arrow function
const fetchData = async (url) => {
    const response = await axios.get(url);
    return response.data;
};

// Class declaration
class DataService {
    constructor(apiUrl) {
        this.apiUrl = apiUrl;
    }
    
    async getData() {
        const data = await fetchData(this.apiUrl);
        return processData(data);
    }
    
    validateData(data) {
        return data && data.length > 0;
    }
}

// Function expression
const helper = function(x) {
    return x * 2;
};

// Export
export default DataService;
export { processData, fetchData };
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "xray"
version = "0.6.1"
description = "XRAY: Progressive code intelligence for AI assistants - Map, Find, Impact"
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
authors = [
    { name = "Srijan Shukla", email = "[email protected]" }
]
keywords = ["mcp", "code-intelligence", "ai-assistant", "code-analysis", "ast-grep", "structural-search"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
]
dependencies = [
    "fastmcp>=0.1.0",
    "ast-grep-cli>=0.39.0",
    "thefuzz>=0.20.0",
]

[project.urls]
Homepage = "https://github.com/srijanshukla18/xray"
Repository = "https://github.com/srijanshukla18/xray"
Issues = "https://github.com/srijanshukla18/xray/issues"

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
where = ["src"]

[project.scripts]
xray-mcp = "xray.mcp_server:main"
```

--------------------------------------------------------------------------------
/uninstall.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# XRAY MCP Server Uninstallation Script
# Usage: bash uninstall.sh

set -e

echo "🗑️ Uninstalling XRAY MCP Server..."

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Check if uv is installed
if ! command -v uv &> /dev/null; then
    echo -e "${RED}❌${NC} uv is not installed. Uninstallation requires uv."
    echo "Please install uv (https://github.com/astral-sh/uv) and try again."
    exit 1
fi

# Uninstall the xray tool
echo -e "${YELLOW}🔧${NC} Uninstalling xray tool..."
if uv tool uninstall xray; then
    echo -e "${GREEN}✓${NC} xray tool uninstalled successfully."
else
    echo -e "${YELLOW}⚠${NC} Could not uninstall xray tool. It might not be installed."
fi

# Remove the installation directory
INSTALL_DIR="$HOME/.xray"
if [ -d "$INSTALL_DIR" ]; then
    echo -e "${YELLOW}🗑️${NC} Removing installation directory: $INSTALL_DIR"
    rm -rf "$INSTALL_DIR"
    echo -e "${GREEN}✓${NC} Installation directory removed."
fi

# Verify uninstallation
if ! command -v xray-mcp &> /dev/null; then
    echo -e "${GREEN}✅ Uninstallation complete!${NC}"
else
    echo -e "${RED}❌ Uninstallation failed.${NC} xray-mcp is still on the PATH."
    echo "This might be due to your shell caching the command. Please restart your shell."
fi 
```

--------------------------------------------------------------------------------
/test_samples/test.ts:
--------------------------------------------------------------------------------

```typescript
// TypeScript test file
import { EventEmitter } from 'events';
import type { Request, Response } from 'express';

// Interface
interface User {
    id: number;
    name: string;
    email: string;
}

// Type alias
type UserRole = 'admin' | 'user' | 'guest';

// Enum
enum Status {
    Active = 'ACTIVE',
    Inactive = 'INACTIVE',
    Pending = 'PENDING'
}

// Abstract class
abstract class BaseService {
    protected apiUrl: string;
    
    constructor(apiUrl: string) {
        this.apiUrl = apiUrl;
    }
    
    abstract fetchData<T>(): Promise<T>;
}

// Generic class
class UserService extends BaseService {
    private users: Map<number, User>;
    
    constructor(apiUrl: string) {
        super(apiUrl);
        this.users = new Map();
    }
    
    async fetchData<User>(): Promise<User> {
        // Implementation
        return {} as User;
    }
    
    addUser(user: User): void {
        this.users.set(user.id, user);
    }
    
    getUser(id: number): User | undefined {
        return this.users.get(id);
    }
}

// Function with type annotations
function processUsers(users: User[], role: UserRole): User[] {
    return users.filter(user => {
        // Some filtering logic
        return true;
    });
}

// Namespace
namespace Utils {
    export function formatDate(date: Date): string {
        return date.toISOString();
    }
    
    export class Logger {
        log(message: string): void {
            console.log(message);
        }
    }
}

// Type guard
function isUser(obj: any): obj is User {
    return obj && typeof obj.id === 'number' && typeof obj.name === 'string';
}

export { UserService, processUsers, Status, Utils };
export type { User, UserRole };
```

--------------------------------------------------------------------------------
/test_samples/test.py:
--------------------------------------------------------------------------------

```python
# Python test file
import os
import sys
from typing import List, Dict, Optional
from dataclasses import dataclass
import asyncio

# Class definition
class DataProcessor:
    def __init__(self, config: Dict[str, any]):
        self.config = config
        self.data = []
    
    def process(self, items: List[str]) -> List[str]:
        """Process a list of items."""
        return [self._transform(item) for item in items]
    
    def _transform(self, item: str) -> str:
        """Transform a single item."""
        return item.upper()
    
    @staticmethod
    def validate(data: any) -> bool:
        """Validate data."""
        return bool(data)

# Dataclass
@dataclass
class User:
    id: int
    name: str
    email: str
    
    def get_display_name(self) -> str:
        return f"{self.name} <{self.email}>"

# Function definitions
def fetch_data(url: str) -> Dict[str, any]:
    """Fetch data from a URL."""
    # Implementation
    return {"data": []}

async def async_fetch(url: str) -> Dict[str, any]:
    """Async fetch data."""
    await asyncio.sleep(1)
    return fetch_data(url)

# Lambda function
transform = lambda x: x * 2

# Generator function
def data_generator(n: int):
    """Generate n items."""
    for i in range(n):
        yield i * 2

# Using imported modules
def main():
    processor = DataProcessor({"debug": True})
    user = User(1, "John", "[email protected]")
    
    # Function calls
    data = fetch_data("https://api.example.com")
    processed = processor.process(["a", "b", "c"])
    
    # Method calls
    display_name = user.get_display_name()
    is_valid = DataProcessor.validate(data)
    
    print(f"User: {display_name}")
    print(f"Valid: {is_valid}")

if __name__ == "__main__":
    main()
```

--------------------------------------------------------------------------------
/test_samples/test.go:
--------------------------------------------------------------------------------

```go
// Go test file
package main

import (
    "fmt"
    "net/http"
    "encoding/json"
    db "database/sql"
)

// Struct definition
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// Interface definition
type Service interface {
    GetUser(id int) (*User, error)
    CreateUser(user User) error
    DeleteUser(id int) error
}

// Type alias
type UserID int

// Constants
const (
    MaxUsers = 100
    DefaultTimeout = 30
)

// Variables
var (
    userCache map[int]*User
    logger    *Logger
)

// Struct with methods
type UserService struct {
    db     *db.DB
    cache  map[int]*User
}

// Method with pointer receiver
func (s *UserService) GetUser(id int) (*User, error) {
    if user, ok := s.cache[id]; ok {
        return user, nil
    }
    
    user := &User{}
    err := s.db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name, &user.Email)
    if err != nil {
        return nil, err
    }
    
    s.cache[id] = user
    return user, nil
}

// Method with value receiver
func (s UserService) String() string {
    return fmt.Sprintf("UserService with %d cached users", len(s.cache))
}

// Function
func NewUserService(database *db.DB) *UserService {
    return &UserService{
        db:    database,
        cache: make(map[int]*User),
    }
}

// Generic-like function using interface{}
func ProcessData(data interface{}) error {
    switch v := data.(type) {
    case *User:
        return processUser(v)
    case []User:
        return processUsers(v)
    default:
        return fmt.Errorf("unsupported type: %T", v)
    }
}

func processUser(user *User) error {
    // Process single user
    logger.Log("Processing user: " + user.Name)
    return nil
}

func processUsers(users []User) error {
    for _, user := range users {
        if err := processUser(&user); err != nil {
            return err
        }
    }
    return nil
}

// HTTP handler
func userHandler(w http.ResponseWriter, r *http.Request) {
    service := NewUserService(nil)
    user, err := service.GetUser(1)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    json.NewEncoder(w).Encode(user)
}

// Logger type
type Logger struct {
    prefix string
}

func (l *Logger) Log(message string) {
    fmt.Printf("%s: %s\n", l.prefix, message)
}

func main() {
    logger = &Logger{prefix: "APP"}
    userCache = make(map[int]*User)
    
    http.HandleFunc("/user", userHandler)
    http.ListenAndServe(":8080", nil)
}
```

--------------------------------------------------------------------------------
/src/xray/lsp_config.json:
--------------------------------------------------------------------------------

```json
{
  "languages": [
    {
      "id": "python",
      "name": "Python",
      "file_extensions": [".py", ".pyw"],
      "server_executable": "pyright-langserver",
      "server_args": ["--stdio"],
      "install_check_command": ["npm", "--version"],
      "install_check_name": "npm",
      "install_command": ["npm", "install", "-g", "pyright"],
      "install_prompt": "To analyze Python files, XRAY needs to install the 'Pyright' language server from Microsoft. This is a one-time setup.",
      "prerequisite_error": "npm is not installed. Please install Node.js (which includes npm) to enable Python analysis support.",
      "installation_docs": "https://nodejs.org/",
      "capabilities": {
        "documentSymbol": true,
        "references": true,
        "definition": true,
        "typeDefinition": true
      }
    },
    {
      "id": "javascript",
      "name": "JavaScript",
      "file_extensions": [".js", ".jsx", ".mjs", ".cjs"],
      "server_executable": "typescript-language-server",
      "server_args": ["--stdio"],
      "install_check_command": ["npm", "--version"],
      "install_check_name": "npm",
      "install_command": ["npm", "install", "-g", "typescript", "typescript-language-server"],
      "install_prompt": "To analyze JavaScript files, XRAY needs to install the TypeScript Language Server. This is a one-time setup.",
      "prerequisite_error": "npm is not installed. Please install Node.js (which includes npm) to enable JavaScript analysis support.",
      "installation_docs": "https://nodejs.org/",
      "capabilities": {
        "documentSymbol": true,
        "references": true,
        "definition": true,
        "typeDefinition": true
      }
    },
    {
      "id": "typescript",
      "name": "TypeScript",
      "file_extensions": [".ts", ".tsx", ".mts", ".cts"],
      "server_executable": "typescript-language-server",
      "server_args": ["--stdio"],
      "install_check_command": ["npm", "--version"],
      "install_check_name": "npm",
      "install_command": ["npm", "install", "-g", "typescript", "typescript-language-server"],
      "install_prompt": "To analyze TypeScript files, XRAY needs to install the TypeScript Language Server. This is a one-time setup.",
      "prerequisite_error": "npm is not installed. Please install Node.js (which includes npm) to enable TypeScript analysis support.",
      "installation_docs": "https://nodejs.org/",
      "capabilities": {
        "documentSymbol": true,
        "references": true,
        "definition": true,
        "typeDefinition": true
      }
    },
    {
      "id": "go",
      "name": "Go",
      "file_extensions": [".go"],
      "server_executable": "gopls",
      "server_args": [],
      "install_check_command": ["go", "version"],
      "install_check_name": "go",
      "install_command": ["go", "install", "golang.org/x/tools/gopls@latest"],
      "install_prompt": "To analyze Go files, XRAY needs to install 'gopls', the official Go language server. This is a one-time setup.",
      "prerequisite_error": "go is not installed. Please install Go to enable Go analysis support.",
      "installation_docs": "https://golang.org/doc/install",
      "capabilities": {
        "documentSymbol": true,
        "references": true,
        "definition": true,
        "typeDefinition": true
      }
    }
  ],
  "lsp_settings": {
    "connection_timeout": 10,
    "request_timeout": 5,
    "max_retries": 3,
    "health_check_interval": 30,
    "workspace_capabilities": {
      "applyEdit": true,
      "workspaceEdit": {
        "documentChanges": true
      },
      "didChangeConfiguration": {
        "dynamicRegistration": true
      },
      "didChangeWatchedFiles": {
        "dynamicRegistration": true
      }
    }
  },
  "server_settings": {
    "initialization_timeout": 60,
    "request_timeout": 30,
    "pyright_initialization_timeout": 120
  }
}
```

--------------------------------------------------------------------------------
/mcp-config-generator.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python3
"""
XRAY MCP Configuration Generator
Generates MCP config for different tools and installation methods.
"""

import json
import sys
import os
from pathlib import Path

CONFIGS = {
    "cursor": {
        "local_python": {
            "mcpServers": {
                "xray": {
                    "command": "python",
                    "args": ["-m", "xray.mcp_server"]
                }
            }
        },
        "docker": {
            "mcpServers": {
                "xray": {
                    "command": "docker", 
                    "args": ["run", "--rm", "-i", "xray"]
                }
            }
        },
        "source": {
            "mcpServers": {
                "xray": {
                    "command": "python",
                    "args": ["run_server.py"],
                    "cwd": str(Path.cwd())
                }
            }
        },
        "installed_script": {
            "mcpServers": {
                "xray": {
                    "command": "xray-mcp"
                }
            }
        }
    },
    "claude": {
        "local_python": {
            "mcpServers": {
                "xray": {
                    "command": "python",
                    "args": ["-m", "xray.mcp_server"]
                }
            }
        },
        "docker": {
            "mcpServers": {
                "xray": {
                    "command": "docker",
                    "args": ["run", "--rm", "-i", "xray"]
                }
            }
        }
    },
    "vscode": {
        "local_python": {
            "mcp": {
                "servers": {
                    "xray": {
                        "type": "stdio",
                        "command": "python",
                        "args": ["-m", "xray.mcp_server"]
                    }
                }
            }
        },
        "docker": {
            "mcp": {
                "servers": {
                    "xray": {
                        "type": "stdio",
                        "command": "docker", 
                        "args": ["run", "--rm", "-i", "xray"]
                    }
                }
            }
        },
        "installed_script": {
            "mcp": {
                "servers": {
                    "xray": {
                        "type": "stdio",
                        "command": "xray-mcp"
                    }
                }
            }
        }
    }
}

def print_config(tool, method):
    """Print MCP configuration for specified tool and method."""
    if tool not in CONFIGS:
        print(f"❌ Unknown tool: {tool}")
        print(f"Available tools: {', '.join(CONFIGS.keys())}")
        return False
    
    if method not in CONFIGS[tool]:
        print(f"❌ Unknown method: {method}")
        print(f"Available methods for {tool}: {', '.join(CONFIGS[tool].keys())}")
        return False
    
    config = CONFIGS[tool][method]
    print(f"🔧 {tool.title()} configuration ({method.replace('_', ' ')}):")
    print()
    print(json.dumps(config, indent=2))
    print()
    
    # Add helpful instructions
    if tool == "cursor":
        print("📝 Add this to your Cursor ~/.cursor/mcp.json file")
    elif tool == "claude":
        print("📝 Add this to your Claude desktop config:")
        print("   macOS: ~/Library/Application Support/Claude/claude_desktop_config.json")
        print("   Windows: %APPDATA%\\Claude\\claude_desktop_config.json")
    elif tool == "vscode":
        print("📝 Add this to your VS Code settings.json file")
    
    return True

def main():
    if len(sys.argv) != 3:
        print("XRAY MCP Configuration Generator")
        print()
        print("Usage: python mcp-config-generator.py <tool> <method>")
        print()
        print("Available tools:")
        for tool in CONFIGS:
            methods = ", ".join(CONFIGS[tool].keys())
            print(f"  {tool}: {methods}")
        print()
        print("Examples:")
        print("  python mcp-config-generator.py cursor local_python")
        print("  python mcp-config-generator.py claude docker")
        print("  python mcp-config-generator.py vscode source")
        return 1
    
    tool = sys.argv[1].lower()
    method = sys.argv[2].lower()
    
    if print_config(tool, method):
        return 0
    else:
        return 1

if __name__ == "__main__":
    sys.exit(main())
```

--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# XRAY MCP Server Installation Script (uv version)
# Usage: curl -fsSL https://raw.githubusercontent.com/srijanshukla18/xray/main/install.sh | bash

set -e

# Check if XRAY is already installed and on the PATH
if command -v xray-mcp &>/dev/null; then
    echo -e "${GREEN}✓${NC} XRAY is already installed."
    # Optionally, ask to reinstall
    read -p "Do you want to reinstall? (y/N) " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        exit 0
    fi
fi

echo "🚀 Installing XRAY MCP Server with uv..."

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Check if Python 3.10+ is available
if command -v python3.11 &>/dev/null; then
    PYTHON_CMD="python3.11"
elif command -v python3 &>/dev/null; then
    PYTHON_CMD="python3"
else
    echo -e "${RED}❌${NC} Python 3 is not installed."
    exit 1
fi

PYTHON_VERSION=$($PYTHON_CMD -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
PYTHON_MAJOR=$(echo $PYTHON_VERSION | cut -d. -f1)
PYTHON_MINOR=$(echo $PYTHON_VERSION | cut -d. -f2)

if [ "$PYTHON_MAJOR" -eq 3 ] && [ "$PYTHON_MINOR" -ge 10 ]; then
    echo -e "${GREEN}✓${NC} Found Python $PYTHON_VERSION"
else
    echo -e "${RED}❌${NC} Python $PYTHON_VERSION found, but 3.10+ is required"
    exit 1
fi

# Check if uv is installed
if ! command -v uv &> /dev/null; then
    echo -e "${YELLOW}📦${NC} Installing uv..."
    
    # Detect OS
    if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then
        echo "Please install uv on Windows using:"
        echo "  powershell -c \"irm https://astral.sh/uv/install.ps1 | iex\""
        exit 1
    else
        # macOS and Linux
        curl -LsSf https://astral.sh/uv/install.sh | sh
        
        # Add to PATH for current session
        export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
        
        # Verify installation
        if command -v uv &> /dev/null; then
            echo -e "${GREEN}✓${NC} uv installed successfully"
        else
            echo -e "${RED}❌${NC} Failed to install uv"
            exit 1
        fi
    fi
else
    echo -e "${GREEN}✓${NC} uv is already installed"
fi

# Determine installation directory
if git rev-parse --is-inside-work-tree &> /dev/null; then
    INSTALL_DIR=$(pwd)
    echo -e "${GREEN}✓${NC} Installing from current Git repository: $INSTALL_DIR"
    SKIP_CLONE=true
else
    INSTALL_DIR="$HOME/.xray"
    echo -e "${YELLOW}📦${NC} Installing to default directory: $INSTALL_DIR"
    SKIP_CLONE=false
fi
mkdir -p "$INSTALL_DIR"

# Clone or update XRAY (only if not installing from current repo)
if [ "$SKIP_CLONE" = false ]; then
    echo -e "${YELLOW}📥${NC} Downloading XRAY..."
    if [ -d "$INSTALL_DIR/.git" ]; then
        cd "$INSTALL_DIR"
        echo -e "${YELLOW}🔄${NC} Updating existing installation..."
        if ! git pull origin main; then
            echo -e "${YELLOW}⚠${NC} Git pull failed. Performing clean installation..."
            cd "$HOME"
            rm -rf "$INSTALL_DIR"
            git clone https://github.com/srijanshukla18/xray.git "$INSTALL_DIR"
            cd "$INSTALL_DIR"
        fi
    elif [ -d "$INSTALL_DIR" ]; then
        echo -e "${YELLOW}⚠${NC} Directory exists but is not a git repository. Cleaning up..."
        rm -rf "$INSTALL_DIR"
        git clone https://github.com/srijanshukla18/xray.git "$INSTALL_DIR"
        cd "$INSTALL_DIR"
    else
        git clone https://github.com/srijanshukla18/xray.git "$INSTALL_DIR"
        cd "$INSTALL_DIR"
    fi
else
    # If installing from current repo, just change to it for uv tool install
    cd "$INSTALL_DIR"
    # Clean uv cache to ensure local changes are picked up
    echo -e "${YELLOW}🧹${NC} Cleaning uv cache..."
    uv clean
fi

# Install XRAY as a uv tool
echo -e "${YELLOW}🔧${NC} Installing XRAY with uv..."
uv tool install . --force

# Add uv's bin directory to PATH for future sessions
uv tool update-shell

# Ensure the current shell can find the freshly installed binary
export PATH="$HOME/.local/bin:$PATH"

# Verify installation
if command -v xray-mcp &> /dev/null; then
    echo -e "${GREEN}✓${NC} XRAY installed successfully!"
else
    echo -e "${RED}❌${NC} Installation failed"
    exit 1
fi

# Run verification test
echo -e "${YELLOW}🧪${NC} Running installation test..."
cd "$INSTALL_DIR"
if $PYTHON_CMD test_installation.py; then
    echo -e "${GREEN}✓${NC} All tests passed!"
else
    echo -e "${YELLOW}⚠${NC} Some tests failed, but installation completed"
fi

# Show next steps
echo ""
echo -e "${GREEN}✅ XRAY installed successfully!${NC}"
echo ""
echo "🎯 Quick Start:"
echo "1. Add this to your MCP config:"
echo '   {"mcpServers": {"xray": {"command": "xray-mcp"}}}'
echo ""
echo "2. Use in prompts:"
echo '   "Analyze this codebase for dependencies. use XRAY tools"'
echo ""
echo "📚 Full documentation:"
echo "   https://github.com/srijanshukla18/xray"
echo ""
echo "💡 Tip: You can also run XRAY without installation using:"
echo "   uvx --from $INSTALL_DIR xray-mcp"

```

--------------------------------------------------------------------------------
/getting_started.md:
--------------------------------------------------------------------------------

```markdown
# Getting Started with XRAY - Modern Installation with uv

XRAY is a minimal-dependency code intelligence system that enhances AI assistants' understanding of codebases. This guide shows how to install and use XRAY with the modern `uv` package manager.

## Prerequisites

- Python 3.10 or later
- [uv](https://docs.astral.sh/uv/) - Fast Python package manager

### Installing uv

```bash
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"

# Or with pip
pip install uv
```

## Installation Options

### Option 1: Automated Install (Easiest)

For the quickest setup, use the one-line installer from the `README.md`. This will handle everything for you.

```bash
curl -fsSL https://raw.githubusercontent.com/srijanshukla18/xray/main/install.sh | bash
```

### Option 2: Quick Try with uvx (Recommended for Testing)

Run XRAY directly without installation using `uvx`:

```bash
# Clone the repository
git clone https://github.com/srijanshukla18/xray.git
cd xray

# Run XRAY directly with uvx
uvx --from . xray-mcp
```

### Option 3: Install as a Tool (Recommended for Regular Use)

Install XRAY as a persistent tool:

```bash
# Clone and install
git clone https://github.com/srijanshukla18/xray.git
cd xray

# Install with uv
uv tool install .

# Now you can run xray-mcp from anywhere
xray-mcp
```

### Option 4: Development Installation

For contributing or modifying XRAY:

```bash
# Clone the repository
git clone https://github.com/srijanshukla18/xray.git
cd xray

# Create and activate virtual environment with uv
uv venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install in editable mode
uv pip install -e .

# Run the server
python -m xray.mcp_server
```

## Configure Your AI Assistant

After installation, configure your AI assistant to use XRAY:

### Using the MCP Config Generator (Recommended)

For easier configuration, use the `mcp-config-generator.py` script located in the XRAY repository. This script can generate the correct JSON configuration for various AI assistants and installation methods.

To use it:

1.  Navigate to the XRAY repository root:
    ```bash
    cd /path/to/xray
    ```
2.  Run the script with your desired tool and installation method. For example, to get the configuration for Claude Desktop with an installed `xray-mcp` script:
    ```bash
    python mcp-config-generator.py claude installed_script
    ```
    Or for VS Code with a local Python installation:
    ```bash
    python mcp-config-generator.py vscode local_python
    ```
    The script will print the JSON configuration and instructions on where to add it.

    Available tools: `cursor`, `claude`, `vscode`
    Available methods: `local_python`, `docker`, `source`, `installed_script` (method availability varies by tool)

### Manual Configuration (Advanced)

If you prefer to configure manually, here are examples for common AI assistants:

#### Claude CLI (Claude Code)

For Claude CLI users, simply run:

```bash
claude mcp add xray xray-mcp -s local
```

Then verify it's connected:

```bash
claude mcp list | grep xray
```

#### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):

```json
{
  "mcpServers": {
    "xray": {
      "command": "uvx",
      "args": ["--from", "/path/to/xray", "xray-mcp"]
    }
  }
}
```

Or if installed as a tool:

```json
{
  "mcpServers": {
    "xray": {
      "command": "xray-mcp"
    }
  }
}
```

#### Cursor

Settings → Cursor Settings → MCP → Add new global MCP server:

```json
{
  "mcpServers": {
    "xray": {
      "command": "xray-mcp"
    }
  }
}
```

## Minimal Dependencies

One of XRAY's best features is its minimal dependency profile. You don't need to install a suite of language servers. XRAY uses:

- **ast-grep**: A single, fast binary for structural code analysis.
- **Python**: For the server and core logic.

This means you can start using XRAY immediately after installation with no complex setup!

## Verify Installation

### 1. Check XRAY is accessible

```bash
# If installed as tool
xray-mcp --version

# If using uvx
uvx --from /path/to/xray xray-mcp --version
```

### 2. Test basic functionality

Create a test file `test_xray.py`:

```python
def hello_world():
    print("Hello from XRAY test!")

def calculate_sum(a, b):
    return a + b

class Calculator:
    def multiply(self, x, y):
        return x * y
```

### 3. In your AI assistant, test these commands:

```
Build the index for the current directory. use XRAY tools
```

Expected: Success message with files indexed

```
Find all functions containing "hello". use XRAY tools
```

Expected: Should find `hello_world` function

```
What would break if I change the multiply method? use XRAY tools
```

Expected: Impact analysis showing any dependencies

## Usage Examples

Once configured, use XRAY by adding "use XRAY tools" to your prompts:

```
# Index a codebase
"Index the src/ directory for analysis. use XRAY tools"

# Find symbols
"Find all classes that contain 'User' in their name. use XRAY tools"

# Impact analysis
"What breaks if I change the authenticate method in UserService? use XRAY tools"

# Dependency tracking
"What does the PaymentProcessor class depend on? use XRAY tools"

# Location queries
"What function is defined at line 125 in main.py? use XRAY tools"
```

## Troubleshooting

### uv not found

Make sure uv is in your PATH:

```bash
# Add to ~/.bashrc or ~/.zshrc
export PATH="$HOME/.cargo/bin:$PATH"
```

### Permission denied

On macOS/Linux, you might need to make the script executable:

```bash
chmod +x ~/.local/bin/xray-mcp
```

### Python version issues

XRAY requires Python 3.10+. Check your version:

```bash
python --version

# If needed, install Python 3.10+ with uv
uv python install 3.10
```

### MCP connection issues

1. Check XRAY is running: `xray-mcp --test`
2. Verify your MCP config JSON is valid
3. Restart your AI assistant after config changes

## Advanced Configuration

### Custom Database Location

Set the `XRAY_DB_PATH` environment variable:

```bash
export XRAY_DB_PATH="$HOME/.xray/databases"
```

### Debug Mode

Enable debug logging:

```bash
export XRAY_DEBUG=1
```

## What's Next?

1. **Index your first repository**: In your AI assistant, ask it to "Build the index for my project. use XRAY tools"

2. **Explore the tools**:
   - `build_index` - Visual file tree of your repository
   - `find_symbol` - Fuzzy search for functions, classes, and methods
   - `what_breaks` - Find what code depends on a symbol (reverse dependencies)
   - `what_depends` - Find what a symbol depends on (calls and imports)
   
   Note: Results may include matches from comments or strings. The AI assistant will intelligently filter based on context.

3. **Read the documentation**: Check out the [README](README.md) for detailed examples and API reference

## Why XRAY Uses a Minimal Dependency Approach

XRAY is designed for simplicity and ease of use. It relies on:

- **ast-grep**: A powerful and fast single-binary tool for code analysis.
- **Python**: For its robust standard library and ease of scripting.

This approach avoids the complexity of setting up and managing multiple language servers, while still providing accurate, structural code intelligence.

## Benefits of Using uv

- **10-100x faster** than pip for installations
- **No virtual environment hassles** - uv manages everything
- **Reproducible installs** - uv.lock ensures consistency
- **Built-in Python management** - install any Python version
- **Global tool management** - like pipx but faster

Happy coding with XRAY! 🚀
```

--------------------------------------------------------------------------------
/src/xray/mcp_server.py:
--------------------------------------------------------------------------------

```python
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))

"""XRAY MCP Server - Progressive code discovery in 3 steps: Map, Find, Impact.

🚀 THE XRAY WORKFLOW (Progressive Discovery):
1. explore_repo() - Start with directory structure, then zoom in with symbols
2. find_symbol() - Find specific functions/classes you need to analyze  
3. what_breaks() - See where that symbol is used (impact analysis)

PROGRESSIVE DISCOVERY EXAMPLE:
```python
# Step 1a: Get the lay of the land (directories only)
tree = explore_repo("/Users/john/myproject")
# Shows directory structure - fast and clean

# Step 1b: Zoom into interesting areas with symbols
tree = explore_repo("/Users/john/myproject", focus_dirs=["src"], include_symbols=True)
# Now shows function signatures and docstrings in src/

# Step 2: Find the specific function you need
symbols = find_symbol("/Users/john/myproject", "validate user")
# Returns list of matching symbols with exact locations

# Step 3: See what would be affected if you change it
impact = what_breaks(symbols[0])  # Pass the ENTIRE symbol object!
# Shows every place that symbol name appears
```

KEY FEATURES:
- Progressive Discovery: Start simple (dirs only), then add detail where needed
- Smart Caching: Symbol extraction cached per git commit for instant re-runs
- Focus Control: Use focus_dirs to examine specific parts of large codebases

TIPS:
- Always use ABSOLUTE paths (e.g., "/Users/john/project"), not relative paths
- Start explore_repo with include_symbols=False to avoid information overload
- find_symbol uses fuzzy matching - "auth" finds "authenticate", "authorization", etc.
- what_breaks does text search - review results to see which are actual code references
"""

import os
from typing import Dict, List, Any, Optional, Union

from fastmcp import FastMCP

from xray.core.indexer import XRayIndexer

# Initialize FastMCP server
mcp = FastMCP("XRAY Code Intelligence")

# Cache for indexer instances per repository path
_indexer_cache: Dict[str, XRayIndexer] = {}


def normalize_path(path: str) -> str:
    """Normalize a path to absolute form."""
    path = os.path.expanduser(path)
    path = os.path.abspath(path)
    path = str(Path(path).resolve())
    if not os.path.exists(path):
        raise ValueError(f"Path '{path}' does not exist")
    if not os.path.isdir(path):
        raise ValueError(f"Path '{path}' is not a directory")
    return path


def get_indexer(path: str) -> XRayIndexer:
    """Get or create indexer instance for the given path."""
    path = normalize_path(path)
    if path not in _indexer_cache:
        _indexer_cache[path] = XRayIndexer(path)
    return _indexer_cache[path]


@mcp.tool
def explore_repo(
    root_path: str, 
    max_depth: Optional[Union[int, str]] = None,
    include_symbols: Union[bool, str] = False,
    focus_dirs: Optional[List[str]] = None,
    max_symbols_per_file: Union[int, str] = 5
) -> str:
    """
    🗺️ STEP 1: Map the codebase structure - start simple, then zoom in!
    
    PROGRESSIVE DISCOVERY WORKFLOW:
    1. First call: explore_repo("/path/to/project") - See directory structure only
    2. Zoom in: explore_repo("/path/to/project", focus_dirs=["src"], include_symbols=True)
    3. Go deeper: explore_repo("/path/to/project", max_depth=3, include_symbols=True)
    
    INPUTS:
    - root_path: The ABSOLUTE path to the project (e.g., "/Users/john/myproject")
                 NOT relative paths like "./myproject" or "~/myproject"
    - max_depth: How deep to traverse directories (None = unlimited, accepts int or string)
    - include_symbols: Show function/class signatures with docs (False = dirs only, accepts bool or string)
    - focus_dirs: List of top-level directories to focus on (e.g., ["src", "lib"])
    - max_symbols_per_file: Max symbols to show per file when include_symbols=True (accepts int or string)
    
    EXAMPLE 1 - Initial exploration (directory only):
    explore_repo("/Users/john/project")
    # Returns:
    # /Users/john/project/
    # ├── src/
    # ├── tests/
    # ├── docs/
    # └── README.md
    
    EXAMPLE 2 - Zoom into src/ with symbols:
    explore_repo("/Users/john/project", focus_dirs=["src"], include_symbols=True)
    # Returns:
    # /Users/john/project/
    # └── src/
    #     ├── auth.py
    #     │   ├── class AuthService: # Handles user authentication
    #     │   ├── def authenticate(username, password): # Validates credentials
    #     │   └── def logout(session_id): # Ends user session
    #     └── models.py
    #         ├── class User(BaseModel): # User account model
    #         └── ... and 3 more
    
    EXAMPLE 3 - Limited depth exploration:
    explore_repo("/Users/john/project", max_depth=1, include_symbols=True)
    # Shows only top-level dirs and files with their symbols
    
    💡 PRO TIP: Start with include_symbols=False to see structure, then set it to True
    for areas you want to examine in detail. This prevents information overload!
    
    ⚡ PERFORMANCE: Symbol extraction is cached per git commit - subsequent calls are instant!
    
    WHAT TO DO NEXT:
    - If you found interesting directories, zoom in with focus_dirs
    - If you see relevant files, use find_symbol() to locate specific functions
    """
    try:
        # Convert string inputs to proper types (for LLMs that pass strings)
        if max_depth is not None and isinstance(max_depth, str):
            max_depth = int(max_depth)
        if isinstance(max_symbols_per_file, str):
            max_symbols_per_file = int(max_symbols_per_file)
        if isinstance(include_symbols, str):
            include_symbols = include_symbols.lower() in ('true', '1', 'yes')
            
        indexer = get_indexer(root_path)
        tree = indexer.explore_repo(
            max_depth=max_depth,
            include_symbols=include_symbols,
            focus_dirs=focus_dirs,
            max_symbols_per_file=max_symbols_per_file
        )
        return tree
    except Exception as e:
        return f"Error exploring repository: {str(e)}"


@mcp.tool
def find_symbol(root_path: str, query: str) -> List[Dict[str, Any]]:
    """
    🔍 STEP 2: Find specific functions, classes, or methods in the codebase.
    
    USE THIS AFTER explore_repo() when you need to locate a specific piece of code.
    Uses fuzzy matching - you don't need the exact name!
    
    INPUTS:
    - root_path: Same ABSOLUTE path used in explore_repo
    - query: What you're looking for (fuzzy search works!)
             Examples: "auth", "user service", "validate", "parseJSON"
    
    EXAMPLE INPUTS:
    find_symbol("/Users/john/awesome-project", "authenticate")
    find_symbol("/Users/john/awesome-project", "user model")  # Fuzzy matches "UserModel"
    
    EXAMPLE OUTPUT:
    [
        {
            "name": "authenticate_user",
            "type": "function",
            "path": "/Users/john/awesome-project/src/auth.py",
            "start_line": 45,
            "end_line": 67
        },
        {
            "name": "AuthService",
            "type": "class", 
            "path": "/Users/john/awesome-project/src/services.py",
            "start_line": 12,
            "end_line": 89
        }
    ]
    
    RETURNS:
    List of symbol objects (dictionaries). Save these objects - you'll pass them to what_breaks()!
    Empty list if no matches found.
    
    WHAT TO DO NEXT:
    Pick a symbol from the results and pass THE ENTIRE SYMBOL OBJECT to what_breaks() 
    to see where it's used in the codebase.
    """
    try:
        indexer = get_indexer(root_path)
        results = indexer.find_symbol(query)
        return results
    except Exception as e:
        return [{"error": f"Error finding symbol: {str(e)}"}]


@mcp.tool  
def what_breaks(exact_symbol: Dict[str, Any]) -> Dict[str, Any]:
    """
    💥 STEP 3: See what code might break if you change this symbol.
    
    USE THIS AFTER find_symbol() to understand the impact of changing a function/class.
    Shows you every place in the codebase where this symbol name appears.
    
    INPUT:
    - exact_symbol: Pass THE ENTIRE SYMBOL OBJECT from find_symbol(), not just the name!
                   Must be a dictionary with AT LEAST 'name' and 'path' keys.
    
    EXAMPLE INPUT:
    # First, get a symbol from find_symbol():
    symbols = find_symbol("/Users/john/project", "authenticate")
    symbol = symbols[0]  # Pick the first result
    
    # Then pass THE WHOLE SYMBOL OBJECT:
    what_breaks(symbol)
    # or directly:
    what_breaks({
        "name": "authenticate_user",
        "type": "function",
        "path": "/Users/john/project/src/auth.py",
        "start_line": 45,
        "end_line": 67
    })
    
    EXAMPLE OUTPUT:
    {
        "references": [
            {
                "file": "/Users/john/project/src/api.py",
                "line": 23,
                "text": "    user = authenticate_user(username, password)"
            },
            {
                "file": "/Users/john/project/tests/test_auth.py", 
                "line": 45,
                "text": "def test_authenticate_user():"
            }
        ],
        "total_count": 2,
        "note": "Found 2 potential references based on a text search for the name 'authenticate_user'. This may include comments, strings, or other unrelated symbols."
    }
    
    ⚠️ IMPORTANT: This does a text search for the name, so it might find:
    - Actual function calls (what you want!)
    - Comments mentioning the function
    - Other functions/variables with the same name
    - Strings containing the name
    
    Review each reference to determine if it's actually affected.
    """
    try:
        # Extract root path from the symbol's path
        symbol_path = Path(exact_symbol['path'])
        root_path = str(symbol_path.parent)
        
        # Find a suitable root (go up until we find a git repo or reach root)
        while root_path != '/':
            if (Path(root_path) / '.git').exists():
                break
            parent = Path(root_path).parent
            if parent == Path(root_path):
                break
            root_path = str(parent)
        
        indexer = get_indexer(root_path)
        return indexer.what_breaks(exact_symbol)
    except Exception as e:
        return {"error": f"Error finding references: {str(e)}"}


def main():
    """Main entry point for the XRAY MCP server."""
    mcp.run()


if __name__ == "__main__":
    main()
```

--------------------------------------------------------------------------------
/src/xray/core/indexer.py:
--------------------------------------------------------------------------------

```python
"""Core indexing engine for XRAY - ast-grep based implementation."""

import os
import re
import ast
import json
import subprocess
import hashlib
import pickle
from pathlib import Path
from typing import Dict, List, Optional, Any, Set, Tuple
import fnmatch
from thefuzz import fuzz

# Default exclusions
DEFAULT_EXCLUSIONS = {
    # Directories
    "node_modules", "vendor", "__pycache__", "venv", ".venv", "env",
    "target", "build", "dist", ".git", ".svn", ".hg", ".idea", ".vscode",
    ".xray", "site-packages", ".tox", ".pytest_cache", ".mypy_cache",
    
    # File patterns
    "*.pyc", "*.pyo", "*.pyd", "*.so", "*.dll", "*.log", 
    ".DS_Store", "Thumbs.db", "*.swp", "*.swo", "*~"
}

# Language extensions
LANGUAGE_MAP = {
    ".py": "python",
    ".js": "javascript", 
    ".jsx": "javascript",
    ".mjs": "javascript",
    ".ts": "typescript",
    ".tsx": "typescript",
    ".go": "go",
}


class XRayIndexer:
    """Main indexer for XRAY - provides file tree and symbol extraction using ast-grep."""
    
    def __init__(self, root_path: str):
        self.root_path = Path(root_path).resolve()
        self._cache = {}
        self._init_cache()
    
    def _init_cache(self):
        """Initialize cache based on git commit SHA."""
        try:
            # Get current git commit SHA
            result = subprocess.run(
                ["git", "rev-parse", "HEAD"],
                cwd=self.root_path,
                capture_output=True,
                text=True
            )
            if result.returncode == 0:
                self.commit_sha = result.stdout.strip()
                self.cache_dir = Path(f"/tmp/.xray_cache/{self.commit_sha}")
                self.cache_dir.mkdir(parents=True, exist_ok=True)
                self._load_cache()
            else:
                self.commit_sha = None
                self.cache_dir = None
        except:
            self.commit_sha = None
            self.cache_dir = None
    
    def _load_cache(self):
        """Load cache from disk if available."""
        if not self.cache_dir:
            return
        
        cache_file = self.cache_dir / "symbols.pkl"
        if cache_file.exists():
            try:
                with open(cache_file, 'rb') as f:
                    self._cache = pickle.load(f)
            except:
                self._cache = {}
    
    def _save_cache(self):
        """Save cache to disk."""
        if not self.cache_dir:
            return
        
        cache_file = self.cache_dir / "symbols.pkl"
        try:
            with open(cache_file, 'wb') as f:
                pickle.dump(self._cache, f)
        except:
            pass
    
    def _get_cache_key(self, file_path: Path) -> str:
        """Generate cache key for a file."""
        try:
            stat = file_path.stat()
            return f"{file_path}:{stat.st_mtime}:{stat.st_size}"
        except:
            return str(file_path)
    
    def explore_repo(
        self, 
        max_depth: Optional[int] = None,
        include_symbols: bool = False,
        focus_dirs: Optional[List[str]] = None,
        max_symbols_per_file: int = 5
    ) -> str:
        """
        Build a visual file tree with optional symbol skeletons.
        
        Args:
            max_depth: Limit directory traversal depth
            include_symbols: Include symbol skeletons in output
            focus_dirs: Only include these top-level directories
            max_symbols_per_file: Max symbols to show per file
            
        Returns:
            Formatted tree string
        """
        # Get gitignore patterns if available
        gitignore_patterns = self._parse_gitignore()
        
        # Build the tree
        tree_lines = []
        self._build_tree_recursive_enhanced(
            self.root_path, 
            tree_lines, 
            "", 
            gitignore_patterns,
            current_depth=0,
            max_depth=max_depth,
            include_symbols=include_symbols,
            focus_dirs=focus_dirs,
            max_symbols_per_file=max_symbols_per_file,
            is_last=True
        )
        
        # Save cache after building tree
        if include_symbols:
            self._save_cache()
        
        return "\n".join(tree_lines)
    
    def _parse_gitignore(self) -> Set[str]:
        """Parse .gitignore file if it exists."""
        patterns = set()
        gitignore_path = self.root_path / ".gitignore"
        
        if gitignore_path.exists():
            try:
                with open(gitignore_path, 'r', encoding='utf-8') as f:
                    for line in f:
                        line = line.strip()
                        if line and not line.startswith('#'):
                            patterns.add(line)
            except Exception:
                pass
        
        return patterns
    
    def _should_exclude(self, path: Path, gitignore_patterns: Set[str]) -> bool:
        """Check if a path should be excluded."""
        name = path.name
        
        # Check default exclusions
        if name in DEFAULT_EXCLUSIONS:
            return True
        
        # Check file pattern exclusions
        for pattern in DEFAULT_EXCLUSIONS:
            if '*' in pattern and fnmatch.fnmatch(name, pattern):
                return True
        
        # Check gitignore patterns (simplified)
        for pattern in gitignore_patterns:
            if pattern in str(path.relative_to(self.root_path)):
                return True
            if fnmatch.fnmatch(name, pattern):
                return True
        
        return False
    
    def _should_include_dir(self, path: Path, focus_dirs: Optional[List[str]], current_depth: int) -> bool:
        """Check if a directory should be included based on focus_dirs."""
        if not focus_dirs or current_depth > 0:
            return True
        
        # At depth 0 (top-level), only include if in focus_dirs
        return path.name in focus_dirs
    
    def _build_tree_recursive_enhanced(
        self, 
        path: Path, 
        tree_lines: List[str], 
        prefix: str, 
        gitignore_patterns: Set[str],
        current_depth: int,
        max_depth: Optional[int],
        include_symbols: bool,
        focus_dirs: Optional[List[str]],
        max_symbols_per_file: int,
        is_last: bool = False
    ):
        """Recursively build the tree representation with enhanced features."""
        if self._should_exclude(path, gitignore_patterns):
            return
        
        # Check depth limit
        if max_depth is not None and current_depth > max_depth:
            return
        
        # Check focus_dirs for directories
        if path.is_dir() and not self._should_include_dir(path, focus_dirs, current_depth):
            return
        
        # Add current item
        name = path.name if path != self.root_path else str(path)
        connector = "└── " if is_last else "├── "
        
        # For files, add skeleton if requested
        if path.is_file() and include_symbols and path.suffix.lower() in LANGUAGE_MAP:
            skeleton = self._get_file_skeleton_enhanced(path, max_symbols_per_file)
            if skeleton:
                # Format with indented skeleton
                if path == self.root_path:
                    tree_lines.append(name)
                else:
                    tree_lines.append(prefix + connector + name)
                
                # Add skeleton lines
                for i, skel_line in enumerate(skeleton):
                    is_last_skel = (i == len(skeleton) - 1)
                    skel_prefix = prefix + ("    " if is_last else "│   ")
                    skel_connector = "└── " if is_last_skel else "├── "
                    tree_lines.append(skel_prefix + skel_connector + skel_line)
            else:
                # No skeleton, just show filename
                if path == self.root_path:
                    tree_lines.append(name)
                else:
                    tree_lines.append(prefix + connector + name)
        else:
            # Directory or file without symbols
            if path == self.root_path:
                tree_lines.append(name)
            else:
                tree_lines.append(prefix + connector + name)
        
        # Only recurse into directories
        if path.is_dir():
            # Get children and sort them
            try:
                children = sorted(path.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower()))
                # Filter out excluded items
                children = [c for c in children if not self._should_exclude(c, gitignore_patterns)]
                
                # Apply focus_dirs filter at top level
                if current_depth == 0 and focus_dirs:
                    children = [c for c in children if c.is_file() or c.name in focus_dirs]
                
                for i, child in enumerate(children):
                    is_last_child = (i == len(children) - 1)
                    extension = "    " if is_last else "│   "
                    new_prefix = prefix + extension if path != self.root_path else ""
                    
                    self._build_tree_recursive_enhanced(
                        child, 
                        tree_lines, 
                        new_prefix, 
                        gitignore_patterns,
                        current_depth + 1,
                        max_depth,
                        include_symbols,
                        focus_dirs,
                        max_symbols_per_file,
                        is_last_child
                    )
            except PermissionError:
                pass
    
    def _get_file_skeleton_enhanced(self, file_path: Path, max_symbols: int) -> List[str]:
        """Extract enhanced symbol info including signatures and docstrings."""
        # Check cache first
        cache_key = self._get_cache_key(file_path)
        if cache_key in self._cache:
            cached_symbols = self._cache[cache_key]
            return self._format_enhanced_skeleton(cached_symbols, max_symbols)
        
        language = LANGUAGE_MAP.get(file_path.suffix.lower())
        if not language:
            return []
        
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            if language == "python":
                symbols = self._extract_python_symbols_enhanced(content)
            else:
                symbols = self._extract_regex_symbols_enhanced(content, language)
            
            # Cache the results
            self._cache[cache_key] = symbols
            
            return self._format_enhanced_skeleton(symbols, max_symbols)
        
        except Exception:
            return []
    
    def _format_enhanced_skeleton(self, symbols: List[Dict[str, str]], max_symbols: int) -> List[str]:
        """Format enhanced symbol info for display."""
        if not symbols:
            return []
        
        lines = []
        shown_count = min(len(symbols), max_symbols)
        
        for symbol in symbols[:shown_count]:
            line = symbol['signature']
            if symbol.get('doc'):
                line += f" # {symbol['doc']}"
            lines.append(line)
        
        if len(symbols) > max_symbols:
            remaining = len(symbols) - max_symbols
            lines.append(f"... and {remaining} more")
        
        return lines
    
    def _extract_python_symbols_enhanced(self, content: str) -> List[Dict[str, str]]:
        """Extract Python symbols with signatures and docstrings."""
        symbols = []
        try:
            tree = ast.parse(content)
            for node in ast.iter_child_nodes(tree):
                if isinstance(node, ast.ClassDef):
                    sig = f"class {node.name}"
                    if node.bases:
                        base_names = []
                        for base in node.bases:
                            if isinstance(base, ast.Name):
                                base_names.append(base.id)
                            elif isinstance(base, ast.Attribute):
                                base_names.append(ast.unparse(base))
                        if base_names:
                            sig += f"({', '.join(base_names)})"
                    sig += ":"
                    
                    doc = ast.get_docstring(node)
                    if doc:
                        doc = doc.split('\n')[0].strip()[:50]
                    
                    symbols.append({'signature': sig, 'doc': doc or ''})
                    
                elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                    # Build function signature
                    sig = "async def " if isinstance(node, ast.AsyncFunctionDef) else "def "
                    sig += f"{node.name}("
                    
                    # Add parameters
                    args = []
                    for arg in node.args.args:
                        args.append(arg.arg)
                    if args:
                        sig += ", ".join(args)
                    sig += "):"
                    
                    doc = ast.get_docstring(node)
                    if doc:
                        doc = doc.split('\n')[0].strip()[:50]
                    
                    symbols.append({'signature': sig, 'doc': doc or ''})
        except:
            pass
        return symbols
    
    def _extract_regex_symbols_enhanced(self, content: str, language: str) -> List[Dict[str, str]]:
        """Extract symbols with signatures and comments for JS/TS/Go."""
        symbols = []
        
        # Language-specific patterns
        if language in ["javascript", "typescript"]:
            patterns = [
                # Function with preceding comment
                (r'(?://\s*(.+?)\n)?^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\((.*?)\)', 
                 lambda m: {'signature': f"function {m.group(2)}({m.group(3)}):", 'doc': (m.group(1) or '').strip()}),
                
                # Class with preceding comment
                (r'(?://\s*(.+?)\n)?^\s*(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?', 
                 lambda m: {'signature': f"class {m.group(2)}" + (f" extends {m.group(3)}" if m.group(3) else "") + ":", 
                           'doc': (m.group(1) or '').strip()}),
                
                # Arrow function with const
                (r'(?://\s*(.+?)\n)?^\s*(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\((.*?)\)\s*=>', 
                 lambda m: {'signature': f"const {m.group(2)} = ({m.group(3)}) =>", 'doc': (m.group(1) or '').strip()}),
            ]
        elif language == "go":
            patterns = [
                # Function with preceding comment
                (r'(?://\s*(.+?)\n)?^func\s+(\w+)\s*\((.*?)\)', 
                 lambda m: {'signature': f"func {m.group(2)}({m.group(3)})", 'doc': (m.group(1) or '').strip()}),
                
                # Method with preceding comment
                (r'(?://\s*(.+?)\n)?^func\s*\((\w+\s+[*]?\w+)\)\s*(\w+)\s*\((.*?)\)', 
                 lambda m: {'signature': f"func ({m.group(2)}) {m.group(3)}({m.group(4)})", 
                           'doc': (m.group(1) or '').strip()}),
                
                # Type struct with preceding comment
                (r'(?://\s*(.+?)\n)?^type\s+(\w+)\s+struct', 
                 lambda m: {'signature': f"type {m.group(2)} struct", 'doc': (m.group(1) or '').strip()}),
            ]
        else:
            return symbols
        
        # Apply patterns
        for pattern, extractor in patterns:
            for match in re.finditer(pattern, content, re.MULTILINE):
                symbols.append(extractor(match))
        
        return symbols
    
    def find_symbol(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
        """
        Find symbols matching the query using fuzzy search.
        Uses ast-grep to find all symbols, then fuzzy matches against the query.
        
        Returns a list of the top matching "Exact Symbol" objects.
        """
        all_symbols = []
        
        # Define patterns for different symbol types
        patterns = [
            # Python functions and classes
            ("def $NAME($$$):", "function"),
            ("class $NAME($$$):", "class"),
            ("async def $NAME($$$):", "function"),
            
            # JavaScript/TypeScript functions and classes
            ("function $NAME($$$)", "function"),
            ("const $NAME = ($$$) =>", "function"),
            ("let $NAME = ($$$) =>", "function"),
            ("var $NAME = ($$$) =>", "function"),
            ("class $NAME", "class"),
            ("interface $NAME", "interface"),
            ("type $NAME =", "type"),
            
            # Go functions and types
            ("func $NAME($$$)", "function"),
            ("func ($$$) $NAME($$$)", "method"),
            ("type $NAME struct", "struct"),
            ("type $NAME interface", "interface"),
        ]
        
        # Run ast-grep for each pattern
        for pattern, symbol_type in patterns:
            cmd = [
                "ast-grep",
                "--pattern", pattern,
                "--json",
                str(self.root_path)
            ]
            
            result = subprocess.run(cmd, capture_output=True, text=True)
            
            if result.returncode == 0:
                try:
                    matches = json.loads(result.stdout)
                    for match in matches:
                        # Extract details from match
                        text = match.get("text", "")
                        file_path = match.get("file", "")
                        start = match.get("range", {}).get("start", {})
                        end = match.get("range", {}).get("end", {})
                        
                        # Extract the name from metavariables
                        metavars = match.get("metaVariables", {})
                        name = None
                        
                        # Try to get NAME from metavariables
                        if "NAME" in metavars:
                            name = metavars["NAME"]["text"]
                        else:
                            # Fallback to regex extraction
                            name = self._extract_symbol_name(text)
                        
                        if name:
                            symbol = {
                                "name": name,
                                "type": symbol_type,
                                "path": file_path,
                                "start_line": start.get("line", 1),
                                "end_line": end.get("line", start.get("line", 1))
                            }
                            all_symbols.append(symbol)
                except json.JSONDecodeError:
                    continue
        
        # Deduplicate symbols (same name and location)
        seen = set()
        unique_symbols = []
        for symbol in all_symbols:
            key = (symbol["name"], symbol["path"], symbol["start_line"])
            if key not in seen:
                seen.add(key)
                unique_symbols.append(symbol)
        
        # Now perform fuzzy matching against the query
        scored_symbols = []
        for symbol in unique_symbols:
            # Calculate similarity score
            score = fuzz.partial_ratio(query.lower(), symbol["name"].lower())
            
            # Boost score for exact substring matches
            if query.lower() in symbol["name"].lower():
                score = max(score, 80)
            
            scored_symbols.append((score, symbol))
        
        # Sort by score and take top results
        scored_symbols.sort(key=lambda x: x[0], reverse=True)
        top_symbols = [s[1] for s in scored_symbols[:limit]]
        
        return top_symbols
    
    def _extract_symbol_name(self, text: str) -> Optional[str]:
        """Extract the symbol name from matched text."""
        # Patterns to extract names from different definition types
        patterns = [
            r'(?:def|class|function|interface|type)\s+(\w+)',
            r'(?:const|let|var)\s+(\w+)\s*=',
            r'func\s+(?:\([^)]+\)\s+)?(\w+)',
        ]
        
        for pattern in patterns:
            match = re.search(pattern, text)
            if match:
                return match.group(1)
        
        return None
    
    def what_breaks(self, exact_symbol: Dict[str, Any]) -> Dict[str, Any]:
        """
        Find what uses a symbol (reverse dependencies).
        Simplified to use basic text search for speed and simplicity.
        
        Returns a dictionary with references and a standard caveat.
        """
        symbol_name = exact_symbol['name']
        references = []
        
        # Use simple grep-like search for the symbol name
        # Check if ripgrep is available, otherwise fall back to Python
        try:
            # Try using ripgrep if available
            cmd = [
                "rg",
                "-w",  # whole word
                "--json",
                symbol_name,
                str(self.root_path)
            ]
            
            result = subprocess.run(cmd, capture_output=True, text=True)
            
            if result.returncode == 0:
                # Parse ripgrep JSON output
                for line in result.stdout.strip().split('\n'):
                    if line:
                        try:
                            data = json.loads(line)
                            if data.get("type") == "match":
                                match_data = data.get("data", {})
                                references.append({
                                    "file": match_data.get("path", {}).get("text", ""),
                                    "line": match_data.get("line_number", 0),
                                    "text": match_data.get("lines", {}).get("text", "").strip()
                                })
                        except json.JSONDecodeError:
                            continue
            else:
                # Ripgrep not available or failed, fall back to Python
                references = self._python_text_search(symbol_name)
        except FileNotFoundError:
            # Ripgrep not installed, use Python fallback
            references = self._python_text_search(symbol_name)
        
        return {
            "references": references,
            "total_count": len(references),
            "note": f"Found {len(references)} potential references based on a text search for the name '{symbol_name}'. This may include comments, strings, or other unrelated symbols."
        }
    
    def _python_text_search(self, symbol_name: str) -> List[Dict[str, Any]]:
        """Fallback text search using Python when ripgrep is not available."""
        references = []
        gitignore_patterns = self._parse_gitignore()
        
        # Create word boundary pattern
        pattern = re.compile(r'\b' + re.escape(symbol_name) + r'\b')
        
        for file_path in self.root_path.rglob('*'):
            if not file_path.is_file():
                continue
            
            # Skip excluded files
            if self._should_exclude(file_path, gitignore_patterns):
                continue
            
            # Only search in source files
            if file_path.suffix.lower() not in LANGUAGE_MAP:
                continue
            
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    for line_num, line in enumerate(f, 1):
                        if pattern.search(line):
                            references.append({
                                "file": str(file_path),
                                "line": line_num,
                                "text": line.strip()
                            })
            except Exception:
                continue
        
        return references
```