context_bar_core/
agent_context.rs1use std::path::Path;
12
13use crate::context_engine::{ContextSnapshot, WindowSummary};
14
15pub fn render(snapshot: &ContextSnapshot) -> String {
16 let mut out = String::new();
17
18 let repo = Path::new(&snapshot.worktree_root)
20 .file_name()
21 .and_then(|n| n.to_str())
22 .unwrap_or(&snapshot.worktree_root);
23 let ts = snapshot
25 .updated_at
26 .get(..16)
27 .unwrap_or(&snapshot.updated_at)
28 .replace('T', " ");
29 out.push_str(&format!(
30 "# AGENT BRIEF — {} · {} · {}Z\n\n",
31 repo, snapshot.branch, ts
32 ));
33
34 out.push_str("## Current focus\n");
36 let now_files = &snapshot.now.top_files;
37 if now_files.is_empty() {
38 out.push_str("No file activity in the last 15 min.\n");
39 } else {
40 let primary = &now_files[0];
41 let also: Vec<&str> = now_files[1..].iter().take(3).map(|s| s.as_str()).collect();
42 if also.is_empty() {
43 out.push_str(&format!("Active: {}\n", primary));
44 } else {
45 out.push_str(&format!("Active: {} | {}\n", primary, also.join(" ")));
46 }
47 if let Some(area) = snapshot.now.focus_areas.first() {
48 out.push_str(&format!("Area: {}/\n", area));
49 }
50 }
51 out.push('\n');
52
53 let changes = &snapshot.now.change_summary;
55 if !changes.is_empty() {
56 out.push_str("## Open loops\n");
57 let staged: Vec<&str> = changes
58 .iter()
59 .filter(|c| c.staged)
60 .map(|c| c.path.as_str())
61 .collect();
62 let modified: Vec<&str> = changes
63 .iter()
64 .filter(|c| c.unstaged && c.code != "??")
65 .map(|c| c.path.as_str())
66 .collect();
67 let untracked: Vec<&str> = changes
68 .iter()
69 .filter(|c| c.code == "??")
70 .map(|c| c.path.as_str())
71 .collect();
72
73 if !staged.is_empty() {
74 out.push_str(&format!(
75 "- Staged ({}): {}\n",
76 staged.len(),
77 staged.join(" ")
78 ));
79 }
80 if !modified.is_empty() {
81 out.push_str(&format!(
82 "- Unstaged ({}): {}\n",
83 modified.len(),
84 modified.join(" ")
85 ));
86 }
87 if !untracked.is_empty() {
88 out.push_str(&format!(
89 "- Untracked ({}): {}\n",
90 untracked.len(),
91 untracked.join(" ")
92 ));
93 }
94 out.push('\n');
95 }
96
97 render_now(&mut out, &snapshot.now);
99
100 render_session(&mut out, &snapshot.session);
102
103 render_week(&mut out, &snapshot.week, &snapshot.branch);
105
106 out.push_str("---\n");
108 out.push_str(
109 "context.json is the machine-readable source. Regenerated on save; do not edit.\n",
110 );
111
112 out
113}
114
115fn render_now(out: &mut String, window: &WindowSummary) {
116 if window.top_files.is_empty() {
117 return;
118 }
119 out.push_str("## Now (last 15 min)\n");
120 let files: Vec<&str> = window.top_files.iter().take(5).map(|s| s.as_str()).collect();
121 out.push_str(&format!("{}\n\n", files.join(" ")));
122}
123
124fn render_session(out: &mut String, window: &WindowSummary) {
125 out.push_str("## Session (last 5 hr)\n");
126
127 if let Some(area) = window.focus_areas.first() {
128 out.push_str(&format!("Focus: {}/\n", area));
129 }
130
131 if !window.commit_refs.is_empty() {
132 let commits: Vec<String> = window
133 .commit_refs
134 .iter()
135 .take(4)
136 .map(|c| format!("{} {}", c.sha, c.subject))
137 .collect();
138 out.push_str(&format!("Commits: {}\n", commits.join(" · ")));
139 } else {
140 out.push_str("Commits: none in window\n");
141 }
142
143 out.push('\n');
144}
145
146fn render_week(out: &mut String, window: &WindowSummary, branch: &str) {
147 out.push_str("## Week (last 7 days)\n");
148
149 let n = window.commit_refs.len();
150 if n == 0 {
151 out.push_str("No commits in window.\n");
152 } else {
153 let subjects: Vec<&str> = window
154 .commit_refs
155 .iter()
156 .take(3)
157 .map(|c| c.subject.as_str())
158 .collect();
159 out.push_str(&format!(
160 "Direction: {} commit(s) on {} — {}\n",
161 n,
162 branch,
163 subjects.join(" · ")
164 ));
165 }
166
167 let signal_themes: Vec<&str> = window
169 .themes
170 .iter()
171 .filter(|t| !t.starts_with("focus on") && t.as_str() != "root-level edits")
172 .map(|t| t.as_str())
173 .take(3)
174 .collect();
175 if !signal_themes.is_empty() {
176 out.push_str(&format!("Themes: {}\n", signal_themes.join(" · ")));
177 }
178
179 out.push('\n');
180}