tessera-codegraph 0.8.0

A local, deterministic semantic code graph and MCP server for AI coding agents. 11 languages, personalized PageRank impact, hallucination validator, call-path tracing, graph export, incremental indexing.
Documentation
use std::fmt::{self, Display};
use std::fs;
use std::path::{Path, PathBuf};

use anyhow::Result;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone)]
pub struct InitOptions {
    pub root: PathBuf,
    pub db_path: PathBuf,
    pub git_hooks: bool,
    pub mcp_configs: bool,
    pub force: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitResult {
    pub root: String,
    pub created: Vec<String>,
    pub skipped: Vec<String>,
    pub next_steps: Vec<String>,
}

impl Display for InitResult {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "Initialized Tessera defaults under {}", self.root)?;
        if !self.created.is_empty() {
            writeln!(f, "Created:")?;
            for path in &self.created {
                writeln!(f, "  {path}")?;
            }
        }
        if !self.skipped.is_empty() {
            writeln!(f, "Skipped existing files:")?;
            for path in &self.skipped {
                writeln!(f, "  {path}")?;
            }
        }
        writeln!(f, "Next:")?;
        for step in &self.next_steps {
            writeln!(f, "  {step}")?;
        }
        Ok(())
    }
}

pub fn run(options: InitOptions) -> Result<InitResult> {
    let root = options
        .root
        .canonicalize()
        .unwrap_or_else(|_| options.root.clone());
    fs::create_dir_all(root.join(".tessera"))?;

    let mut created = Vec::new();
    let mut skipped = Vec::new();

    write_file(
        &root,
        ".tessera/config.toml",
        &config_toml(&options.db_path),
        options.force,
        &mut created,
        &mut skipped,
    )?;

    if options.mcp_configs {
        write_file(
            &root,
            ".tessera/mcp/codex.toml",
            &codex_mcp(&options.db_path),
            options.force,
            &mut created,
            &mut skipped,
        )?;
        write_file(
            &root,
            ".tessera/mcp/claude.json",
            &claude_mcp(&options.db_path),
            options.force,
            &mut created,
            &mut skipped,
        )?;
        write_file(
            &root,
            ".tessera/mcp/cursor.json",
            &cursor_mcp(&options.db_path),
            options.force,
            &mut created,
            &mut skipped,
        )?;
    }

    if options.git_hooks {
        write_file(
            &root,
            ".git/hooks/post-merge",
            "#!/bin/sh\ntessera index .\n",
            options.force,
            &mut created,
            &mut skipped,
        )?;
        write_file(
            &root,
            ".git/hooks/post-checkout",
            "#!/bin/sh\ntessera index .\n",
            options.force,
            &mut created,
            &mut skipped,
        )?;
    }

    Ok(InitResult {
        root: root.display().to_string(),
        created,
        skipped,
        next_steps: vec![
            format!("tessera index . --db {}", options.db_path.display()),
            format!("tessera doctor --db {}", options.db_path.display()),
            "tessera impact <symbol>".to_string(),
        ],
    })
}

fn write_file(
    root: &Path,
    rel_path: &str,
    content: &str,
    force: bool,
    created: &mut Vec<String>,
    skipped: &mut Vec<String>,
) -> Result<()> {
    let path = root.join(rel_path);
    if path.exists() && !force {
        skipped.push(rel_path.to_string());
        return Ok(());
    }
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(&path, content)?;
    created.push(rel_path.to_string());
    Ok(())
}

fn config_toml(db_path: &Path) -> String {
    format!(
        "# Tessera project defaults\n[index]\ndb = \"{}\"\nwatch_poll_ms = 500\nwatch_debounce_ms = 250\n\n[include]\n# Empty means all supported source files under the root.\npaths = []\n\n[exclude]\n# Relative path prefixes to skip after built-in ignores.\npaths = []\n\n[ignore]\n# Built-in ignores already cover .git, node_modules, target, dist, build, out, coverage, vendor, .next, virtualenvs, caches, and .tessera.\nextra = []\n",
        db_path.display()
    )
}

fn codex_mcp(db_path: &Path) -> String {
    format!(
        "[[mcp_servers]]\nname = \"tessera\"\ncommand = \"tessera\"\nargs = [\"mcp\", \"--db\", \"{}\"]\n",
        db_path.display()
    )
}

fn claude_mcp(db_path: &Path) -> String {
    format!(
        "{{\n  \"mcpServers\": {{\n    \"tessera\": {{\n      \"command\": \"tessera\",\n      \"args\": [\"mcp\", \"--db\", \"{}\"]\n    }}\n  }}\n}}\n",
        db_path.display()
    )
}

fn cursor_mcp(db_path: &Path) -> String {
    claude_mcp(db_path)
}