Skip to main content

kaizen/store/sqlite/
artifact_windows.rs

1use super::*;
2
3impl Store {
4    /// Distinct `(session_id, path)` for sessions with activity in the time window.
5    pub fn files_touched_in_window(
6        &self,
7        workspace: &str,
8        start_ms: u64,
9        end_ms: u64,
10    ) -> Result<Vec<(String, String)>> {
11        let mut stmt = self.conn.prepare(
12            "SELECT DISTINCT ft.session_id, ft.path
13             FROM files_touched ft
14             JOIN sessions s ON s.id = ft.session_id
15             WHERE s.workspace = ?1
16               AND EXISTS (
17                 SELECT 1 FROM events e
18                 JOIN sessions ss ON ss.id = e.session_id
19                 WHERE e.session_id = ft.session_id
20                   AND (
21                     (e.ts_ms >= ?2 AND e.ts_ms <= ?3)
22                     OR (e.ts_ms < ?4 AND ss.started_at_ms >= ?2 AND ss.started_at_ms <= ?3)
23                   )
24               )
25             ORDER BY ft.session_id, ft.path",
26        )?;
27        let out: Vec<(String, String)> = stmt
28            .query_map(
29                params![
30                    workspace,
31                    start_ms as i64,
32                    end_ms as i64,
33                    SYNTHETIC_TS_CEILING_MS,
34                ],
35                |r| Ok((r.get(0)?, r.get(1)?)),
36            )?
37            .filter_map(|r| r.ok())
38            .collect();
39        Ok(out)
40    }
41
42    /// Distinct skill slugs referenced in `skills_used` for a workspace since `since_ms`
43    /// (any session with an indexed skill row; join events optional — use row existence).
44    pub fn skills_used_since(&self, workspace: &str, since_ms: u64) -> Result<Vec<String>> {
45        let mut stmt = self.conn.prepare(
46            "SELECT DISTINCT su.skill
47             FROM skills_used su
48             JOIN sessions s ON s.id = su.session_id
49             WHERE s.workspace = ?1
50               AND EXISTS (
51                 SELECT 1 FROM events e
52                 JOIN sessions ss ON ss.id = e.session_id
53                 WHERE e.session_id = su.session_id
54                   AND (e.ts_ms >= ?2 OR (e.ts_ms < ?3 AND ss.started_at_ms >= ?2))
55               )
56             ORDER BY su.skill",
57        )?;
58        let out: Vec<String> = stmt
59            .query_map(
60                params![workspace, since_ms as i64, SYNTHETIC_TS_CEILING_MS],
61                |r| r.get::<_, String>(0),
62            )?
63            .filter_map(|r| r.ok())
64            .filter(|s: &String| crate::store::event_index::is_valid_slug(s))
65            .collect();
66        Ok(out)
67    }
68
69    /// Distinct `(session_id, skill)` for sessions with activity in the time window.
70    pub fn skills_used_in_window(
71        &self,
72        workspace: &str,
73        start_ms: u64,
74        end_ms: u64,
75    ) -> Result<Vec<(String, String)>> {
76        let mut stmt = self.conn.prepare(
77            "SELECT DISTINCT su.session_id, su.skill
78             FROM skills_used su
79             JOIN sessions s ON s.id = su.session_id
80             WHERE s.workspace = ?1
81               AND EXISTS (
82                 SELECT 1 FROM events e
83                 JOIN sessions ss ON ss.id = e.session_id
84                 WHERE e.session_id = su.session_id
85                   AND (
86                     (e.ts_ms >= ?2 AND e.ts_ms <= ?3)
87                     OR (e.ts_ms < ?4 AND ss.started_at_ms >= ?2 AND ss.started_at_ms <= ?3)
88                   )
89               )
90             ORDER BY su.session_id, su.skill",
91        )?;
92        let out: Vec<(String, String)> = stmt
93            .query_map(
94                params![
95                    workspace,
96                    start_ms as i64,
97                    end_ms as i64,
98                    SYNTHETIC_TS_CEILING_MS,
99                ],
100                |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)),
101            )?
102            .filter_map(|r| r.ok())
103            .filter(|(_, skill): &(String, String)| crate::store::event_index::is_valid_slug(skill))
104            .collect();
105        Ok(out)
106    }
107
108    /// Distinct rule stems referenced in `rules_used` for a workspace since `since_ms`.
109    pub fn rules_used_since(&self, workspace: &str, since_ms: u64) -> Result<Vec<String>> {
110        let mut stmt = self.conn.prepare(
111            "SELECT DISTINCT ru.rule
112             FROM rules_used ru
113             JOIN sessions s ON s.id = ru.session_id
114             WHERE s.workspace = ?1
115               AND EXISTS (
116                 SELECT 1 FROM events e
117                 JOIN sessions ss ON ss.id = e.session_id
118                 WHERE e.session_id = ru.session_id
119                   AND (e.ts_ms >= ?2 OR (e.ts_ms < ?3 AND ss.started_at_ms >= ?2))
120               )
121             ORDER BY ru.rule",
122        )?;
123        let out: Vec<String> = stmt
124            .query_map(
125                params![workspace, since_ms as i64, SYNTHETIC_TS_CEILING_MS],
126                |r| r.get::<_, String>(0),
127            )?
128            .filter_map(|r| r.ok())
129            .filter(|s: &String| crate::store::event_index::is_valid_slug(s))
130            .collect();
131        Ok(out)
132    }
133
134    /// Distinct `(session_id, rule)` for sessions with activity in the time window.
135    pub fn rules_used_in_window(
136        &self,
137        workspace: &str,
138        start_ms: u64,
139        end_ms: u64,
140    ) -> Result<Vec<(String, String)>> {
141        let mut stmt = self.conn.prepare(
142            "SELECT DISTINCT ru.session_id, ru.rule
143             FROM rules_used ru
144             JOIN sessions s ON s.id = ru.session_id
145             WHERE s.workspace = ?1
146               AND EXISTS (
147                 SELECT 1 FROM events e
148                 JOIN sessions ss ON ss.id = e.session_id
149                 WHERE e.session_id = ru.session_id
150                   AND (
151                     (e.ts_ms >= ?2 AND e.ts_ms <= ?3)
152                     OR (e.ts_ms < ?4 AND ss.started_at_ms >= ?2 AND ss.started_at_ms <= ?3)
153                   )
154               )
155             ORDER BY ru.session_id, ru.rule",
156        )?;
157        let out: Vec<(String, String)> = stmt
158            .query_map(
159                params![
160                    workspace,
161                    start_ms as i64,
162                    end_ms as i64,
163                    SYNTHETIC_TS_CEILING_MS,
164                ],
165                |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)),
166            )?
167            .filter_map(|r| r.ok())
168            .filter(|(_, rule): &(String, String)| crate::store::event_index::is_valid_slug(rule))
169            .collect();
170        Ok(out)
171    }
172}