=== src/mcp/handler.rs ===
use crate::db::models::{CodeElement, Relationship};
use crate::graph::{GraphEngine, ImpactAnalyzer};
use serde_json::{json, Value};

const INSTRUCTIONS_CONTENT: &str = r#"# LeanKG Tools - Usage Instructions

## For AI Coding Agents (Cursor, OpenCode, etc.)

Use LeanKG tools **first** before performing any codebase search, navigation, or impact analysis.

---

## When to Use Each Tool

### Code Discovery & Search

| Task | Use This Tool |
|------|--------------|
| Find a file by name | `query_file` |
| Find a function definition | `find_function` |
| Search code by name/type | `search_code` |
| Get full codebase structure | `get_code_tree` |

### Dependency Analysis

| Task | Use This Tool |
|------|--------------|
| Get direct imports of a file | `get_dependencies` |
| Get files that import/use a file | `get_dependents` |
| Get function call chain (full depth) | `get_call_graph` |
| Calculate what breaks if file changes | `get_impact_radius` |

### Review & Context

| Task | Use This Tool |
|------|--------------|
| Generate focused review context | `get_review_context` |
| Get minimal AI context (token-optimized) | `get_context` |
| Find oversized functions | `find_large_functions` |

### Testing & Documentation

| Task | Use This Tool |
|------|--------------|
| Get test coverage for a function | `get_tested_by` |
| Get docs that reference a file | `get_doc_for_file` |
| Get code elements in a doc | `get_files_for_doc` |
| Get doc directory structure | `get_doc_structure` |
| Find docs related to a change | `find_related_docs` |


=== src/indexer/extractor.rs ===
use crate::db::models::{CodeElement, Relationship};
use std::path::Path;
use tree_sitter::{Node, Tree};

pub struct EntityExtractor<'a> {
    source: &'a [u8],
    file_path: &'a str,
    language: &'a str,
}

pub fn is_test_file(file_path: &str) -> bool {
    let path = Path::new(file_path);
    let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");

    match path.extension().and_then(|e| e.to_str()).unwrap_or("") {
        "go" => file_name.ends_with("_test.go"),
        "py" => file_name.starts_with("test_") || file_name.ends_with("_test.py"),
        "rb" => file_name.ends_with("_spec.rb"),
        "rs" => {
            file_name.ends_with("_test.rs") || path.components().any(|c| c.as_os_str() == "tests")
        }
        "ts" | "js" => {
            file_name.ends_with(".test.ts")
                || file_name.ends_with(".test.js")
                || file_name.ends_with(".spec.ts")
                || file_name.ends_with(".spec.js")
        }
        _ => false,
    }
}

pub fn is_noise_call(name: &str) -> bool {
    matches!(
        name,
        "println"
            | "print"
            | "eprintln"
            | "format"
            | "vec"
            | "assert"
            | "assert_eq"
            | "assert_ne"
            | "panic"
            | "unwrap"
            | "expect"
            | "clone"
            | "to_string"
            | "into"
            | "from"
            | "len"

=== src/config/project.rs ===
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectConfig {
    pub project: ProjectSettings,
    pub indexer: IndexerConfig,
    pub mcp: McpConfig,
    pub documentation: DocConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectSettings {
    pub name: String,
    pub root: PathBuf,
    pub languages: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexerConfig {
    pub exclude: Vec<String>,
    pub include: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpConfig {
    pub enabled: bool,
    pub port: u16,
    pub auth_token: String,
    pub auto_index_on_start: bool,
    pub auto_index_threshold_minutes: u64,
    pub index_on_first_call: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocConfig {
    pub output: PathBuf,
    pub templates: Vec<String>,
}

impl Default for ProjectConfig {
    fn default() -> Self {
        Self {
            project: ProjectSettings {
                name: "my-project".to_string(),
                root: PathBuf::from("./src"),
                languages: vec![
                    "go".to_string(),
                    "typescript".to_string(),
                    "python".to_string(),

=== src/web/handlers.rs ===
use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
    Json,
};
use serde::{Deserialize, Serialize};

use crate::db;

use super::{ApiResponse, AppState};

#[derive(Deserialize, Serialize)]
pub struct QueryRequest {
    pub query: String,
}

#[derive(Serialize)]
pub struct QueryResponse {
    pub result: Vec<serde_json::Value>,
}

#[derive(Deserialize)]
pub struct SearchParams {
    pub q: Option<String>,
    pub element_type: Option<String>,
    pub file_path: Option<String>,
}

#[derive(Deserialize, Serialize)]
pub struct AnnotationRequest {
    pub element_qualified: String,
    pub description: String,
    pub user_story_id: Option<String>,
    pub feature_id: Option<String>,
}

#[derive(Serialize, Clone)]
pub struct GraphData {
    pub nodes: Vec<GraphNode>,
    pub edges: Vec<GraphEdge>,
    pub filtered: Option<GraphFilterInfo>,
}

#[derive(Serialize, Clone)]
pub struct GraphFilterInfo {
    pub tests_filtered: usize,
    pub message: String,
}


=== src/graph/cache.rs ===
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;

#[derive(Debug, Clone)]
pub struct CacheEntry<T> {
    pub value: T,
    pub created_at: Instant,
}

pub struct TimedCache<K, V> {
    data: HashMap<K, CacheEntry<V>>,
    ttl: Duration,
    max_entries: usize,
}

impl<K: Eq + Hash + Clone, V: Clone> TimedCache<K, V> {
    pub fn new(ttl_secs: u64, max_entries: usize) -> Self {
        Self {
            data: HashMap::new(),
            ttl: Duration::from_secs(ttl_secs),
            max_entries,
        }
    }

    pub fn get(&self, key: &K) -> Option<V> {
        self.data.get(key).and_then(|entry| {
            if entry.created_at.elapsed() < self.ttl {
                Some(entry.value.clone())
            } else {
                None
            }
        })
    }

    pub fn insert(&mut self, key: K, value: V) {
        if self.data.len() >= self.max_entries {
            self.evict_expired();
            if self.data.len() >= self.max_entries {
                if let Some(oldest) = self
                    .data
                    .iter()
                    .min_by_key(|(_, entry)| entry.created_at)
                    .map(|(k, _)| k.clone())
                {
                    self.data.remove(&oldest);
                }
            }

=== src/web/handlers.rs ===
use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
    Json,
};
use serde::{Deserialize, Serialize};

use crate::db;

use super::{ApiResponse, AppState};

#[derive(Deserialize, Serialize)]
pub struct QueryRequest {
    pub query: String,
}

#[derive(Serialize)]
pub struct QueryResponse {
    pub result: Vec<serde_json::Value>,
}

#[derive(Deserialize)]
pub struct SearchParams {
    pub q: Option<String>,
    pub element_type: Option<String>,
    pub file_path: Option<String>,
}

#[derive(Deserialize, Serialize)]
pub struct AnnotationRequest {
    pub element_qualified: String,
    pub description: String,
    pub user_story_id: Option<String>,
    pub feature_id: Option<String>,
}

#[derive(Serialize, Clone)]
pub struct GraphData {
    pub nodes: Vec<GraphNode>,
    pub edges: Vec<GraphEdge>,
    pub filtered: Option<GraphFilterInfo>,
}

#[derive(Serialize, Clone)]
pub struct GraphFilterInfo {
    pub tests_filtered: usize,
    pub message: String,
}


=== src/mcp/handler.rs ===
use crate::db::models::{CodeElement, Relationship};
use crate::graph::{GraphEngine, ImpactAnalyzer};
use serde_json::{json, Value};

const INSTRUCTIONS_CONTENT: &str = r#"# LeanKG Tools - Usage Instructions

## For AI Coding Agents (Cursor, OpenCode, etc.)

Use LeanKG tools **first** before performing any codebase search, navigation, or impact analysis.

---

## When to Use Each Tool

### Code Discovery & Search

| Task | Use This Tool |
|------|--------------|
| Find a file by name | `query_file` |
| Find a function definition | `find_function` |
| Search code by name/type | `search_code` |
| Get full codebase structure | `get_code_tree` |

### Dependency Analysis

| Task | Use This Tool |
|------|--------------|
| Get direct imports of a file | `get_dependencies` |
| Get files that import/use a file | `get_dependents` |
| Get function call chain (full depth) | `get_call_graph` |
| Calculate what breaks if file changes | `get_impact_radius` |

### Review & Context

| Task | Use This Tool |
|------|--------------|
| Generate focused review context | `get_review_context` |
| Get minimal AI context (token-optimized) | `get_context` |
| Find oversized functions | `find_large_functions` |

### Testing & Documentation

| Task | Use This Tool |
|------|--------------|
| Get test coverage for a function | `get_tested_by` |
| Get docs that reference a file | `get_doc_for_file` |
| Get code elements in a doc | `get_files_for_doc` |
| Get doc directory structure | `get_doc_structure` |
| Find docs related to a change | `find_related_docs` |


=== src/mcp/server.rs ===
use crate::db::schema::init_db;
use crate::graph::GraphEngine;
use crate::mcp::auth::AuthConfig;
use crate::mcp::handler::ToolHandler;
use crate::mcp::tools::ToolRegistry;
use crate::mcp::watcher::start_watcher;
use rmcp::handler::server::ServerHandler;
use rmcp::model::{
    CallToolRequestParams, CallToolResult, Content, ListToolsResult, ServerCapabilities,
    ServerInfo, Tool,
};
use rmcp::service::{serve_server, RoleServer};
use rmcp::transport::stdio;
use std::path::PathBuf;
use std::sync::Arc;
use parking_lot::RwLock;
use tokio::sync::RwLock as TokioRwLock;

pub struct MCPServer {
    auth_config: Arc<TokioRwLock<AuthConfig>>,
    db_path: Arc<RwLock<PathBuf>>,
    graph_engine: Arc<parking_lot::Mutex<Option<GraphEngine>>>,
    watch_path: Option<PathBuf>,
}

impl std::fmt::Debug for MCPServer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("MCPServer")
            .field("db_path", &self.db_path)
            .finish()
    }
}

impl Clone for MCPServer {
    fn clone(&self) -> Self {
        Self {
            auth_config: self.auth_config.clone(),
            db_path: self.db_path.clone(),
            graph_engine: self.graph_engine.clone(),
            watch_path: self.watch_path.clone(),
        }
    }
}

impl MCPServer {
    pub fn new(db_path: std::path::PathBuf) -> Self {
        Self {
            auth_config: Arc::new(TokioRwLock::new(AuthConfig::default())),
            db_path: Arc::new(RwLock::new(db_path)),
            graph_engine: Arc::new(parking_lot::Mutex::new(None)),

=== src/benchmark/runner.rs ===
use crate::benchmark::data::BenchmarkResult;
use std::error::Error;
use std::path::PathBuf;
use std::process::{Command, Output, Stdio};
use std::time::Duration;

fn kilo_config_path() -> PathBuf {
    dirs::config_dir()
        .unwrap_or_else(|| PathBuf::from(".config"))
        .join("kilo")
}

const KILO_MCP_WITH_LEANKG: &str = "mcp_settings_with_leankg.json";
const KILO_MCP_WITHOUT_LEANKG: &str = "mcp_settings_without_leankg.json";
const KILO_MCP_SETTINGS: &str = "kilo.json";

trait WaitWithOutputTimeout {
    fn wait_with_output_timeout(self, duration: Duration) -> Result<Output, ()>;
}

impl WaitWithOutputTimeout for std::process::Child {
    fn wait_with_output_timeout(self, duration: Duration) -> Result<Output, ()> {
        use std::thread;
        use std::time::Instant;

        let start = Instant::now();
        let handle =
            thread::spawn(move || self.wait_with_output().expect("wait_with_output failed"));

        loop {
            if handle.is_finished() {
                return handle.join().map_err(|_| ());
            }
            if start.elapsed() >= duration {
                return Err(());
            }
            thread::sleep(Duration::from_millis(100));
        }
    }
}

pub struct BenchmarkRunner {
    output_dir: PathBuf,
    cli: CliTool,
}

#[derive(Clone)]
pub enum CliTool {
    OpenCode,
    Gemini,

=== src/benchmark/mod.rs ===
pub mod data;
pub mod runner;

use std::path::PathBuf;

pub use runner::{BenchmarkRunner, CliTool};

pub fn run(category: Option<String>, cli: CliTool) -> Result<(), Box<dyn std::error::Error>> {
    let prompts_dir = PathBuf::from("benchmark/prompts");
    let output_dir = PathBuf::from("benchmark/results");

    let categories = if let Some(cat) = category {
        vec![data::PromptCategory::from_yaml(
            &prompts_dir.join(format!("{}.yaml", cat)),
        )?]
    } else {
        data::PromptCategory::load_all(&prompts_dir)?
    };

    let runner = BenchmarkRunner::new(output_dir, cli);

    for cat in &categories {
        println!("\n=== Category: {} ===\n", cat.name);
        for task in &cat.tasks {
            println!("Running: {}", task.id);

            let with_leankg = runner.run_with_leankg(&task.prompt);
            let without_leankg = runner.run_without_leankg(&task.prompt);

            let overhead = with_leankg.overhead(&without_leankg);

            println!(
                "  With LeanKG: {} tokens (input: {}, cached: {})",
                with_leankg.total_tokens, with_leankg.input_tokens, with_leankg.cached_tokens
            );
            println!(
                "  Without: {} tokens (input: {}, cached: {})",
                without_leankg.total_tokens,
                without_leankg.input_tokens,
                without_leankg.cached_tokens
            );
            println!("  Overhead: {} tokens\n", overhead.token_delta);

            let _ = runner.save_comparison(&with_leankg, &without_leankg, &task.id);
        }
    }

    Ok(())
}

=== src/benchmark/data.rs ===
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::path::Path;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkResult {
    pub total_tokens: u32,
    pub input_tokens: u32,
    pub cached_tokens: u32,
    pub token_percent: f32,
    pub build_time_seconds: f32,
    pub success: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptTask {
    pub id: String,
    pub prompt: String,
    pub expected: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptCategory {
    pub name: String,
    pub description: String,
    pub tasks: Vec<PromptTask>,
}

impl PromptCategory {
    pub fn from_yaml(path: &Path) -> Result<Self, Box<dyn Error>> {
        let content = std::fs::read_to_string(path)?;
        let category: PromptCategory = serde_yaml::from_str(&content)?;
        Ok(category)
    }

    pub fn load_all(prompts_dir: &Path) -> Result<Vec<Self>, Box<dyn Error>> {
        let mut categories = Vec::new();
        for entry in std::fs::read_dir(prompts_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.extension().and_then(|s| s.to_str()) == Some("yaml") {
                categories.push(Self::from_yaml(&path)?);
            }
        }
        Ok(categories)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OverheadResult {

=== src/benchmark/runner.rs ===
use crate::benchmark::data::BenchmarkResult;
use std::error::Error;
use std::path::PathBuf;
use std::process::{Command, Output, Stdio};
use std::time::Duration;

fn kilo_config_path() -> PathBuf {
    dirs::config_dir()
        .unwrap_or_else(|| PathBuf::from(".config"))
        .join("kilo")
}

const KILO_MCP_WITH_LEANKG: &str = "mcp_settings_with_leankg.json";
const KILO_MCP_WITHOUT_LEANKG: &str = "mcp_settings_without_leankg.json";
const KILO_MCP_SETTINGS: &str = "kilo.json";

trait WaitWithOutputTimeout {
    fn wait_with_output_timeout(self, duration: Duration) -> Result<Output, ()>;
}

impl WaitWithOutputTimeout for std::process::Child {
    fn wait_with_output_timeout(self, duration: Duration) -> Result<Output, ()> {
        use std::thread;
        use std::time::Instant;

        let start = Instant::now();
        let handle =
            thread::spawn(move || self.wait_with_output().expect("wait_with_output failed"));

        loop {
            if handle.is_finished() {
                return handle.join().map_err(|_| ());
            }
            if start.elapsed() >= duration {
                return Err(());
            }
            thread::sleep(Duration::from_millis(100));
        }
    }
}

pub struct BenchmarkRunner {
    output_dir: PathBuf,
    cli: CliTool,
}

#[derive(Clone)]
pub enum CliTool {
    OpenCode,
    Gemini,

=== src/benchmark/data.rs ===
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::path::Path;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkResult {
    pub total_tokens: u32,
    pub input_tokens: u32,
    pub cached_tokens: u32,
    pub token_percent: f32,
    pub build_time_seconds: f32,
    pub success: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptTask {
    pub id: String,
    pub prompt: String,
    pub expected: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptCategory {
    pub name: String,
    pub description: String,
    pub tasks: Vec<PromptTask>,
}

impl PromptCategory {
    pub fn from_yaml(path: &Path) -> Result<Self, Box<dyn Error>> {
        let content = std::fs::read_to_string(path)?;
        let category: PromptCategory = serde_yaml::from_str(&content)?;
        Ok(category)
    }

    pub fn load_all(prompts_dir: &Path) -> Result<Vec<Self>, Box<dyn Error>> {
        let mut categories = Vec::new();
        for entry in std::fs::read_dir(prompts_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.extension().and_then(|s| s.to_str()) == Some("yaml") {
                categories.push(Self::from_yaml(&path)?);
            }
        }
        Ok(categories)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OverheadResult {

=== src/watcher/notify_handler.rs ===
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Receiver};
use std::time::Duration;
use tokio::sync::mpsc as tokio_mpsc;

#[derive(Debug, Clone)]
pub struct FileChange {
    pub path: PathBuf,
    pub kind: FileChangeKind,
}

#[derive(Debug, Clone)]
pub enum FileChangeKind {
    Created,
    Modified,
    Deleted,
}

impl From<&notify::EventKind> for FileChangeKind {
    fn from(kind: &notify::EventKind) -> Self {
        match kind {
            notify::EventKind::Create(_) => FileChangeKind::Created,
            notify::EventKind::Modify(_) => FileChangeKind::Modified,
            notify::EventKind::Remove(_) => FileChangeKind::Deleted,
            _ => FileChangeKind::Modified,
        }
    }
}

pub struct FileWatcher {
    watcher: RecommendedWatcher,
    watch_path: PathBuf,
    rx: Receiver<Result<Event, notify::Error>>,
}

impl FileWatcher {
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, notify::Error> {
        let (tx, rx) = channel();

        let watcher = RecommendedWatcher::new(
            move |res: Result<Event, notify::Error>| {
                if let Ok(event) = res {
                    let _ = tx.send(Ok(event));
                }
            },
            Config::default().with_poll_interval(Duration::from_secs(2)),
        )?;

        let mut watcher = watcher;

=== src/indexer/extractor.rs ===
use crate::db::models::{CodeElement, Relationship};
use std::path::Path;
use tree_sitter::{Node, Tree};

pub struct EntityExtractor<'a> {
    source: &'a [u8],
    file_path: &'a str,
    language: &'a str,
}

pub fn is_test_file(file_path: &str) -> bool {
    let path = Path::new(file_path);
    let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");

    match path.extension().and_then(|e| e.to_str()).unwrap_or("") {
        "go" => file_name.ends_with("_test.go"),
        "py" => file_name.starts_with("test_") || file_name.ends_with("_test.py"),
        "rb" => file_name.ends_with("_spec.rb"),
        "rs" => {
            file_name.ends_with("_test.rs") || path.components().any(|c| c.as_os_str() == "tests")
        }
        "ts" | "js" => {
            file_name.ends_with(".test.ts")
                || file_name.ends_with(".test.js")
                || file_name.ends_with(".spec.ts")
                || file_name.ends_with(".spec.js")
        }
        _ => false,
    }
}

pub fn is_noise_call(name: &str) -> bool {
    matches!(
        name,
        "println"
            | "print"
            | "eprintln"
            | "format"
            | "vec"
            | "assert"
            | "assert_eq"
            | "assert_ne"
            | "panic"
            | "unwrap"
            | "expect"
            | "clone"
            | "to_string"
            | "into"
            | "from"
            | "len"

=== src/indexer/cicd.rs ===
use crate::db::models::{CodeElement, Relationship};
use serde_yaml::Value;

pub struct CicdYamlExtractor {
    source: Vec<u8>,
    file_path: String,
}

#[derive(Debug)]
enum CicdPlatform {
    GitHubActions,
    GitLabCI,
    AzurePipelines,
    Unknown,
}

impl CicdYamlExtractor {
    pub fn new(source: &[u8], file_path: &str) -> Self {
        Self {
            source: source.to_vec(),
            file_path: file_path.to_string(),
        }
    }

    pub fn extract(&self) -> (Vec<CodeElement>, Vec<Relationship>) {
        let mut elements = Vec::new();
        let content = String::from_utf8_lossy(&self.source);
        let source_str = content.as_ref();

        let yaml: Value = match serde_yaml::from_str(source_str) {
            Ok(v) => v,
            Err(_) => return (elements, Vec::new()),
        };

        let platform = self.detect_platform(&yaml);

        let line_count = source_str.lines().count() as u32;
        elements.push(CodeElement {
            qualified_name: self.file_path.clone(),
            element_type: "cicd".to_string(),
            name: self
                .file_path
                .rsplit('/')
                .next()
                .unwrap_or(&self.file_path)
                .to_string(),
            file_path: self.file_path.clone(),
            line_start: 1,
            line_end: line_count,
            language: "yaml".to_string(),

=== src/indexer/terraform.rs ===
use crate::db::models::{CodeElement, Relationship};
use regex::Regex;

pub struct TerraformExtractor {
    source: Vec<u8>,
    file_path: String,
}

impl TerraformExtractor {
    pub fn new(source: &[u8], file_path: &str) -> Self {
        Self {
            source: source.to_vec(),
            file_path: file_path.to_string(),
        }
    }

    pub fn extract(&self) -> (Vec<CodeElement>, Vec<Relationship>) {
        let mut elements = Vec::new();
        let content = String::from_utf8_lossy(&self.source);
        let source_str = content.as_ref();

        let resource_regex = Regex::new(r#"(?m)^resource\s+"([^"]+)"\s+"([^"]+)""#).ok();
        let data_regex = Regex::new(r#"(?m)^data\s+"([^"]+)"\s+"([^"]+)""#).ok();
        let variable_regex = Regex::new(r#"(?m)^variable\s+"([^"]+)""#).ok();
        let output_regex = Regex::new(r#"(?m)^output\s+"([^"]+)""#).ok();
        let module_regex = Regex::new(r#"(?m)^module\s+"([^"]+)""#).ok();
        let provider_regex = Regex::new(r#"(?m)^provider\s+"([^"]+)""#).ok();

        elements.push(CodeElement {
            qualified_name: self.file_path.clone(),
            element_type: "terraform".to_string(),
            name: self
                .file_path
                .rsplit('/')
                .next()
                .unwrap_or(&self.file_path)
                .to_string(),
            file_path: self.file_path.clone(),
            line_start: 1,
            line_end: source_str.lines().count() as u32,
            language: "terraform".to_string(),
            parent_qualified: None,
            metadata: serde_json::json!({}),
        });

        if let Some(re) = resource_regex {
            for cap in re.captures_iter(source_str) {
                if let (Some(resource_type), Some(name)) = (cap.get(1), cap.get(2)) {
                    let qualified_name = format!("{}::{}", self.file_path, name.as_str());
                    elements.push(CodeElement {

=== src/config/project.rs ===
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectConfig {
    pub project: ProjectSettings,
    pub indexer: IndexerConfig,
    pub mcp: McpConfig,
    pub documentation: DocConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectSettings {
    pub name: String,
    pub root: PathBuf,
    pub languages: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexerConfig {
    pub exclude: Vec<String>,
    pub include: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpConfig {
    pub enabled: bool,
    pub port: u16,
    pub auth_token: String,
    pub auto_index_on_start: bool,
    pub auto_index_threshold_minutes: u64,
    pub index_on_first_call: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocConfig {
    pub output: PathBuf,
    pub templates: Vec<String>,
}

impl Default for ProjectConfig {
    fn default() -> Self {
        Self {
            project: ProjectSettings {
                name: "my-project".to_string(),
                root: PathBuf::from("./src"),
                languages: vec![
                    "go".to_string(),
                    "typescript".to_string(),
                    "python".to_string(),

=== src/web/handlers.rs ===
use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
    Json,
};
use serde::{Deserialize, Serialize};

use crate::db;

use super::{ApiResponse, AppState};

#[derive(Deserialize, Serialize)]
pub struct QueryRequest {
    pub query: String,
}

#[derive(Serialize)]
pub struct QueryResponse {
    pub result: Vec<serde_json::Value>,
}

#[derive(Deserialize)]
pub struct SearchParams {
    pub q: Option<String>,
    pub element_type: Option<String>,
    pub file_path: Option<String>,
}

#[derive(Deserialize, Serialize)]
pub struct AnnotationRequest {
    pub element_qualified: String,
    pub description: String,
    pub user_story_id: Option<String>,
    pub feature_id: Option<String>,
}

#[derive(Serialize, Clone)]
pub struct GraphData {
    pub nodes: Vec<GraphNode>,
    pub edges: Vec<GraphEdge>,
    pub filtered: Option<GraphFilterInfo>,
}

#[derive(Serialize, Clone)]
pub struct GraphFilterInfo {
    pub tests_filtered: usize,
    pub message: String,
}


=== src/lib.rs ===
// LeanKG Library
// This library contains the core modules for the knowledge graph system

pub mod cli;
pub mod config;
pub mod db;
pub mod doc;
pub mod doc_indexer;
pub mod graph;
pub mod indexer;
pub mod mcp;
pub mod watcher;
pub mod benchmark;

=== src/benchmark/runner.rs ===
use crate::benchmark::data::BenchmarkResult;
use std::error::Error;
use std::path::PathBuf;
use std::process::{Command, Output, Stdio};
use std::time::Duration;

fn kilo_config_path() -> PathBuf {
    dirs::config_dir()
        .unwrap_or_else(|| PathBuf::from(".config"))
        .join("kilo")
}

const KILO_MCP_WITH_LEANKG: &str = "mcp_settings_with_leankg.json";
const KILO_MCP_WITHOUT_LEANKG: &str = "mcp_settings_without_leankg.json";
const KILO_MCP_SETTINGS: &str = "kilo.json";

trait WaitWithOutputTimeout {
    fn wait_with_output_timeout(self, duration: Duration) -> Result<Output, ()>;
}

impl WaitWithOutputTimeout for std::process::Child {
    fn wait_with_output_timeout(self, duration: Duration) -> Result<Output, ()> {
        use std::thread;
        use std::time::Instant;

        let start = Instant::now();
        let handle =
            thread::spawn(move || self.wait_with_output().expect("wait_with_output failed"));

        loop {
            if handle.is_finished() {
                return handle.join().map_err(|_| ());
            }
            if start.elapsed() >= duration {
                return Err(());
            }
            thread::sleep(Duration::from_millis(100));
        }
    }
}

pub struct BenchmarkRunner {
    output_dir: PathBuf,
    cli: CliTool,
}

#[derive(Clone)]
pub enum CliTool {
    OpenCode,
    Gemini,

=== src/benchmark/data.rs ===
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::path::Path;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkResult {
    pub total_tokens: u32,
    pub input_tokens: u32,
    pub cached_tokens: u32,
    pub token_percent: f32,
    pub build_time_seconds: f32,
    pub success: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptTask {
    pub id: String,
    pub prompt: String,
    pub expected: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptCategory {
    pub name: String,
    pub description: String,
    pub tasks: Vec<PromptTask>,
}

impl PromptCategory {
    pub fn from_yaml(path: &Path) -> Result<Self, Box<dyn Error>> {
        let content = std::fs::read_to_string(path)?;
        let category: PromptCategory = serde_yaml::from_str(&content)?;
        Ok(category)
    }

    pub fn load_all(prompts_dir: &Path) -> Result<Vec<Self>, Box<dyn Error>> {
        let mut categories = Vec::new();
        for entry in std::fs::read_dir(prompts_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.extension().and_then(|s| s.to_str()) == Some("yaml") {
                categories.push(Self::from_yaml(&path)?);
            }
        }
        Ok(categories)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OverheadResult {

=== src/watcher/notify_handler.rs ===
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Receiver};
use std::time::Duration;
use tokio::sync::mpsc as tokio_mpsc;

#[derive(Debug, Clone)]
pub struct FileChange {
    pub path: PathBuf,
    pub kind: FileChangeKind,
}

#[derive(Debug, Clone)]
pub enum FileChangeKind {
    Created,
    Modified,
    Deleted,
}

impl From<&notify::EventKind> for FileChangeKind {
    fn from(kind: &notify::EventKind) -> Self {
        match kind {
            notify::EventKind::Create(_) => FileChangeKind::Created,
            notify::EventKind::Modify(_) => FileChangeKind::Modified,
            notify::EventKind::Remove(_) => FileChangeKind::Deleted,
            _ => FileChangeKind::Modified,
        }
    }
}

pub struct FileWatcher {
    watcher: RecommendedWatcher,
    watch_path: PathBuf,
    rx: Receiver<Result<Event, notify::Error>>,
}

impl FileWatcher {
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, notify::Error> {
        let (tx, rx) = channel();

        let watcher = RecommendedWatcher::new(
            move |res: Result<Event, notify::Error>| {
                if let Ok(event) = res {
                    let _ = tx.send(Ok(event));
                }
            },
            Config::default().with_poll_interval(Duration::from_secs(2)),
        )?;

        let mut watcher = watcher;

=== src/mcp/handler.rs ===
use crate::db::models::{CodeElement, Relationship};
use crate::graph::{GraphEngine, ImpactAnalyzer};
use serde_json::{json, Value};

const INSTRUCTIONS_CONTENT: &str = r#"# LeanKG Tools - Usage Instructions

## For AI Coding Agents (Cursor, OpenCode, etc.)

Use LeanKG tools **first** before performing any codebase search, navigation, or impact analysis.

---

## When to Use Each Tool

### Code Discovery & Search

| Task | Use This Tool |
|------|--------------|
| Find a file by name | `query_file` |
| Find a function definition | `find_function` |
| Search code by name/type | `search_code` |
| Get full codebase structure | `get_code_tree` |

### Dependency Analysis

| Task | Use This Tool |
|------|--------------|
| Get direct imports of a file | `get_dependencies` |
| Get files that import/use a file | `get_dependents` |
| Get function call chain (full depth) | `get_call_graph` |
| Calculate what breaks if file changes | `get_impact_radius` |

### Review & Context

| Task | Use This Tool |
|------|--------------|
| Generate focused review context | `get_review_context` |
| Get minimal AI context (token-optimized) | `get_context` |
| Find oversized functions | `find_large_functions` |

### Testing & Documentation

| Task | Use This Tool |
|------|--------------|
| Get test coverage for a function | `get_tested_by` |
| Get docs that reference a file | `get_doc_for_file` |
| Get code elements in a doc | `get_files_for_doc` |
| Get doc directory structure | `get_doc_structure` |
| Find docs related to a change | `find_related_docs` |


=== src/indexer/extractor.rs ===
use crate::db::models::{CodeElement, Relationship};
use std::path::Path;
use tree_sitter::{Node, Tree};

pub struct EntityExtractor<'a> {
    source: &'a [u8],
    file_path: &'a str,
    language: &'a str,
}

pub fn is_test_file(file_path: &str) -> bool {
    let path = Path::new(file_path);
    let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");

    match path.extension().and_then(|e| e.to_str()).unwrap_or("") {
        "go" => file_name.ends_with("_test.go"),
        "py" => file_name.starts_with("test_") || file_name.ends_with("_test.py"),
        "rb" => file_name.ends_with("_spec.rb"),
        "rs" => {
            file_name.ends_with("_test.rs") || path.components().any(|c| c.as_os_str() == "tests")
        }
        "ts" | "js" => {
            file_name.ends_with(".test.ts")
                || file_name.ends_with(".test.js")
                || file_name.ends_with(".spec.ts")
                || file_name.ends_with(".spec.js")
        }
        _ => false,
    }
}

pub fn is_noise_call(name: &str) -> bool {
    matches!(
        name,
        "println"
            | "print"
            | "eprintln"
            | "format"
            | "vec"
            | "assert"
            | "assert_eq"
            | "assert_ne"
            | "panic"
            | "unwrap"
            | "expect"
            | "clone"
            | "to_string"
            | "into"
            | "from"
            | "len"
