context_bar_core/
state_writer.rs1use std::{
2 fs::{self, File},
3 io::Write,
4 path::{Path, PathBuf},
5 time::{SystemTime, UNIX_EPOCH},
6};
7
8use crate::agent_context;
9use crate::context_engine::{ContextSnapshot, render_window_markdown};
10use crate::hud;
11
12#[cfg(target_arch = "wasm32")]
13use zed_extension_api::serde_json;
14
15#[derive(Clone, Debug)]
16pub struct StateWriteResult {
17 pub state_path: PathBuf,
18 pub now_brief_path: PathBuf,
19 pub session_brief_path: PathBuf,
20 pub week_brief_path: PathBuf,
21 pub agent_brief_path: PathBuf,
22 pub claude_brief_path: PathBuf,
23 pub hud_path: PathBuf,
24}
25
26pub fn write(root: &Path, snapshot: &ContextSnapshot) -> Result<StateWriteResult, String> {
32 let state_dir = root.join(".context-bar");
33 fs::create_dir_all(&state_dir)
34 .map_err(|error| format!("failed to create {}: {error}", state_dir.display()))?;
35
36 let state_path = state_dir.join("state.json");
37 let now_brief_path = state_dir.join("brief-now.md");
38 let session_brief_path = state_dir.join("brief-session.md");
39 let week_brief_path = state_dir.join("brief-week.md");
40 let agent_brief_path = state_dir.join("AGENT.md");
41 let claude_brief_path = root.join("CLAUDE.md");
42 let hud_path = state_dir.join("hud.md");
43
44 let json = serde_json::to_string_pretty(snapshot)
45 .map_err(|error| format!("failed to serialize state.json: {error}"))?;
46
47 let agent_md = agent_context::render(snapshot);
49
50 atomic_write(&state_path, json.as_bytes())?;
51 atomic_write(
52 &now_brief_path,
53 render_window_markdown(snapshot, "now").as_bytes(),
54 )?;
55 atomic_write(
56 &session_brief_path,
57 render_window_markdown(snapshot, "session").as_bytes(),
58 )?;
59 atomic_write(
60 &week_brief_path,
61 render_window_markdown(snapshot, "week").as_bytes(),
62 )?;
63 atomic_write(&agent_brief_path, agent_md.as_bytes())?;
64 atomic_write(&claude_brief_path, agent_md.as_bytes())?;
65 atomic_write(&hud_path, hud::render(snapshot, &snapshot.usage).as_bytes())?;
66
67 Ok(StateWriteResult {
68 state_path,
69 now_brief_path,
70 session_brief_path,
71 week_brief_path,
72 agent_brief_path,
73 claude_brief_path,
74 hud_path,
75 })
76}
77
78pub(crate) fn atomic_write(path: &Path, bytes: &[u8]) -> Result<(), String> {
82 let nanos = SystemTime::now()
83 .duration_since(UNIX_EPOCH)
84 .unwrap_or_default()
85 .as_nanos();
86 let pid = std::process::id();
87 let orig_ext = path
88 .extension()
89 .and_then(|e| e.to_str())
90 .unwrap_or("");
91 let suffix = if orig_ext.is_empty() {
92 format!("tmp.{pid}.{nanos}")
93 } else {
94 format!("{orig_ext}.tmp.{pid}.{nanos}")
95 };
96 let tmp = path.with_extension(suffix);
97
98 {
99 let mut f = File::create(&tmp)
100 .map_err(|error| format!("failed to create {}: {error}", tmp.display()))?;
101 f.write_all(bytes)
102 .map_err(|error| format!("failed to write {}: {error}", tmp.display()))?;
103 f.sync_all()
105 .map_err(|error| format!("failed to fsync {}: {error}", tmp.display()))?;
106 }
107
108 fs::rename(&tmp, path).map_err(|error| {
109 let _ = fs::remove_file(&tmp);
110 format!("failed to rename {} -> {}: {error}", tmp.display(), path.display())
111 })
112}