winx_code_agent/utils/
workspace_stats.rs1use crate::errors::{Result, WinxError};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7const MAX_ACTIVE_FILES: usize = 30;
8
9#[derive(Debug, Default, Serialize, Deserialize)]
10struct WorkspaceStats {
11 files: HashMap<String, FileStats>,
12}
13
14#[derive(Debug, Default, Serialize, Deserialize)]
15struct FileStats {
16 reads: u64,
17 writes: u64,
18 edits: u64,
19}
20
21pub fn record_read(root: &Path, path: &Path) -> Result<()> {
22 record(root, path, |stats| stats.reads += 1)
23}
24
25pub fn record_write(root: &Path, path: &Path) -> Result<()> {
26 record(root, path, |stats| stats.writes += 1)
27}
28
29pub fn record_edit(root: &Path, path: &Path) -> Result<()> {
30 record(root, path, |stats| stats.edits += 1)
31}
32
33pub fn active_files(root: &Path) -> Vec<String> {
34 let Ok(stats) = load(root) else {
35 return Vec::new();
36 };
37
38 let mut files = stats.files.into_iter().collect::<Vec<_>>();
39 files.sort_by_key(|(path, stats)| {
40 let score = stats.reads + (stats.edits * 4) + (stats.writes * 3);
41 (std::cmp::Reverse(score), path.clone())
42 });
43 files.truncate(MAX_ACTIVE_FILES);
44 files.into_iter().map(|(path, _)| path).collect()
45}
46
47fn record(root: &Path, path: &Path, update: impl FnOnce(&mut FileStats)) -> Result<()> {
48 let relative = path.strip_prefix(root).unwrap_or(path).to_string_lossy().to_string();
49 let mut stats = load(root).unwrap_or_default();
50 update(stats.files.entry(relative).or_default());
51 save(root, &stats)
52}
53
54fn load(root: &Path) -> Result<WorkspaceStats> {
55 let path = stats_path(root);
56 if !path.exists() {
57 return Ok(WorkspaceStats::default());
58 }
59 let content = fs::read_to_string(&path).map_err(|e| WinxError::FileAccessError {
60 path: path.clone(),
61 message: format!("Failed to read workspace stats: {e}"),
62 })?;
63 serde_json::from_str(&content)
64 .map_err(|e| WinxError::SerializationError(format!("Failed to parse workspace stats: {e}")))
65}
66
67fn save(root: &Path, stats: &WorkspaceStats) -> Result<()> {
68 let path = stats_path(root);
69 if let Some(parent) = path.parent() {
70 fs::create_dir_all(parent).map_err(|e| WinxError::FileAccessError {
71 path: parent.to_path_buf(),
72 message: format!("Failed to create workspace stats directory: {e}"),
73 })?;
74 }
75 let content = serde_json::to_string_pretty(stats)
76 .map_err(|e| WinxError::SerializationError(format!("Failed to serialize stats: {e}")))?;
77 fs::write(&path, content).map_err(|e| WinxError::FileAccessError {
78 path,
79 message: format!("Failed to write workspace stats: {e}"),
80 })
81}
82
83fn stats_path(root: &Path) -> PathBuf {
84 root.join(".winx").join("workspace_stats.json")
85}