use crate::errors::CoreResult;
use std::collections::BTreeMap;
use std::io::BufRead;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct CommandStats {
pub counts: BTreeMap<String, u64>,
}
pub fn collect_jsonl_files(dir: &Path) -> CoreResult<Vec<PathBuf>> {
let mut out = Vec::new();
collect_jsonl_files_recursive(dir, &mut out);
Ok(out)
}
fn collect_jsonl_files_recursive(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for e in entries {
let Ok(e) = e else {
continue;
};
let path = e.path();
if path.is_dir() {
collect_jsonl_files_recursive(&path, out);
continue;
}
let Some(ext) = path.extension().and_then(|s| s.to_str()) else {
continue;
};
if ext == "jsonl" {
out.push(path);
}
}
}
pub fn compute_command_stats(log_dir: &Path) -> CoreResult<CommandStats> {
let mut counts: BTreeMap<String, u64> = BTreeMap::new();
for id in known_command_ids() {
counts.insert(id.to_string(), 0);
}
let files = collect_jsonl_files(log_dir)?;
for path in files {
let Ok(f) = std::fs::File::open(&path) else {
continue;
};
let reader = std::io::BufReader::new(f);
for line in reader.lines() {
let Ok(line) = line else {
continue;
};
let line = line.trim();
if line.is_empty() {
continue;
}
#[derive(serde::Deserialize)]
struct Event {
event_type: Option<String>,
command_id: Option<String>,
}
let Ok(ev) = serde_json::from_str::<Event>(line) else {
continue;
};
let Some(event_type) = ev.event_type else {
continue;
};
if event_type != "command_end" {
continue;
}
let Some(command_id) = ev.command_id else {
continue;
};
let entry = counts.entry(command_id).or_insert(0);
*entry = entry.saturating_add(1);
}
}
Ok(CommandStats { counts })
}
pub fn known_command_ids() -> &'static [&'static str] {
&[
"ito.init",
"ito.update",
"ito.list",
"ito.config.path",
"ito.config.list",
"ito.config.get",
"ito.config.set",
"ito.config.unset",
"ito.agent_config.init",
"ito.agent_config.summary",
"ito.agent_config.get",
"ito.agent_config.set",
"ito.create.module",
"ito.create.change",
"ito.new.change",
"ito.plan.init",
"ito.plan.status",
"ito.tasks.init",
"ito.tasks.status",
"ito.tasks.next",
"ito.tasks.start",
"ito.tasks.complete",
"ito.tasks.shelve",
"ito.tasks.unshelve",
"ito.tasks.add",
"ito.tasks.show",
"ito.workflow.init",
"ito.workflow.list",
"ito.workflow.show",
"ito.status",
"ito.stats",
"ito.templates",
"ito.instructions",
"ito.x_instructions",
"ito.agent.instruction",
"ito.show",
"ito.validate",
"ito.ralph",
"ito.loop",
"ito.grep",
]
}