use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(
name = "roteiro",
version,
about = "Provenance-tagged codebase knowledge graph",
long_about = "Roteiro — the pilot book for your codebase.\n\n\
One SQLite store holding structure, intent, and context as a single \
provenance-tagged knowledge graph, queryable by humans and AI agents \
alike. Subcommands are scaffolds while the graph core lands; see \
ADR-0001 and docs/BUILD_PLAN.md for the roadmap.",
arg_required_else_help = true,
propagate_version = true
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Init,
Sync {
#[arg(long)]
json: bool,
#[arg(long)]
committed: bool,
},
Check,
Import {
#[arg(long)]
from: String,
},
Render {
target: String,
},
Spec,
#[cfg(feature = "mcp")]
Serve,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let name = match cli.command {
Command::Sync { json, committed } => return run_sync(json, committed),
Command::Init => "init",
Command::Check => "check",
Command::Import { .. } => "import",
Command::Render { .. } => "render",
Command::Spec => "spec",
#[cfg(feature = "mcp")]
Command::Serve => "serve",
};
anyhow::bail!("`roteiro {name}` is not implemented yet (scaffold; see docs/BUILD_PLAN.md)")
}
fn run_sync(json: bool, committed_only: bool) -> anyhow::Result<()> {
use rto_graph::{ObjectCache, Registry, Repo, Store, sync, sync_worktree};
let cwd = std::env::current_dir()?;
let repo = Repo::discover(&cwd)?;
let store_dir = repo.git_dir().join("roteiro");
std::fs::create_dir_all(&store_dir)?;
let mut store = Store::open(&store_dir.join("graph.db"))?;
let cache = ObjectCache::open(repo.common_dir().join("roteiro").join("objects"))?;
let report = if committed_only {
sync(&mut store, &repo, &cache, &Registry)?
} else {
sync_worktree(&mut store, &repo, &cache, &Registry)?
};
if json {
println!("{}", serde_json::to_string_pretty(&report)?);
} else {
let tree = &report.tree[..report.tree.len().min(12)];
let dirty = if report.blobs_dirty > 0 {
format!(" +{} uncommitted", report.blobs_dirty)
} else {
String::new()
};
if report.no_op {
println!(
"up to date (tree {tree}{dirty}) — {} nodes, {} edges",
report.nodes, report.edges
);
} else {
println!(
"synced tree {tree}{dirty} — {} blobs ({} extracted, {} cached) → {} nodes, {} edges",
report.blobs_total,
report.blobs_extracted,
report.blobs_cached,
report.nodes,
report.edges
);
}
}
Ok(())
}