dx_forge/api/
branching.rs1use anyhow::Result;
4use std::path::PathBuf;
5use std::sync::Arc;
6use parking_lot::RwLock;
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
11pub struct FileChange {
12 pub path: PathBuf,
13 pub old_content: Option<String>,
14 pub new_content: String,
15 pub tool_id: String,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum BranchColor {
21 Green, Yellow, Red, NoOpinion, }
26
27#[derive(Debug, Clone)]
29pub struct BranchingVote {
30 pub voter_id: String,
31 pub color: BranchColor,
32 pub reason: String,
33 pub confidence: f32, }
35
36static mut BRANCHING_STATE: Option<Arc<RwLock<BranchingState>>> = None;
38
39struct BranchingState {
40 voters: Vec<String>,
41 pending_changes: Vec<FileChange>,
42 votes: HashMap<PathBuf, Vec<BranchingVote>>,
43 last_application: Option<Vec<PathBuf>>,
44}
45
46impl Default for BranchingState {
47 fn default() -> Self {
48 Self {
49 voters: Vec::new(),
50 pending_changes: Vec::new(),
51 votes: HashMap::new(),
52 last_application: None,
53 }
54 }
55}
56
57fn get_branching_state() -> Arc<RwLock<BranchingState>> {
58 unsafe {
59 if BRANCHING_STATE.is_none() {
60 BRANCHING_STATE = Some(Arc::new(RwLock::new(BranchingState::default())));
61 }
62 BRANCHING_STATE.as_ref().unwrap().clone()
63 }
64}
65
66pub fn apply_changes(changes: Vec<FileChange>) -> Result<Vec<PathBuf>> {
68 tracing::info!("📝 Applying {} changes with branching safety", changes.len());
69
70 let state = get_branching_state();
71 let mut state = state.write();
72
73 let mut applied_files = Vec::new();
74
75 for change in changes {
76 let color = query_predicted_branch_color(&change.path)?;
78
79 match color {
80 BranchColor::Green => {
81 apply_file_change(&change)?;
83 applied_files.push(change.path.clone());
84 tracing::info!("🟢 Auto-applied: {:?}", change.path);
85 }
86 BranchColor::Yellow => {
87 tracing::warn!("🟡 Review recommended for: {:?}", change.path);
89 prompt_review_for_yellow_conflicts(vec![change.clone()])?;
90 apply_file_change(&change)?;
92 applied_files.push(change.path.clone());
93 }
94 BranchColor::Red => {
95 tracing::error!("🔴 Manual resolution required: {:?}", change.path);
97 automatically_reject_red_conflicts(vec![change.clone()])?;
98 }
99 BranchColor::NoOpinion => {
100 apply_file_change(&change)?;
102 applied_files.push(change.path.clone());
103 }
104 }
105 }
106
107 state.last_application = Some(applied_files.clone());
108
109 Ok(applied_files)
110}
111
112pub fn apply_changes_with_preapproved_votes(changes: Vec<FileChange>) -> Result<Vec<PathBuf>> {
114 tracing::info!("⚡ Fast-path applying {} pre-approved changes", changes.len());
115
116 let mut applied_files = Vec::new();
117
118 for change in changes {
119 apply_file_change(&change)?;
120 applied_files.push(change.path.clone());
121 }
122
123 let state = get_branching_state();
124 state.write().last_application = Some(applied_files.clone());
125
126 Ok(applied_files)
127}
128
129pub fn apply_changes_force_unchecked(changes: Vec<FileChange>) -> Result<Vec<PathBuf>> {
131 tracing::warn!("⚠️ FORCE APPLYING {} changes WITHOUT SAFETY CHECKS", changes.len());
132
133 let mut applied_files = Vec::new();
134
135 for change in changes {
136 apply_file_change(&change)?;
137 applied_files.push(change.path.clone());
138 }
139
140 Ok(applied_files)
141}
142
143pub fn preview_proposed_changes(changes: Vec<FileChange>) -> Result<String> {
145 let mut preview = String::new();
146
147 preview.push_str("╔══════════════════════════════════════════════════════════════╗\n");
148 preview.push_str("║ PROPOSED CHANGES PREVIEW ║\n");
149 preview.push_str("╚══════════════════════════════════════════════════════════════╝\n\n");
150
151 for change in &changes {
152 let color = query_predicted_branch_color(&change.path)?;
153 let color_icon = match color {
154 BranchColor::Green => "🟢",
155 BranchColor::Yellow => "🟡",
156 BranchColor::Red => "🔴",
157 BranchColor::NoOpinion => "⚪",
158 };
159
160 preview.push_str(&format!("{} {:?}\n", color_icon, change.path));
161 preview.push_str(&format!(" Tool: {}\n", change.tool_id));
162 preview.push_str(&format!(" Risk: {:?}\n\n", color));
163 }
164
165 Ok(preview)
166}
167
168pub fn automatically_accept_green_conflicts(changes: Vec<FileChange>) -> Result<Vec<PathBuf>> {
170 let green_changes: Vec<FileChange> = changes.into_iter()
171 .filter(|c| query_predicted_branch_color(&c.path).ok() == Some(BranchColor::Green))
172 .collect();
173
174 tracing::info!("🟢 Auto-accepting {} green changes", green_changes.len());
175 apply_changes_with_preapproved_votes(green_changes)
176}
177
178pub fn prompt_review_for_yellow_conflicts(changes: Vec<FileChange>) -> Result<()> {
180 tracing::info!("🟡 Prompting review for {} yellow changes", changes.len());
181
182 Ok(())
186}
187
188pub fn automatically_reject_red_conflicts(changes: Vec<FileChange>) -> Result<()> {
190 tracing::error!("🔴 Rejecting {} red changes", changes.len());
191
192 for change in changes {
193 tracing::error!(" ❌ {:?} - Manual resolution required", change.path);
194 }
195
196 Ok(())
197}
198
199pub fn revert_most_recent_application() -> Result<Vec<PathBuf>> {
201 let state = get_branching_state();
202 let state = state.read();
203
204 if let Some(files) = &state.last_application {
205 tracing::info!("🔙 Reverting {} files", files.len());
206
207 Ok(files.clone())
211 } else {
212 anyhow::bail!("No recent application to revert")
213 }
214}
215
216pub fn submit_branching_vote(file: &PathBuf, vote: BranchingVote) -> Result<()> {
222 let state = get_branching_state();
223 let mut state = state.write();
224
225 state.votes
226 .entry(file.clone())
227 .or_insert_with(Vec::new)
228 .push(vote);
229
230 Ok(())
231}
232
233pub fn register_permanent_branching_voter(voter_id: String) -> Result<()> {
235 let state = get_branching_state();
236 let mut state = state.write();
237
238 if !state.voters.contains(&voter_id) {
239 tracing::info!("🗳️ Registered permanent voter: {}", voter_id);
240 state.voters.push(voter_id);
241 }
242
243 Ok(())
244}
245
246pub fn query_predicted_branch_color(file: &PathBuf) -> Result<BranchColor> {
248 let state = get_branching_state();
249 let state = state.read();
250
251 if let Some(votes) = state.votes.get(file) {
253 if votes.iter().any(|v| v.color == BranchColor::Red) {
255 return Ok(BranchColor::Red);
256 }
257
258 if votes.iter().any(|v| v.color == BranchColor::Yellow) {
260 return Ok(BranchColor::Yellow);
261 }
262
263 if votes.iter().all(|v| v.color == BranchColor::Green || v.color == BranchColor::NoOpinion) {
265 return Ok(BranchColor::Green);
266 }
267 }
268
269 Ok(BranchColor::Green)
271}
272
273pub fn is_change_guaranteed_safe(file: &PathBuf) -> Result<bool> {
275 let state = get_branching_state();
276 let state = state.read();
277
278 if let Some(votes) = state.votes.get(file) {
279 Ok(votes.iter().all(|v| v.color == BranchColor::Green))
280 } else {
281 Ok(false)
282 }
283}
284
285pub fn issue_immediate_veto(file: &PathBuf, voter_id: &str, reason: &str) -> Result<()> {
287 let vote = BranchingVote {
288 voter_id: voter_id.to_string(),
289 color: BranchColor::Red,
290 reason: reason.to_string(),
291 confidence: 1.0,
292 };
293
294 tracing::error!("🚫 VETO issued for {:?} by {}: {}", file, voter_id, reason);
295
296 submit_branching_vote(file, vote)?;
297
298 Ok(())
299}
300
301pub fn reset_branching_engine_state() -> Result<()> {
303 let state = get_branching_state();
304 let mut state = state.write();
305
306 tracing::info!("🔄 Resetting branching engine state");
307 state.votes.clear();
308 state.pending_changes.clear();
309
310 Ok(())
311}
312
313fn apply_file_change(change: &FileChange) -> Result<()> {
315 tracing::debug!("💾 Writing file: {:?}", change.path);
317 Ok(())
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_branching_votes() {
326 let file = PathBuf::from("test.ts");
327
328 let vote = BranchingVote {
329 voter_id: "test-voter".to_string(),
330 color: BranchColor::Green,
331 reason: "Test vote".to_string(),
332 confidence: 0.9,
333 };
334
335 submit_branching_vote(&file, vote).unwrap();
336
337 let color = query_predicted_branch_color(&file).unwrap();
338 assert_eq!(color, BranchColor::Green);
339 }
340}