magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
use crate::sessions::SessionEventKind;
use serde_json::{Value, json};
use std::collections::{BTreeMap, BTreeSet};

/// Host-owned evidence, shared by live execution and interrupted-batch replay.
#[derive(Default)]
pub(crate) struct CodeModeAudit {
    actions: BTreeMap<u64, (String, Option<bool>)>,
    changed_paths: BTreeSet<String>,
    changed_paths_incomplete: bool,
    warnings: BTreeSet<String>,
    pub(crate) contexts: Vec<Value>,
}

impl CodeModeAudit {
    pub(crate) fn observe(&mut self, kind: SessionEventKind, payload: &Value) {
        let sequence = payload["sequence"].as_u64().unwrap_or_default();
        match kind {
            SessionEventKind::CodeModeToolCall => {
                self.actions.insert(
                    sequence,
                    (
                        payload["name"]
                            .as_str()
                            .unwrap_or("unknown")
                            .chars()
                            .take(256)
                            .collect(),
                        None,
                    ),
                );
            }
            SessionEventKind::CodeModeToolResult => {
                if let Some(action) = self.actions.get_mut(&sequence) {
                    action.1 = payload["result"]["success"].as_bool();
                }
                if let Some(paths) = payload["changed_paths"]
                    .as_array()
                    .or_else(|| payload["execution_effects"]["paths"].as_array())
                {
                    self.changed_paths_incomplete |=
                        payload["execution_effects"]["changed_path_count"]
                            .as_u64()
                            .is_some_and(|count| count > paths.len() as u64);
                    for path in paths.iter().filter_map(Value::as_str) {
                        let mut path = crate::output::redact_sensitive_text(path);
                        self.changed_paths_incomplete |= path.len() > 512;
                        let mut end = path.len().min(512);
                        while !path.is_char_boundary(end) {
                            end -= 1;
                        }
                        path.truncate(end);
                        if self.changed_paths.len() < 1000 {
                            self.changed_paths.insert(path);
                        } else if !self.changed_paths.contains(&path) {
                            self.changed_paths_incomplete = true;
                        }
                    }
                }
            }
            SessionEventKind::CodeModeContext => self.contexts.push(payload.clone()),
            SessionEventKind::CodeModeWarning => {
                if let Some(warning) = payload["warning"].as_str() {
                    self.warnings.insert(warning.to_owned());
                }
            }
            _ => {}
        }
    }

    pub(crate) fn summary(&self) -> Value {
        json!({
            "inner_calls": self.actions.len(),
            "successful": self.actions.values().filter(|(_, success)| *success == Some(true)).count(),
            "recoverable_failures": self.actions.values().filter(|(_, success)| *success == Some(false)).count(),
            "unknown_outcomes": self.actions.values().filter(|(_, success)| success.is_none()).count(),
            "actions": self.actions.iter().take(100).map(|(sequence, (name, success))| json!({"sequence": sequence,"tool":name,"success":success})).collect::<Vec<_>>(),
            "actions_omitted": self.actions.len().saturating_sub(100),
            "changed_paths": self.changed_paths.iter().take(50).collect::<Vec<_>>(),
            "changed_path_count": self.changed_paths.len(),
            "changed_paths_incomplete": self.changed_paths_incomplete,
            "warnings": self.warnings,
            "effects": "Completed actions remain applied. Missing results have unknown outcomes. No rollback or automatic retry. Shell and external effects may be untracked."
        })
    }
}