mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-10 11:23:32 +08:00
feat(ecc2): persist file activity diff previews
This commit is contained in:
@@ -134,6 +134,7 @@ pub struct FileActivityEntry {
|
||||
pub action: FileActivityAction,
|
||||
pub path: String,
|
||||
pub summary: String,
|
||||
pub diff_preview: Option<String>,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
|
||||
@@ -759,6 +759,8 @@ impl StateStore {
|
||||
struct ToolActivityFileEvent {
|
||||
path: String,
|
||||
action: String,
|
||||
#[serde(default)]
|
||||
diff_preview: Option<String>,
|
||||
}
|
||||
|
||||
let file = File::open(metrics_path)
|
||||
@@ -800,6 +802,7 @@ impl StateStore {
|
||||
.map(|path| PersistedFileEvent {
|
||||
path,
|
||||
action: infer_file_activity_action(&row.tool_name),
|
||||
diff_preview: None,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
@@ -814,6 +817,7 @@ impl StateStore {
|
||||
path,
|
||||
action: parse_file_activity_action(&event.action)
|
||||
.unwrap_or_else(|| infer_file_activity_action(&row.tool_name)),
|
||||
diff_preview: normalize_optional_string(event.diff_preview),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
@@ -1594,6 +1598,7 @@ impl StateStore {
|
||||
Some(PersistedFileEvent {
|
||||
path,
|
||||
action: infer_file_activity_action(&tool_name),
|
||||
diff_preview: None,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
@@ -1605,6 +1610,7 @@ impl StateStore {
|
||||
action: event.action,
|
||||
path: event.path,
|
||||
summary: summary.clone(),
|
||||
diff_preview: event.diff_preview,
|
||||
timestamp: occurred_at,
|
||||
});
|
||||
if events.len() >= limit {
|
||||
@@ -1621,6 +1627,8 @@ impl StateStore {
|
||||
struct PersistedFileEvent {
|
||||
path: String,
|
||||
action: FileActivityAction,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
diff_preview: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_persisted_file_events(value: &str) -> Option<Vec<PersistedFileEvent>> {
|
||||
@@ -1635,6 +1643,7 @@ fn parse_persisted_file_events(value: &str) -> Option<Vec<PersistedFileEvent>> {
|
||||
Some(PersistedFileEvent {
|
||||
path,
|
||||
action: event.action,
|
||||
diff_preview: normalize_optional_string(event.diff_preview),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
@@ -1656,6 +1665,17 @@ fn parse_file_activity_action(value: &str) -> Option<FileActivityAction> {
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_optional_string(value: Option<String>) -> Option<String> {
|
||||
value.and_then(|value| {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(trimmed.to_string())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn infer_file_activity_action(tool_name: &str) -> FileActivityAction {
|
||||
let tool_name = tool_name.trim().to_ascii_lowercase();
|
||||
if tool_name.contains("read") {
|
||||
@@ -1938,6 +1958,50 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_file_activity_preserves_diff_previews() -> Result<()> {
|
||||
let tempdir = TestDir::new("store-file-activity-diffs")?;
|
||||
let db = StateStore::open(&tempdir.path().join("state.db"))?;
|
||||
let now = Utc::now();
|
||||
|
||||
db.insert_session(&Session {
|
||||
id: "session-1".to_string(),
|
||||
task: "sync tools".to_string(),
|
||||
agent_type: "claude".to_string(),
|
||||
working_dir: PathBuf::from("/tmp"),
|
||||
state: SessionState::Running,
|
||||
pid: None,
|
||||
worktree: None,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
last_heartbeat_at: now,
|
||||
metrics: SessionMetrics::default(),
|
||||
})?;
|
||||
|
||||
let metrics_dir = tempdir.path().join("metrics");
|
||||
fs::create_dir_all(&metrics_dir)?;
|
||||
let metrics_path = metrics_dir.join("tool-usage.jsonl");
|
||||
fs::write(
|
||||
&metrics_path,
|
||||
concat!(
|
||||
"{\"id\":\"evt-1\",\"session_id\":\"session-1\",\"tool_name\":\"Edit\",\"input_summary\":\"Edit src/config.ts\",\"output_summary\":\"updated config\",\"file_paths\":[\"src/config.ts\"],\"file_events\":[{\"path\":\"src/config.ts\",\"action\":\"modify\",\"diff_preview\":\"API_URL=http://localhost:3000 -> API_URL=https://api.example.com\"}],\"timestamp\":\"2026-04-09T00:00:00Z\"}\n"
|
||||
),
|
||||
)?;
|
||||
|
||||
db.sync_tool_activity_metrics(&metrics_path)?;
|
||||
|
||||
let activity = db.list_file_activity("session-1", 10)?;
|
||||
assert_eq!(activity.len(), 1);
|
||||
assert_eq!(activity[0].action, FileActivityAction::Modify);
|
||||
assert_eq!(activity[0].path, "src/config.ts");
|
||||
assert_eq!(
|
||||
activity[0].diff_preview.as_deref(),
|
||||
Some("API_URL=http://localhost:3000 -> API_URL=https://api.example.com")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refresh_session_durations_updates_running_and_terminal_sessions() -> Result<()> {
|
||||
let tempdir = TestDir::new("store-duration-metrics")?;
|
||||
|
||||
@@ -5398,11 +5398,18 @@ fn session_state_color(state: &SessionState) -> Color {
|
||||
}
|
||||
|
||||
fn file_activity_summary(entry: &FileActivityEntry) -> String {
|
||||
format!(
|
||||
let mut summary = format!(
|
||||
"{} {}",
|
||||
file_activity_verb(entry.action.clone()),
|
||||
truncate_for_dashboard(&entry.path, 72)
|
||||
)
|
||||
);
|
||||
|
||||
if let Some(diff_preview) = entry.diff_preview.as_ref() {
|
||||
summary.push_str(" | ");
|
||||
summary.push_str(&truncate_for_dashboard(diff_preview, 56));
|
||||
}
|
||||
|
||||
summary
|
||||
}
|
||||
|
||||
fn file_activity_verb(action: crate::session::FileActivityAction) -> &'static str {
|
||||
@@ -6085,7 +6092,7 @@ mod tests {
|
||||
&metrics_path,
|
||||
concat!(
|
||||
"{\"id\":\"evt-1\",\"session_id\":\"focus-12345678\",\"tool_name\":\"Read\",\"input_summary\":\"Read src/lib.rs\",\"output_summary\":\"ok\",\"file_paths\":[\"src/lib.rs\"],\"timestamp\":\"2026-04-09T00:00:00Z\"}\n",
|
||||
"{\"id\":\"evt-2\",\"session_id\":\"focus-12345678\",\"tool_name\":\"Write\",\"input_summary\":\"Write README.md\",\"output_summary\":\"updated readme\",\"file_paths\":[\"README.md\"],\"timestamp\":\"2026-04-09T00:01:00Z\"}\n"
|
||||
"{\"id\":\"evt-2\",\"session_id\":\"focus-12345678\",\"tool_name\":\"Write\",\"input_summary\":\"Write README.md\",\"output_summary\":\"updated readme\",\"file_paths\":[\"README.md\"],\"file_events\":[{\"path\":\"README.md\",\"action\":\"create\",\"diff_preview\":\"+ # ECC 2.0\"}],\"timestamp\":\"2026-04-09T00:01:00Z\"}\n"
|
||||
),
|
||||
)?;
|
||||
dashboard.db.sync_tool_activity_metrics(&metrics_path)?;
|
||||
@@ -6095,11 +6102,13 @@ mod tests {
|
||||
let rendered = dashboard.rendered_output_text(180, 30);
|
||||
assert!(rendered.contains("read src/lib.rs"));
|
||||
assert!(rendered.contains("create README.md"));
|
||||
assert!(rendered.contains("+ # ECC 2.0"));
|
||||
assert!(!rendered.contains("files touched 2"));
|
||||
|
||||
let metrics_text = dashboard.selected_session_metrics_text();
|
||||
assert!(metrics_text.contains("Recent file activity"));
|
||||
assert!(metrics_text.contains("create README.md"));
|
||||
assert!(metrics_text.contains("+ # ECC 2.0"));
|
||||
assert!(metrics_text.contains("read src/lib.rs"));
|
||||
|
||||
let _ = fs::remove_dir_all(root);
|
||||
|
||||
Reference in New Issue
Block a user