use std::path::Path;
use std::time::Instant;
use anyhow::{Context, Result};
use selfware::evolve::{
build_map, extract_rust_skeleton, fit_tier, reduce_source, ContextComposer, FitBudget,
GraphBuilder, NodeLayer, TierMeasurer, TIER_LADDER,
};
use selfware::token_count::estimate_content_tokens;
fn main() -> Result<()> {
let dump_dir = parse_dump_dir();
println!("Building evolve graph from ./src ...");
let scan_start = Instant::now();
let graph = GraphBuilder::new(Path::new("src"))
.scan_src()
.context("graph scan failed (run from the repository root)")?;
println!(
"graph: {} nodes, {} edges (scan took {:.1} ms)\n",
graph.nodes.len(),
graph.edges.len(),
scan_start.elapsed().as_secs_f64() * 1e3
);
section_per_tier(&graph);
section_fit_windows(&graph);
if let Some(dir) = dump_dir {
section_dump(&graph, &dir)?;
}
Ok(())
}
fn section_per_tier(graph: &selfware::evolve::Graph) {
println!("== Section 1: per-tier measurement (measured vs composer heuristic) ==");
println!(
"{:<13} {:>13} {:>10} {:>9} {:>13} {:>8}",
"tier", "measured tok", "time ms", "io reads", "heuristic tok", "m/h"
);
println!("{}", "-".repeat(70));
let measurer = TierMeasurer::new(graph, Path::new("."));
let mut last_io = measurer.io_reads();
for mode in TIER_LADDER {
let start = Instant::now();
let measured = measurer.measure(&mode);
let elapsed_ms = start.elapsed().as_secs_f64() * 1e3;
let io_delta = measurer.io_reads() - last_io;
last_io = measurer.io_reads();
let mut composer = ContextComposer::new(graph.clone());
composer.set_mode(mode.clone());
let heuristic = composer.estimate_tokens();
if heuristic == 0 {
println!(
"{:<13} {:>13} {:>10.1} {:>9} {:>13} {:>8}",
mode.name(),
measured,
elapsed_ms,
io_delta,
"-",
"-"
);
} else {
println!(
"{:<13} {:>13} {:>10.1} {:>9} {:>13} {:>7.2}x",
mode.name(),
measured,
elapsed_ms,
io_delta,
heuristic,
measured as f64 / heuristic as f64
);
}
}
println!();
}
fn section_fit_windows(graph: &selfware::evolve::Graph) {
println!("== Section 2: tier ladder resolution per context window (auto) ==");
println!(
"{:>9} {:>13} {:<13} {:>13} {:>5} {:>10} {:>13}",
"window",
"usable budget",
"resolved tier",
"measured tok",
"fits",
"time ms",
"cum io reads"
);
println!("{}", "-".repeat(82));
for context_length in [
8192usize, 32768, 131072, 262144, 1048576, 3_000_000, 4_500_000,
] {
let budget = FitBudget::new(context_length, 4096, 0.70);
let measurer = TierMeasurer::new(graph, Path::new("."));
let start = Instant::now();
let outcome = fit_tier(&measurer, &budget);
let elapsed_ms = start.elapsed().as_secs_f64() * 1e3;
println!(
"{:>9} {:>13} {:<13} {:>13} {:>5} {:>10.1} {:>13}",
context_length,
budget.usable(),
outcome.mode.name(),
outcome.measured_tokens,
outcome.fits,
elapsed_ms,
measurer.io_reads()
);
}
println!();
}
fn section_dump(graph: &selfware::evolve::Graph, dir: &Path) -> Result<()> {
println!("== Section 3: artifact dump -> {} ==", dir.display());
std::fs::create_dir_all(dir)
.with_context(|| format!("cannot create dump dir {}", dir.display()))?;
let map = build_map(graph, Path::new("."));
write_artifact(dir, "map.txt", &map.rendered)?;
let mut lite_parts: Vec<String> = Vec::new();
let mut compact_parts: Vec<String> = Vec::new();
for node in graph.nodes.iter().filter(|n| n.layer == NodeLayer::Code) {
let Some(rel) = node.path.as_deref() else {
continue;
};
let Ok(src) = std::fs::read_to_string(Path::new(".").join(rel)) else {
continue;
};
if rel.ends_with(".rs") {
lite_parts.push(extract_rust_skeleton(Path::new(rel), &src).render());
}
compact_parts.push(reduce_source(&src));
}
write_artifact(dir, "lite.txt", &lite_parts.join("\n\n"))?;
write_artifact(dir, "compact.txt", &compact_parts.join("\n\n"))?;
println!();
Ok(())
}
fn write_artifact(dir: &Path, name: &str, content: &str) -> Result<()> {
let path = dir.join(name);
std::fs::write(&path, content).with_context(|| format!("cannot write {}", path.display()))?;
println!(
" {:<12} {:>9} bytes {:>9} tokens",
name,
content.len(),
estimate_content_tokens(content)
);
Ok(())
}
fn parse_dump_dir() -> Option<std::path::PathBuf> {
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
if arg == "--dump" {
return args.next().map(std::path::PathBuf::from);
}
if let Some(dir) = arg.strip_prefix("--dump=") {
return Some(std::path::PathBuf::from(dir));
}
}
None
}