magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;

fn hash_edit_running_ops(input: &str) -> Vec<String> {
    crate::tools::hash_edit_operation_summaries(input)
        .into_iter()
        .map(|operation| match operation {
            crate::tools::HashEditOperationSummary::Update { path } => {
                format!("Update {}", sanitized_single_line(&path))
            }
            crate::tools::HashEditOperationSummary::Move { path, destination } => format!(
                "Move {} → {}",
                sanitized_single_line(&path),
                sanitized_single_line(&destination)
            ),
            crate::tools::HashEditOperationSummary::Delete { path } => {
                format!("Delete {}", sanitized_single_line(&path))
            }
        })
        .collect()
}

pub(super) fn hash_edit_card(
    state: &MissionControlState,
    id: TranscriptCardId,
    link: Option<TranscriptActivityLink>,
) -> TranscriptCard {
    let node = link
        .as_ref()
        .and_then(|link| state.nodes.get(&link.activity_id));
    let detail = node.and_then(|node| node.tool_detail.as_ref());
    let metadata = detail.map(|detail| &detail.metadata);
    let error_kind = hash_edit_metadata_str(metadata, "error_kind");
    let outcome = hash_edit_metadata_str(metadata, "outcome");
    let mut status = node
        .map(|node| transcript_status_from_activity(node.status))
        .or_else(|| detail.map(|detail| transcript_status_from_activity(detail.status)))
        .unwrap_or_else(|| transcript_tool_status(state, id.entry_index));
    if error_kind == Some("canceled") && outcome != Some("partial") {
        status = TranscriptCardStatus::Canceled;
    }
    let files = metadata
        .and_then(|metadata| metadata.get("files"))
        .and_then(serde_json::Value::as_array);
    let added = hash_edit_metadata_usize(metadata, "added").unwrap_or(0);
    let removed = hash_edit_metadata_usize(metadata, "removed").unwrap_or(0);
    let stale = outcome == Some("stale_tag") || error_kind == Some("stale_tag");
    let partial = outcome == Some("partial");
    let mut diff_preview_start = None;
    let (title, lines) = if status == TranscriptCardStatus::Running {
        let operations = detail
            .and_then(|detail| detail.params.get("input"))
            .and_then(serde_json::Value::as_str)
            .map(hash_edit_running_ops)
            .unwrap_or_default();
        ("Hash Edit".to_string(), hash_edit_bounded_rows(operations))
    } else if status == TranscriptCardStatus::Canceled {
        (
            "Hash Edit • canceled".to_string(),
            vec!["Canceled before completion".to_string()],
        )
    } else if stale {
        let path = hash_edit_metadata_str(metadata, "path")
            .map(sanitized_single_line)
            .unwrap_or_default();
        let expected = hash_edit_metadata_str(metadata, "expected_tag")
            .map(sanitized_single_line)
            .unwrap_or_default();
        let current = hash_edit_metadata_str(metadata, "current_tag")
            .map(sanitized_single_line)
            .unwrap_or_default();
        (
            "Hash Edit • stale tag".to_string(),
            vec![
                path,
                format!("Expected #{expected} · current #{current}"),
                "Re-read file, then retry.".to_string(),
            ],
        )
    } else if partial {
        let committed = hash_edit_metadata_usize(metadata, "committed").unwrap_or(0);
        let changed = hash_edit_metadata_usize(metadata, "changed").unwrap_or(committed);
        let sections = hash_edit_metadata_usize(metadata, "sections")
            .unwrap_or_else(|| files.map_or(0, Vec::len));
        let rows = files
            .map(|files| {
                files
                    .iter()
                    .filter_map(hash_edit_partial_row)
                    .collect::<Vec<_>>()
            })
            .unwrap_or_default();
        let mut rows = hash_edit_bounded_rows(rows);
        rows.push(format!(
            "Changes to {changed} {} remain on disk.",
            plural(changed, "file", "files")
        ));
        (format!("Hash Edit • partial {committed}/{sections}"), rows)
    } else if status == TranscriptCardStatus::Failed {
        let mut rows = Vec::new();
        if let Some(path) = hash_edit_failure_path(detail, files) {
            rows.push(path);
        }
        rows.push(
            detail
                .and_then(|detail| first_non_empty_line(detail.output.as_ref()))
                .unwrap_or_else(|| "hash_edit failed".to_string()),
        );
        if metadata
            .and_then(|value| value.get("humanize_protection_denied"))
            .and_then(serde_json::Value::as_bool)
            == Some(true)
        {
            rows.extend(attempted_change_preview(
                detail.and_then(|detail| detail.params.get("input")),
            ));
        }
        ("Hash Edit".to_string(), rows)
    } else {
        let total = files.map_or(0, Vec::len);
        let operations = files
            .map(|files| {
                files
                    .iter()
                    .filter_map(hash_edit_success_row)
                    .collect::<Vec<_>>()
            })
            .unwrap_or_default();
        let mut rows = hash_edit_bounded_rows(operations);
        let title = hash_edit_success_title(files, added, removed);
        let contains_delete = files.is_some_and(|files| {
            files.iter().any(|file| {
                file.get("operation").and_then(serde_json::Value::as_str) == Some("delete")
            })
        });
        if !contains_delete
            && (added > 0 || removed > 0)
            && let Some(diff) = detail.and_then(|detail| detail.applied_diff.as_deref())
        {
            diff_preview_start = Some(rows.len());
            rows.extend(hash_edit_diff_preview(
                diff,
                metadata
                    .and_then(|metadata| metadata.get("diff_truncated"))
                    .and_then(serde_json::Value::as_bool)
                    .unwrap_or(false),
            ));
        }
        debug_assert!(total == 0 || !rows.is_empty());
        (title, rows)
    };
    let body_lines = lines
        .iter()
        .enumerate()
        .map(|(index, line)| {
            let role = if diff_preview_start.is_some_and(|start| index >= start) {
                match line.as_bytes().first() {
                    Some(b'+') => DisplayRole::DiffInserted,
                    Some(b'-') => DisplayRole::DiffRemoved,
                    _ => DisplayRole::Plain,
                }
            } else {
                DisplayRole::Plain
            };
            DisplayLine::from_span(line, role)
        })
        .collect();
    TranscriptCard {
        id,
        role: TranscriptCardRole::HashEdit,
        status,
        title,
        metadata: humanize_protection_metadata(detail),
        body: lines.join("\n"),
        body_lines,
        children: Vec::new(),
        activity_rows: Vec::new(),
        subagent_card: None,
    }
}

fn hash_edit_metadata_str<'a>(
    metadata: Option<&'a serde_json::Value>,
    key: &str,
) -> Option<&'a str> {
    metadata?.get(key)?.as_str()
}

fn hash_edit_metadata_usize(metadata: Option<&serde_json::Value>, key: &str) -> Option<usize> {
    metadata?
        .get(key)?
        .as_u64()
        .and_then(|value| usize::try_from(value).ok())
}

fn hash_edit_bounded_rows(mut rows: Vec<String>) -> Vec<String> {
    if rows.len() <= TOOL_PREVIEW_LINES {
        return rows;
    }
    let remaining = rows.len() - TOOL_PREVIEW_LINES;
    rows.truncate(TOOL_PREVIEW_LINES);
    rows.push(format!("… {remaining} more files"));
    rows
}

fn hash_edit_success_title(
    files: Option<&Vec<serde_json::Value>>,
    added: usize,
    removed: usize,
) -> String {
    let total = files.map_or(0, Vec::len);
    if total > 1 {
        let all_noop = files.is_some_and(|files| {
            files.iter().all(|file| {
                file.get("operation").and_then(serde_json::Value::as_str) == Some("noop")
            })
        });
        return if all_noop {
            format!("Hash Edit • {total} files • no changes")
        } else {
            format!("Hash Edit • {total} files • +{added} −{removed}")
        };
    }
    match files
        .and_then(|files| files.first())
        .and_then(|file| file.get("operation"))
        .and_then(serde_json::Value::as_str)
    {
        Some("move") if added + removed > 0 => {
            format!("Hash Edit • moved • +{added} −{removed}")
        }
        Some("move") => "Hash Edit • moved".to_string(),
        Some("delete") => format!("Hash Edit • deleted • −{removed}"),
        Some("noop") => "Hash Edit • no changes".to_string(),
        Some("update") => format!("Hash Edit • +{added} −{removed}"),
        _ => "Hash Edit".to_string(),
    }
}

fn hash_edit_partial_row(value: &serde_json::Value) -> Option<String> {
    let path = value
        .get("path")
        .and_then(serde_json::Value::as_str)
        .map(sanitized_single_line)?;
    let status = value
        .get("status")
        .and_then(serde_json::Value::as_str)
        .unwrap_or("");
    match status {
        "committed" => hash_edit_success_row(value).map(|row| format!("✓ {row}")),
        "failed"
        | "committed_but_undurable"
        | "committed_with_error"
        | "destination_written_source_retained"
        | "partially_applied" => Some(format!(
            "✗ {path} · {}",
            value
                .get("error")
                .and_then(serde_json::Value::as_str)
                .and_then(first_non_empty_line)
                .unwrap_or_else(|| "error".to_string())
        )),
        _ => Some(format!("○ {path} · not written")),
    }
}

fn hash_edit_success_row(value: &serde_json::Value) -> Option<String> {
    let path = value
        .get("path")
        .and_then(serde_json::Value::as_str)
        .map(sanitized_single_line)?;
    match value.get("operation").and_then(serde_json::Value::as_str) {
        Some("move") => Some(format!(
            "Moved {path} → {}",
            value
                .get("destination")
                .and_then(serde_json::Value::as_str)
                .map(sanitized_single_line)
                .unwrap_or_default()
        )),
        Some("delete") => Some(format!("Deleted {path}")),
        Some("noop") => Some(format!("Unchanged {path}")),
        _ => Some(format!("Updated {path}")),
    }
}

fn hash_edit_failure_path(
    detail: Option<&crate::output::ToolActivityDetail>,
    files: Option<&Vec<serde_json::Value>>,
) -> Option<String> {
    files
        .and_then(|files| files.first())
        .and_then(|file| file.get("path"))
        .and_then(serde_json::Value::as_str)
        .map(sanitized_single_line)
        .or_else(|| {
            detail
                .and_then(|detail| detail.metadata.get("path"))
                .and_then(serde_json::Value::as_str)
                .map(sanitized_single_line)
        })
        .or_else(|| {
            detail
                .and_then(|detail| detail.params.get("input"))
                .and_then(serde_json::Value::as_str)
                .map(hash_edit_running_ops)
                .and_then(|rows| rows.first().cloned())
                .and_then(|row| row.split_once(' ').map(|(_, path)| path.to_string()))
        })
}

pub(super) const HASH_EDIT_DIFF_PREVIEW_LINES: usize = 10;

fn hash_edit_diff_preview(diff: &str, diff_truncated: bool) -> Vec<String> {
    let mut changed = Vec::new();
    let mut header_rows_remaining = 0usize;
    for line in diff.lines() {
        if line.starts_with("diff --git ") {
            header_rows_remaining = 2;
            continue;
        }
        if header_rows_remaining > 0 {
            header_rows_remaining -= 1;
            continue;
        }
        if line.starts_with("--- a/")
            || line == "--- /dev/null"
            || line.starts_with("+++ b/")
            || line == "+++ /dev/null"
        {
            continue;
        }
        if !(line.starts_with('+') || line.starts_with('-')) {
            continue;
        }
        let safe = transcript::sanitize_preview(line);
        changed.push(truncate_preview_line(&safe, PREVIEW_LINE_MAX_DISPLAY_WIDTH));
    }
    let limited = diff_truncated || changed.len() > HASH_EDIT_DIFF_PREVIEW_LINES;
    changed.truncate(HASH_EDIT_DIFF_PREVIEW_LINES);
    if limited {
        changed.push(format!(
            "… diff preview limited to {HASH_EDIT_DIFF_PREVIEW_LINES} changed lines"
        ));
    }
    changed
}