magi-code 0.102.0

Repository-aware CLI coding agent for terminal work
Documentation
mod engine;
mod matcher;
mod reader;
use super::{
    ToolResult, ToolResultDisplay,
    args::{GrepArgs, GrepMode},
    contract::tool_name,
};
use crate::cancellation::AgentCancellation;
use serde_json::json;
pub(in crate::tools) struct GrepContext<'a> {
    pub(in crate::tools) paths: crate::tools::fs::PathContext<'a>,
    pub(in crate::tools) settings: &'a crate::config::GrepToolSettings,
    pub(in crate::tools) workspace_walker: &'a super::workspace::WorkspaceWalker,
}

impl GrepContext<'_> {
    pub(super) fn grep(
        &self,
        args: GrepArgs,
        cancellation: &AgentCancellation,
    ) -> anyhow::Result<ToolResult> {
        let path_arg = args.path.clone().unwrap_or_else(|| ".".into());
        let path = self.paths.resolve_existing_path(
            &path_arg,
            super::fs::ExistingPathPolicy::grep(self.settings.absolute_paths),
        )?;
        cancellation.check()?;
        let mode = args.mode.unwrap_or(GrepMode::Ranked);
        let raw = mode == GrepMode::Raw || args.pattern.is_some();
        let limit = args.limit.unwrap_or(crate::tools::args::GREP_DEFAULT_LIMIT);
        let offset = args.offset.unwrap_or(0);
        let requested_context = args.context.unwrap_or(0);
        let context = requested_context.min(super::args::GREP_MAX_CONTEXT);
        let notice = (requested_context != context)
            .then(|| format!("context clamped from {requested_context} to {context}"));
        let scan = engine::run(engine::GrepRequest {
            cwd: self.paths.cwd_canonical,
            walker: self.workspace_walker,
            patterns: args
                .patterns
                .as_ref()
                .ok_or_else(|| anyhow::anyhow!("patterns required"))?,
            root: &path,
            limit,
            offset,
            context,
            notice: notice.as_deref(),
            raw,
            cancellation,
        })?;
        let pattern_count = args.patterns.as_ref().map_or(0, Vec::len);
        let mut metadata = json!({
            "engine": "native",
            "patterns_count": pattern_count,
            "mode": if raw { "raw" } else { "ranked" },
            "offset": offset,
            "limit": limit,
            "output_format": if raw { "raw_v1" } else { "grouped_v1" },
            "order": if raw { "path_line_v1" } else { "ranked_v1" },
            "matches_returned": scan.matches_returned,
            "output_lines_returned": scan.output_lines_returned,
            "truncated": scan.truncated,
            "timed_out": scan.timed_out,
            "files_scanned": scan.files_scanned,
            "bytes_scanned": scan.bytes_scanned,
            "byte_limit_reached": scan.byte_limit_reached,
            "scan_byte_limit_reached": scan.scan_byte_limit_reached,
            "file_limit_reached": scan.file_limit_reached,
            "match_limit_reached": scan.match_limit_reached,
            "per_file_limit_reached": scan.per_file_limit_reached,
            "files_with_matches": scan.files_with_matches,
            "files_returned": scan.files_returned,
            "invalid_utf8_files": scan.invalid_utf8_files,
            "invalid_utf8_lines": scan.invalid_utf8_lines,
            "read_errors_skipped": scan.read_errors_skipped,
            "walk_errors": scan.walk_errors,
            "walk_entries_omitted": scan.walk_entries_omitted,
            "binary_files_skipped": scan.binary_files_skipped,
            "scan_complete": scan.scan_complete,
            "matches_seen": scan.matches_seen,
            "matches_seen_exact": scan.matches_seen_exact,
            "output_bytes": scan.output_bytes,
            "output_byte_limit_reached": scan.output_byte_limit_reached,
            "context_lines_omitted": scan.context_lines_omitted,
            "patterns_adjusted": scan.patterns_adjusted,
            "literal_fallbacks": scan.literal_fallbacks,
            "raw_lookahead_reached": scan.raw_lookahead_reached,
            "truncation_reasons": scan.truncation_reasons,
            "limit_reason": scan.truncation_reasons.first().copied(),
        });
        metadata["next_offset"] = scan
            .next_offset
            .map_or(serde_json::Value::Null, |v| json!(v));
        metadata["has_more"] = scan.has_more.map_or(serde_json::Value::Null, |v| json!(v));

        let mut content = scan.lines.join("\n");
        if scan.matches_returned == 0 && !scan.scan_complete {
            if !content.is_empty() {
                content.push('\n');
            }
            content.push_str(&format!(
                "SEARCH INCOMPLETE: no matching lines returned; search coverage incomplete ({})",
                scan.truncation_reasons.join(", ")
            ));
        }
        metadata["output_bytes"] = json!(content.len());
        Ok(ToolResult {
            tool_name: tool_name::GREP.into(),
            success: true,
            content,
            metadata,
            display: ToolResultDisplay::default(),
        })
    }
}