Skip to main content

ccboard_types/models/
invocations.rs

1//! Invocation statistics for agents, commands, and skills
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Statistics about agent/command/skill invocations across all sessions
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct InvocationStats {
10    /// Agent invocations (subagent_type -> count)
11    /// Example: "technical-writer" -> 5
12    pub agents: HashMap<String, usize>,
13
14    /// Command invocations (/command -> count)
15    /// Example: "/commit" -> 12
16    pub commands: HashMap<String, usize>,
17
18    /// Skill invocations (skill name -> count)
19    /// Example: "pdf-generator" -> 3
20    pub skills: HashMap<String, usize>,
21
22    /// When stats were last computed
23    pub last_computed: DateTime<Utc>,
24
25    /// Number of sessions analyzed
26    pub sessions_analyzed: usize,
27}
28
29impl InvocationStats {
30    /// Create new empty stats
31    pub fn new() -> Self {
32        Self {
33            agents: HashMap::new(),
34            commands: HashMap::new(),
35            skills: HashMap::new(),
36            last_computed: Utc::now(),
37            sessions_analyzed: 0,
38        }
39    }
40
41    /// Get total number of invocations across all types
42    pub fn total_invocations(&self) -> usize {
43        self.agents.values().sum::<usize>()
44            + self.commands.values().sum::<usize>()
45            + self.skills.values().sum::<usize>()
46    }
47
48    /// Merge another InvocationStats into this one
49    pub fn merge(&mut self, other: &InvocationStats) {
50        for (name, count) in &other.agents {
51            *self.agents.entry(name.clone()).or_insert(0) += count;
52        }
53        for (name, count) in &other.commands {
54            *self.commands.entry(name.clone()).or_insert(0) += count;
55        }
56        for (name, count) in &other.skills {
57            *self.skills.entry(name.clone()).or_insert(0) += count;
58        }
59        self.sessions_analyzed += other.sessions_analyzed;
60        // Keep the most recent timestamp
61        if other.last_computed > self.last_computed {
62            self.last_computed = other.last_computed;
63        }
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_total_invocations() {
73        let mut stats = InvocationStats::new();
74        stats.agents.insert("technical-writer".to_string(), 5);
75        stats.commands.insert("/commit".to_string(), 12);
76        stats.skills.insert("pdf-generator".to_string(), 3);
77
78        assert_eq!(stats.total_invocations(), 20);
79    }
80
81    #[test]
82    fn test_merge() {
83        let mut stats1 = InvocationStats::new();
84        stats1.agents.insert("debugger".to_string(), 3);
85        stats1.commands.insert("/commit".to_string(), 5);
86        stats1.sessions_analyzed = 10;
87
88        let mut stats2 = InvocationStats::new();
89        stats2.agents.insert("debugger".to_string(), 2);
90        stats2.agents.insert("code-reviewer".to_string(), 1);
91        stats2.commands.insert("/commit".to_string(), 7);
92        stats2.sessions_analyzed = 5;
93
94        stats1.merge(&stats2);
95
96        assert_eq!(stats1.agents.get("debugger"), Some(&5));
97        assert_eq!(stats1.agents.get("code-reviewer"), Some(&1));
98        assert_eq!(stats1.commands.get("/commit"), Some(&12));
99        assert_eq!(stats1.sessions_analyzed, 15);
100    }
101}