1use crate::errors::CoreResult;
7use std::collections::BTreeMap;
8use std::io::BufRead;
9use std::path::{Path, PathBuf};
10
11#[derive(Debug, Clone)]
13pub struct CommandStats {
14 pub counts: BTreeMap<String, u64>,
16}
17
18pub fn collect_jsonl_files(dir: &Path) -> CoreResult<Vec<PathBuf>> {
23 let mut out = Vec::new();
24 collect_jsonl_files_recursive(dir, &mut out);
25 Ok(out)
26}
27
28fn collect_jsonl_files_recursive(dir: &Path, out: &mut Vec<PathBuf>) {
29 let Ok(entries) = std::fs::read_dir(dir) else {
30 return;
31 };
32 for e in entries {
33 let Ok(e) = e else {
34 continue;
35 };
36 let path = e.path();
37 if path.is_dir() {
38 collect_jsonl_files_recursive(&path, out);
39 continue;
40 }
41 let Some(ext) = path.extension().and_then(|s| s.to_str()) else {
42 continue;
43 };
44 if ext == "jsonl" {
45 out.push(path);
46 }
47 }
48}
49
50pub fn compute_command_stats(log_dir: &Path) -> CoreResult<CommandStats> {
57 let mut counts: BTreeMap<String, u64> = BTreeMap::new();
58 for id in known_command_ids() {
59 counts.insert(id.to_string(), 0);
60 }
61
62 let files = collect_jsonl_files(log_dir)?;
63
64 for path in files {
65 let Ok(f) = std::fs::File::open(&path) else {
66 continue;
67 };
68 let reader = std::io::BufReader::new(f);
69 for line in reader.lines() {
70 let Ok(line) = line else {
71 continue;
72 };
73 let line = line.trim();
74 if line.is_empty() {
75 continue;
76 }
77
78 #[derive(serde::Deserialize)]
79 struct Event {
80 event_type: Option<String>,
81 command_id: Option<String>,
82 }
83
84 let Ok(ev) = serde_json::from_str::<Event>(line) else {
85 continue;
86 };
87 let Some(event_type) = ev.event_type else {
88 continue;
89 };
90 if event_type != "command_end" {
91 continue;
92 }
93 let Some(command_id) = ev.command_id else {
94 continue;
95 };
96
97 let entry = counts.entry(command_id).or_insert(0);
98 *entry = entry.saturating_add(1);
99 }
100 }
101
102 Ok(CommandStats { counts })
103}
104
105pub fn known_command_ids() -> &'static [&'static str] {
110 &[
111 "ito.init",
112 "ito.update",
113 "ito.list",
114 "ito.config.path",
115 "ito.config.list",
116 "ito.config.get",
117 "ito.config.set",
118 "ito.config.unset",
119 "ito.agent_config.init",
120 "ito.agent_config.summary",
121 "ito.agent_config.get",
122 "ito.agent_config.set",
123 "ito.create.module",
124 "ito.create.change",
125 "ito.new.change",
126 "ito.plan.init",
127 "ito.plan.status",
128 "ito.tasks.init",
129 "ito.tasks.status",
130 "ito.tasks.next",
131 "ito.tasks.start",
132 "ito.tasks.complete",
133 "ito.tasks.shelve",
134 "ito.tasks.unshelve",
135 "ito.tasks.add",
136 "ito.tasks.show",
137 "ito.workflow.init",
138 "ito.workflow.list",
139 "ito.workflow.show",
140 "ito.status",
141 "ito.stats",
142 "ito.templates",
143 "ito.instructions",
144 "ito.x_instructions",
145 "ito.agent.instruction",
146 "ito.show",
147 "ito.validate",
148 "ito.ralph",
149 "ito.loop",
150 "ito.grep",
151 ]
152}