aicx 0.9.2

Operator CLI + MCP server: canonical corpus first, optional semantic index second (Claude Code, Codex, Gemini)
Documentation
use super::{MigrationAction, MigrationItem, MigrationManifest, is_unclassified_item};

pub(super) fn render_migration_report(manifest: &MigrationManifest) -> String {
    let mut report = String::new();
    report.push_str("# AICX Migration Report\n\n");
    report.push_str(&format!(
        "- Generated at: `{}`\n",
        manifest.generated_at.to_rfc3339()
    ));
    report.push_str(&format!("- Dry run: `{}`\n", manifest.dry_run));
    report.push_str(&format!("- Legacy root: `{}`\n", manifest.legacy_root));
    report.push_str(&format!("- Store root: `{}`\n", manifest.store_root));
    report.push_str(&format!("- Manifest: `{}`\n", manifest.manifest_path));
    report.push_str(&format!("- Report: `{}`\n\n", manifest.report_path));

    report.push_str("## Summary\n\n");
    report.push_str(&format!(
        "- Swept `{}` migration item(s) from `{}` legacy file(s)\n",
        manifest.totals.total_items, manifest.totals.total_legacy_files
    ));
    report.push_str(&format!(
        "- Planned rebuild-only items: `{}`\n",
        manifest.totals.rebuild_items
    ));
    report.push_str(&format!(
        "- Planned rebuild+salvage items: `{}`\n",
        manifest.totals.rebuild_and_salvage_items
    ));
    report.push_str(&format!(
        "- Planned salvage-only items: `{}`\n",
        manifest.totals.salvage_items
    ));
    report.push_str(&format!(
        "- Unclassified legacy items: `{}`\n",
        manifest.totals.unclassified_items
    ));
    report.push_str(&format!(
        "- Resolved source matches: `{}`\n",
        manifest.totals.resolved_sources
    ));
    report.push_str(&format!(
        "- Missing source hints: `{}`\n",
        manifest.totals.missing_source_hints
    ));
    report.push_str(&format!(
        "- Ambiguous source hints: `{}`\n",
        manifest.totals.ambiguous_source_hints
    ));
    report.push_str(&format!(
        "- Rebuilt items: `{}` (`{}` canonical path(s))\n",
        manifest.totals.rebuilt_items, manifest.totals.rebuilt_paths
    ));
    report.push_str(&format!(
        "- Salvaged items: `{}` (`{}` preserved path(s))\n",
        manifest.totals.salvaged_items, manifest.totals.salvaged_paths
    ));
    report.push_str(&format!(
        "- Items with execution errors: `{}`\n\n",
        manifest.totals.failed_items
    ));

    push_report_section(
        &mut report,
        if manifest.dry_run {
            "Planned Rebuild"
        } else {
            "Rebuilt"
        },
        manifest.items.iter().filter(|item| {
            matches!(
                item.action,
                MigrationAction::Rebuild | MigrationAction::RebuildAndSalvage
            )
        }),
    );
    push_report_section(
        &mut report,
        if manifest.dry_run {
            "Planned Salvage"
        } else {
            "Salvaged"
        },
        manifest.items.iter().filter(|item| {
            item.action == MigrationAction::Salvage
                || item.action == MigrationAction::RebuildAndSalvage
                || !item.salvage_paths.is_empty()
        }),
    );
    push_report_section(
        &mut report,
        "Unclassified Legacy Items",
        manifest
            .items
            .iter()
            .filter(|item| is_unclassified_item(item)),
    );

    report
}

fn push_report_section<'a, I>(report: &mut String, title: &str, items: I)
where
    I: Iterator<Item = &'a MigrationItem>,
{
    report.push_str(&format!("## {}\n\n", title));
    let mut wrote = false;

    for item in items {
        wrote = true;
        report.push_str(&format!(
            "- `{}` [{}]\n",
            item.legacy_group, item.action_reason
        ));
        if !item.existing_sources.is_empty() {
            report.push_str(&format!(
                "  sources: `{}`\n",
                item.existing_sources.join("`, `")
            ));
        }
        if !item.canonical_paths.is_empty() {
            report.push_str(&format!(
                "  canonical: `{}`\n",
                item.canonical_paths.join("`, `")
            ));
        }
        if !item.salvage_paths.is_empty() {
            report.push_str(&format!(
                "  legacy: `{}`\n",
                item.salvage_paths.join("`, `")
            ));
        }
        if !item.missing_sources.is_empty() {
            report.push_str(&format!(
                "  missing: `{}`\n",
                item.missing_sources.join("`, `")
            ));
        }
        if !item.ambiguous_sources.is_empty() {
            report.push_str(&format!(
                "  ambiguous: `{}`\n",
                item.ambiguous_sources.join("`, `")
            ));
        }
        if !item.errors.is_empty() {
            report.push_str(&format!("  errors: `{}`\n", item.errors.join("`, `")));
        }
    }

    if !wrote {
        report.push_str("- none\n");
    }

    report.push('\n');
}

pub(super) fn print_migration_summary(manifest: &MigrationManifest) {
    println!(
        "Legacy sweep: {} item(s) from {} file(s).",
        manifest.totals.total_items, manifest.totals.total_legacy_files
    );
    println!("Legacy root: {}", manifest.legacy_root);
    println!("Store root: {}", manifest.store_root);
    println!(
        "Plan: {} rebuild-only, {} rebuild+salvage, {} salvage-only, {} unclassified.",
        manifest.totals.rebuild_items,
        manifest.totals.rebuild_and_salvage_items,
        manifest.totals.salvage_items,
        manifest.totals.unclassified_items
    );
    println!(
        "Source hints: {} resolved, {} missing, {} ambiguous.",
        manifest.totals.resolved_sources,
        manifest.totals.missing_source_hints,
        manifest.totals.ambiguous_source_hints
    );

    if !manifest.dry_run {
        println!(
            "Executed: {} rebuilt item(s) -> {} canonical path(s); {} salvaged item(s) -> {} preserved path(s); {} item(s) with errors.",
            manifest.totals.rebuilt_items,
            manifest.totals.rebuilt_paths,
            manifest.totals.salvaged_items,
            manifest.totals.salvaged_paths,
            manifest.totals.failed_items
        );
    }
}