magi-code 0.77.1

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

#[cfg(test)]
thread_local! {
    static PROJECT_SPAN_VISITS: Cell<usize> = const { Cell::new(0) };
}

#[cfg(test)]
pub(crate) fn take_project_span_visits_for_test() -> usize {
    PROJECT_SPAN_VISITS.with(|count| count.replace(0))
}

pub(crate) struct ProjectedTranscriptCard {
    pub(crate) card: Arc<TranscriptCard>,
    pub(crate) first_entry_index: usize,
    pub(crate) content_marker: u64,
}

fn project_card_span(
    state: &MissionControlState,
    first: usize,
    end: usize,
) -> ProjectedTranscriptCard {
    #[cfg(test)]
    PROJECT_SPAN_VISITS.with(|count| count.set(count.get() + 1));
    let first_entry = state.transcript.get(first).expect("transcript entry");
    let mut content_marker = transcript_card_content_marker(state, first, first_entry);
    if first_entry.kind().is_thinking() && end > first + 1 {
        for index in first + 1..end {
            content_marker = hash_combine(content_marker, state.transcript_entry_revision(index));
        }
    }
    content_marker = hash_combine(content_marker, end.saturating_sub(first) as u64);
    let card = cached_project_entry(state, first, end, first_entry, content_marker);
    ProjectedTranscriptCard {
        card,
        first_entry_index: first,
        content_marker,
    }
}

fn next_forward_span(state: &MissionControlState, first: usize) -> usize {
    if !state
        .transcript
        .get(first)
        .is_some_and(|entry| entry.kind().is_thinking())
    {
        return first + 1;
    }
    let mut end = first + 1;
    while state
        .transcript
        .get(end)
        .is_some_and(|entry| entry.kind().is_thinking())
    {
        end += 1;
    }
    end
}

pub(crate) fn project_card_spans(
    state: &MissionControlState,
) -> impl Iterator<Item = ProjectedTranscriptCard> + '_ {
    let mut index = 0;
    std::iter::from_fn(move || {
        if index >= state.transcript.len() {
            return None;
        }
        let end = next_forward_span(state, index);
        let span = project_card_span(state, index, end);
        index = end;
        Some(span)
    })
}

#[cfg(test)]
pub(crate) fn project_card_spans_reverse(
    state: &MissionControlState,
) -> impl Iterator<Item = ProjectedTranscriptCard> + '_ {
    project_card_spans_reverse_from(state, state.transcript.len())
}

pub(crate) fn project_card_spans_reverse_from(
    state: &MissionControlState,
    mut end: usize,
) -> impl Iterator<Item = ProjectedTranscriptCard> + '_ {
    std::iter::from_fn(move || {
        if end == 0 {
            return None;
        }
        let mut first = end - 1;
        while first > 0
            && state
                .transcript
                .get(first - 1)
                .is_some_and(|entry| entry.kind().is_thinking())
            && state
                .transcript
                .get(first)
                .is_some_and(|entry| entry.kind().is_thinking())
        {
            first -= 1;
        }
        let span = project_card_span(state, first, end);
        end = first;
        Some(span)
    })
}

fn cached_project_entry(
    state: &MissionControlState,
    entry_index: usize,
    end_entry_index: usize,
    entry: &TranscriptEntry,
    content_marker: u64,
) -> Arc<TranscriptCard> {
    let cache_index = state
        .transcript_absolute_index(entry_index)
        .unwrap_or(entry_index);
    if let Some(cached) = state.transcript_cache.borrow().cards.get(&cache_index)
        && cached.content_marker == content_marker
    {
        return Arc::clone(&cached.card);
    }

    let mut card = normalize_projected_card(project_entry(state, entry_index, entry));
    if card.role == TranscriptCardRole::Reasoning && end_entry_index > entry_index + 1 {
        card.body = (entry_index..end_entry_index)
            .filter_map(|index| state.transcript.get(index))
            .map(|entry| entry.body().trim_end_matches('\n'))
            .collect::<Vec<_>>()
            .join("\n\n");
    }
    let card = Arc::new(card);
    state.transcript_cache.borrow_mut().cards.insert(
        cache_index,
        transcript::CachedTranscriptCard {
            content_marker,
            card: Arc::clone(&card),
        },
    );
    card
}

#[cfg(test)]
pub(crate) fn cached_card_ptr_for_test(
    state: &MissionControlState,
    entry_index: usize,
) -> *const TranscriptCard {
    Arc::as_ptr(&project_card_span(state, entry_index, next_forward_span(state, entry_index)).card)
}

fn transcript_card_content_marker(
    state: &MissionControlState,
    entry_index: usize,
    _entry: &TranscriptEntry,
) -> u64 {
    let mut hash = hash_combine(
        0x6a09_e667_f3bc_c909,
        state.transcript_entry_revision(entry_index),
    );
    if state.active_assistant_transcript_index() == Some(entry_index) {
        hash ^= 0x9e37_79b9_7f4a_7c15;
    }
    let plain_first_marker = state.final_assistant_plain_first_marker(entry_index);
    match plain_first_marker {
        1 => {
            hash ^= 0x517c_c1b7_dae5_8a2d;
            if state.transcript_selection_active() {
                hash ^= 0x3c8b_a73d_142f_e9b6;
            }
        }
        2 => hash ^= 0xa24f_9c3e_65b1_d47a,
        _ => {}
    }
    if let Some(timestamp) = state.transcript_timestamp(entry_index) {
        hash = hash_combine(hash, timestamp.timestamp() as u64);
        hash = hash_combine(hash, u64::from(timestamp.timestamp_subsec_nanos()));
    }
    if let Some(absolute_index) = state.transcript_absolute_index(entry_index)
        && let Some(link) = state.transcript.activity_links().get(&absolute_index)
    {
        hash = hash_combine(hash, stable_hash(link.activity_id.as_str().as_bytes()));
        hash = hash_combine(hash, stable_hash(link.tool_name.as_bytes()));
        let tool_capability = ToolCapability::from_dispatch_name(&link.tool_name);
        if tool_capability == Some(ToolCapability::Bash) {
            // A shell result is rendered as a full bash-mode response only when its
            // preceding transcript row is a bash command. Include that derived bit so
            // front-pruning (which changes the local predecessor) cannot reuse a stale card.
            let bash_mode_response =
                is_bash_mode_response_card(state, entry_index, tool_capability);
            hash = hash_combine(hash, if bash_mode_response { 1 } else { 0 });
        }
        // Activity reducers invalidate linked transcript entries when usage changes.
        // Do not scan the activity tree here; projection is on the render hot path.
    }
    hash
}

fn hash_combine(left: u64, right: u64) -> u64 {
    left ^ right
        .wrapping_add(0x9e37_79b9_7f4a_7c15)
        .wrapping_add(left << 6)
        .wrapping_add(left >> 2)
}