use clap::{Parser, Subcommand, ValueEnum};
pub use crate::analysis::DeadSymbolJson;
#[derive(Parser, Debug, Clone)]
#[command(name = "mirage")]
#[command(author, version, about)]
#[command(
long_about = "Mirage is a path-aware code intelligence engine that operates on graphs, not text.
It materializes behavior explicitly: paths, proofs, counterexamples.
NOT:
- A search tool (llmgrep already does this)
- An embedding tool
- Static analysis / linting
IS:
- Path enumeration and verification
- Graph-based reasoning about code behavior
- Truth engine that materializes facts for LLM consumption
The Golden Rule: An agent may only speak if it can reference a graph artifact."
)]
pub struct Cli {
#[arg(global = true, long, env = "MIRAGE_DB")]
pub db: Option<String>,
#[arg(global = true, long, value_enum, default_value_t = OutputFormat::Human)]
pub output: OutputFormat,
#[arg(long, global = true, default_value = "false")]
pub detect_backend: bool,
#[arg(long, global = true, default_value = "false")]
pub record: bool,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Human,
Json,
Pretty,
}
#[derive(Subcommand, Debug, Clone)]
pub enum Commands {
Status(StatusArgs),
Paths(PathsArgs),
Cfg(CfgArgs),
Dominators(DominatorsArgs),
Loops(LoopsArgs),
Unreachable(UnreachableArgs),
Patterns(PatternsArgs),
Frontiers(FrontiersArgs),
Verify(VerifyArgs),
BlastZone(BlastZoneArgs),
Cycles(CyclesArgs),
Slice(SliceArgs),
Hotspots(HotspotsArgs),
Hotpaths(HotpathsArgs),
Diff(DiffArgs),
Icfg(IcfgArgs),
Coverage(CoverageArgs),
Migrate(MigrateArgs),
Docs(DocsArgs),
Risk(RiskArgs),
Suggest(SuggestArgs),
Stats(StatsArgs),
}
#[derive(Parser, Debug, Clone, Copy)]
pub struct StatusArgs {}
#[derive(Parser, Debug, Clone)]
pub struct PathsArgs {
#[arg(long)]
pub function: String,
#[arg(long)]
pub file: Option<String>,
#[arg(long)]
pub show_errors: bool,
#[arg(long)]
pub max_length: Option<usize>,
#[arg(long)]
pub with_blocks: bool,
#[arg(long)]
pub incremental: bool,
#[arg(long)]
pub since: Option<String>,
#[arg(long)]
pub by_coverage: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct CfgArgs {
#[arg(long)]
pub function: String,
#[arg(long)]
pub file: Option<String>,
#[arg(long, value_enum)]
pub format: Option<CfgFormat>,
}
#[derive(Parser, Debug, Clone)]
pub struct CoverageArgs {
#[arg(long)]
pub function: String,
#[arg(long)]
pub file: Option<String>,
}
#[derive(Parser, Debug, Clone)]
pub struct DominatorsArgs {
#[arg(long)]
pub function: String,
#[arg(long)]
pub file: Option<String>,
#[arg(long)]
pub must_pass_through: Option<String>,
#[arg(long)]
pub post: bool,
#[arg(long)]
pub inter_procedural: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct LoopsArgs {
#[arg(long)]
pub function: String,
#[arg(long)]
pub file: Option<String>,
#[arg(long)]
pub verbose: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UnreachableArgs {
#[arg(long)]
pub within_functions: bool,
#[arg(long)]
pub show_branches: bool,
#[arg(long)]
pub include_uncalled: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct PatternsArgs {
#[arg(long)]
pub function: String,
#[arg(long)]
pub file: Option<String>,
#[arg(long)]
pub if_else: bool,
#[arg(long)]
pub r#match: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct FrontiersArgs {
#[arg(long)]
pub function: String,
#[arg(long)]
pub file: Option<String>,
#[arg(long)]
pub iterated: bool,
#[arg(long)]
pub node: Option<usize>,
}
#[derive(Parser, Debug, Clone)]
pub struct VerifyArgs {
#[arg(long)]
pub path_id: String,
}
#[derive(Parser, Debug, Clone)]
pub struct BlastZoneArgs {
#[arg(long)]
pub function: Option<String>,
#[arg(long)]
pub file: Option<String>,
#[arg(long)]
pub block_id: Option<usize>,
#[arg(long)]
pub path_id: Option<String>,
#[arg(long, default_value_t = 100)]
pub max_depth: usize,
#[arg(long)]
pub include_errors: bool,
#[arg(long)]
pub use_call_graph: bool,
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum CycleTypeArg {
All,
InterFunction,
SelfLoop,
}
#[derive(Parser, Debug, Clone)]
pub struct CyclesArgs {
#[arg(long)]
pub call_graph: bool,
#[arg(long)]
pub function_loops: bool,
#[arg(long)]
pub both: bool,
#[arg(long, value_enum, default_value = "all")]
pub cycle_type: CycleTypeArg,
#[arg(long)]
pub verbose: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct SliceArgs {
#[arg(long)]
pub symbol: String,
#[arg(long, value_enum)]
pub direction: SliceDirectionArg,
#[arg(long)]
pub verbose: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct HotspotsArgs {
#[arg(long, default_value = "main")]
pub entry: String,
#[arg(long, default_value = "20")]
pub top: usize,
#[arg(long)]
pub min_paths: Option<usize>,
#[arg(long)]
pub verbose: bool,
#[arg(long, default_value = "true")]
pub inter_procedural: bool,
#[arg(long, conflicts_with = "inter_procedural")]
pub intra_procedural: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct HotpathsArgs {
#[arg(long)]
pub function: String,
#[arg(long, default_value = "10")]
pub top: usize,
#[arg(long)]
pub rationale: bool,
#[arg(long)]
pub min_score: Option<f64>,
}
#[derive(Parser, Debug, Clone)]
pub struct MigrateArgs {
#[arg(long, value_enum)]
pub from: BackendFormat,
#[arg(long, value_enum)]
pub to: BackendFormat,
#[arg(short, long)]
pub db: String,
#[arg(long)]
pub backup: bool,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct DocsArgs {
#[arg(long)]
pub kind: Option<String>,
#[arg(long)]
pub tag: Option<String>,
#[arg(long, default_value = "50")]
pub limit: usize,
}
#[derive(Parser, Debug, Clone)]
pub struct RiskArgs {
#[arg(long)]
pub function: String,
#[arg(long)]
pub file: Option<String>,
}
#[derive(Parser, Debug, Clone)]
pub struct SuggestArgs {
#[arg(long)]
pub symbol: String,
#[arg(long)]
pub file: Option<String>,
}
#[derive(Parser, Debug, Clone, Copy)]
pub struct StatsArgs {}
#[derive(Parser, Debug, Clone)]
pub struct IcfgArgs {
#[arg(long)]
pub entry: String,
#[arg(long, default_value = "3")]
pub depth: usize,
#[arg(long, default_value = "true")]
pub return_edges: bool,
#[arg(long, value_enum)]
pub format: Option<IcfgFormat>,
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum IcfgFormat {
Dot,
Json,
Human,
}
#[derive(Parser, Debug, Clone)]
pub struct DiffArgs {
#[arg(long)]
pub function: String,
#[arg(long)]
pub before_db: String,
#[arg(long)]
pub after_db: String,
#[arg(long)]
pub show_edges: bool,
#[arg(long)]
pub verbose: bool,
}
#[derive(clap::ValueEnum, Clone, Debug, Copy, PartialEq, Eq)]
pub enum BackendFormat {
Sqlite,
}
impl std::fmt::Display for BackendFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Sqlite => write!(f, "sqlite"),
}
}
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum SliceDirectionArg {
Backward,
Forward,
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum CfgFormat {
Human,
Dot,
Json,
}
pub fn resolve_db_path(cli_db: Option<String>) -> anyhow::Result<String> {
if let Some(path) = cli_db {
return Ok(path);
}
if let Ok(path) = std::env::var("MIRAGE_DB") {
return Ok(path);
}
if let Some(path) = auto_discover_db() {
eprintln!("Info: Auto-discovered database at {}", path);
return Ok(path);
}
Err(anyhow::anyhow!(
"No database specified. Use --db, set MIRAGE_DB env var, \
or run from a directory with a .db file"
))
}
fn auto_discover_db() -> Option<String> {
use std::path::Path;
let search_dirs = [".magellan", ".forge", "."];
for dir in &search_dirs {
if let Ok(entries) = std::fs::read_dir(dir) {
let mut db_files: Vec<_> = entries
.filter_map(|e| e.ok())
.filter(|e| {
let path = e.path();
path.extension().map(|ext| ext == "db").unwrap_or(false)
})
.map(|e| e.path())
.collect();
db_files.sort();
if let Some(preferred) = db_files.iter().find(|p| {
let name = p
.file_stem()
.map(|s| s.to_string_lossy())
.unwrap_or_default();
name == "magellan" || name == "mirage"
}) {
return Some(preferred.to_string_lossy().to_string());
}
if let Some(first) = db_files.first() {
return Some(first.to_string_lossy().to_string());
}
}
}
let candidates = [
".magellan/mirage.db",
".magellan/magellan.db",
"mirage.db",
"magellan.db",
"graph.db",
];
for name in &candidates {
if Path::new(name).exists() {
return Some(name.to_string());
}
}
None
}
fn detect_repo_path(db_path: &str) -> std::path::PathBuf {
use std::path::Path;
let db_path = Path::new(db_path);
let mut path = if db_path.is_absolute() {
db_path.to_path_buf()
} else {
std::env::current_dir()
.map(|cwd| cwd.join(db_path))
.unwrap_or_else(|_| db_path.to_path_buf())
};
while path.pop() {
let git_dir = path.join(".git");
if git_dir.exists() {
return path;
}
}
Path::new(".").to_path_buf()
}
pub mod cmds;
pub mod responses;
pub mod tests;