use anyhow::{Context, Result};
use clap::Args;
use std::path::Path;
use crate::output::json::JsonOutput;
fn find_scope_bin() -> std::path::PathBuf {
if let Ok(exe) = std::env::current_exe() {
let s = exe.to_string_lossy();
if !s.contains(" (deleted)") && exe.exists() {
return exe;
}
}
std::path::PathBuf::from("scope")
}
fn run_subprocess(project_root: &Path, args: &[&str]) -> Result<()> {
let scope_bin = find_scope_bin();
let status = std::process::Command::new(scope_bin)
.args(args)
.current_dir(project_root)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::inherit())
.status()
.with_context(|| format!("Failed to run scope {}", args[0]))?;
if !status.success() {
anyhow::bail!(
"scope {} failed with exit code {:?}",
args[0],
status.code()
);
}
Ok(())
}
#[derive(Args, Debug)]
pub struct SetupArgs {
#[arg(long)]
pub preload: bool,
#[arg(long, short = 'j')]
pub json: bool,
}
fn build_preload_snippet(scope_dir: &Path) -> Result<Option<String>> {
let db_path = scope_dir.join("graph.db");
if !db_path.exists() {
return Ok(None);
}
let graph = crate::core::graph::Graph::open(&db_path)?;
let stats_line = format!(
"{} files, {} symbols, {} edges",
graph.file_count()?,
graph.symbol_count()?,
graph.edge_count()?,
);
let core = graph.get_symbols_by_importance(10)?;
let core_lines: Vec<String> = core
.iter()
.map(|(sym, count)| format!(" {} ({}) — {} callers", sym.name, sym.file_path, count))
.collect();
let dirs = graph.get_directory_stats()?;
let arch_lines: Vec<String> = dirs
.iter()
.map(|(dir, files, syms)| format!(" {dir} — {files} files, {syms} symbols"))
.collect();
Ok(Some(format!(
"\n### Preloaded Architecture (scope map)\n\n\
Stats: {stats_line}\n\n\
Core symbols:\n{}\n\n\
Architecture:\n{}\n",
core_lines.join("\n"),
arch_lines.join("\n"),
)))
}
pub fn run(args: &SetupArgs, project_root: &Path) -> Result<()> {
let scope_dir = project_root.join(".scope");
let mut did_init = false;
let mut did_claude_md = false;
let mut did_skill = false;
if !scope_dir.exists() {
if args.json {
run_subprocess(project_root, &["init"])?;
} else {
println!("Initialising scope...");
let init_args = crate::commands::init::InitArgs { json: false };
crate::commands::init::run(&init_args, project_root)?;
}
did_init = true;
} else if !args.json {
println!("scope already initialised, skipping init.");
}
if args.json {
run_subprocess(project_root, &["index", "--full"])?;
} else {
println!("Building index...");
let index_args = crate::commands::index::IndexArgs {
full: true,
json: false,
watch: false,
};
crate::commands::index::run(&index_args, project_root)?;
}
let claude_md_path = project_root.join("CLAUDE.md");
let snippet_marker = "## Code Navigation";
let existing = std::fs::read_to_string(&claude_md_path).unwrap_or_default();
let has_section = existing.contains(snippet_marker);
let has_preload = existing.contains("### Preloaded Architecture");
let needs_preload_upgrade = has_section && args.preload && !has_preload;
if has_section && !needs_preload_upgrade {
if !args.json {
println!("CLAUDE.md already has Code Navigation section, skipping.");
}
} else if needs_preload_upgrade {
if let Some(preload_snippet) = build_preload_snippet(&scope_dir)? {
let mut content = existing;
content.push_str(&preload_snippet);
std::fs::write(&claude_md_path, content)?;
did_claude_md = true;
if !args.json {
println!("Added preloaded architecture to existing CLAUDE.md");
}
}
} else {
let mut snippet = format!(
"\n\n{snippet_marker}\n\n\
This project has [Scope](https://github.com/rynhardt-potgieter/scope) CLI installed.\n\
Run `scope status` to check availability and `scope map` for a repo overview.\n\n\
When dispatching subagents that need to navigate, search, or understand code,\n\
include the `code-navigation` skill or instruct them to read\n\
`.claude/skills/code-navigation/SKILL.md` before starting.\n"
);
if args.preload {
if let Some(preload_snippet) = build_preload_snippet(&scope_dir)? {
snippet.push_str(&preload_snippet);
}
}
let mut content = existing;
content.push_str(&snippet);
std::fs::write(&claude_md_path, content)?;
did_claude_md = true;
if !args.json {
println!("Appended Code Navigation section to CLAUDE.md");
}
}
let skill_dir = project_root.join(".claude/skills/code-navigation");
if !skill_dir.exists() {
std::fs::create_dir_all(&skill_dir)?;
let skill_content = include_str!("../../skills/code-navigation/SKILL.md");
std::fs::write(skill_dir.join("SKILL.md"), skill_content)?;
did_skill = true;
if !args.json {
println!("Installed code-navigation skill to .claude/skills/");
}
} else if !args.json {
println!("code-navigation skill already installed, skipping.");
}
if args.json {
let data = serde_json::json!({
"initialized": did_init,
"indexed": true,
"preloaded": args.preload,
"claude_md_updated": did_claude_md,
"skill_installed": did_skill,
"scope_dir": ".scope/",
});
let envelope = JsonOutput {
command: "setup",
symbol: None,
data: &data,
truncated: false,
total: 1,
};
println!("{}", serde_json::to_string_pretty(&envelope)?);
} else if args.preload {
println!(
"\nSetup complete with preloading. Benchmark data shows this saves ~32% on agent cost."
);
} else {
println!("\nSetup complete. Run with --preload to bake architecture into CLAUDE.md for 32% agent cost savings.");
}
Ok(())
}