tokmd 1.13.1

Tokei-backed repo inventory receipts (Markdown/TSV/JSONL/CSV) for PRs, CI, and LLM workflows.
Documentation
//! Proof-pack route receipt summary.

use std::collections::BTreeSet;

use serde_json::Value;

pub(in crate::commands::handoff) struct ProofRouteSummary {
    pub(in crate::commands::handoff) schema_version: Option<u64>,
    pub(in crate::commands::handoff) changed_file_count: usize,
    pub(in crate::commands::handoff) routed_file_count: usize,
    pub(in crate::commands::handoff) unmatched_file_count: usize,
    pub(in crate::commands::handoff) skipped_lane_count: usize,
    pub(in crate::commands::handoff) skipped_blocking_lanes: usize,
    pub(in crate::commands::handoff) surfaces: Vec<String>,
    pub(in crate::commands::handoff) skipped_reason_counts: Vec<(String, u64)>,
    pub(in crate::commands::handoff) first_changed_files: Vec<RouteFileSummary>,
    pub(in crate::commands::handoff) first_unmatched_files: Vec<String>,
    pub(in crate::commands::handoff) first_skipped_lanes: Vec<SkippedLaneSummary>,
}

pub(in crate::commands::handoff) struct RouteFileSummary {
    pub(in crate::commands::handoff) path: String,
    pub(in crate::commands::handoff) surface: String,
    pub(in crate::commands::handoff) proof_packs: Vec<String>,
}

pub(in crate::commands::handoff) struct SkippedLaneSummary {
    pub(in crate::commands::handoff) lane: String,
    pub(in crate::commands::handoff) reason: String,
    pub(in crate::commands::handoff) matched_files: usize,
    pub(in crate::commands::handoff) blocking: bool,
    pub(in crate::commands::handoff) estimated_lem: Option<u64>,
    pub(in crate::commands::handoff) estimate_source: Option<String>,
}

pub(super) fn summarize(value: &Value) -> ProofRouteSummary {
    let changed_files = value
        .get("changed_files")
        .and_then(Value::as_array)
        .map(Vec::as_slice)
        .unwrap_or(&[]);
    let skipped = value
        .get("skipped_by_policy")
        .and_then(Value::as_array)
        .map(Vec::as_slice)
        .unwrap_or(&[]);
    let unmatched_files = value
        .get("unmatched_files")
        .and_then(Value::as_array)
        .map(Vec::as_slice)
        .unwrap_or(&[]);
    let summary = value.get("summary");

    let first_changed_files = changed_files
        .iter()
        .filter_map(route_file_summary)
        .take(8)
        .collect::<Vec<_>>();
    let first_unmatched_files = unmatched_files
        .iter()
        .filter_map(Value::as_str)
        .take(8)
        .map(str::to_string)
        .collect::<Vec<_>>();
    let first_skipped_lanes = skipped
        .iter()
        .filter_map(skipped_lane_summary)
        .take(8)
        .collect::<Vec<_>>();
    let surfaces = changed_files
        .iter()
        .filter_map(|file| file.get("surface").and_then(Value::as_str))
        .collect::<BTreeSet<_>>()
        .into_iter()
        .take(10)
        .map(str::to_string)
        .collect::<Vec<_>>();

    ProofRouteSummary {
        schema_version: value.get("schema_version").and_then(Value::as_u64),
        changed_file_count: summary_count(summary, "changed_file_count", changed_files.len()),
        routed_file_count: summary_count(summary, "routed_file_count", changed_files.len()),
        unmatched_file_count: summary_count(summary, "unmatched_file_count", unmatched_files.len()),
        skipped_lane_count: summary_count(summary, "skipped_lane_count", skipped.len()),
        skipped_blocking_lanes: skipped
            .iter()
            .filter(|row| row.get("blocking").and_then(Value::as_bool) == Some(true))
            .count(),
        surfaces,
        skipped_reason_counts: skipped_reason_counts(summary),
        first_changed_files,
        first_unmatched_files,
        first_skipped_lanes,
    }
}

pub(super) fn render(out: &mut String, proof_route: &ProofRouteSummary) {
    let schema = proof_route
        .schema_version
        .map(|version| format!("schema v{version}, "))
        .unwrap_or_default();
    out.push_str(&format!(
        "- Proof route: {schema}{} changed file(s), {} routed, {} unmatched, {} skipped lane(s)\n",
        proof_route.changed_file_count,
        proof_route.routed_file_count,
        proof_route.unmatched_file_count,
        proof_route.skipped_lane_count
    ));
    if !proof_route.surfaces.is_empty() {
        out.push_str("  - Surfaces: ");
        out.push_str(&proof_route.surfaces.join(", "));
        out.push('\n');
    }
    if !proof_route.skipped_reason_counts.is_empty() {
        let counts = proof_route
            .skipped_reason_counts
            .iter()
            .map(|(reason, count)| format!("{reason}={count}"))
            .collect::<Vec<_>>();
        out.push_str("  - Skipped reasons: ");
        out.push_str(&counts.join(", "));
        out.push('\n');
    }
    if !proof_route.first_skipped_lanes.is_empty() {
        out.push_str("  - First skipped lanes:\n");
        for lane in &proof_route.first_skipped_lanes {
            let estimate = match (lane.estimated_lem, lane.estimate_source.as_deref()) {
                (Some(lem), Some(source)) => format!(", {lem} LEM, {source}"),
                (Some(lem), None) => format!(", {lem} LEM"),
                (None, _) => String::new(),
            };
            let blocking = if lane.blocking { ", blocking" } else { "" };
            out.push_str(&format!(
                "    - `{}`: {}{}{}, {} matched file(s)\n",
                lane.lane, lane.reason, blocking, estimate, lane.matched_files
            ));
        }
        if proof_route.skipped_lane_count > proof_route.first_skipped_lanes.len() {
            out.push_str(&format!(
                "    - ... {} more skipped lane(s); open the proof route for the full list.\n",
                proof_route.skipped_lane_count - proof_route.first_skipped_lanes.len()
            ));
        }
    }
    out.push_str("  - A proof route is routing evidence, not execution proof.\n");
}

fn route_file_summary(value: &Value) -> Option<RouteFileSummary> {
    let path = value
        .get("changed_file")
        .or_else(|| value.get("path"))
        .and_then(Value::as_str)?
        .to_string();
    let surface = value
        .get("surface")
        .and_then(Value::as_str)
        .unwrap_or("unknown")
        .to_string();
    let proof_packs = string_array_field(value, "required_packs")
        .or_else(|| string_array_field(value, "proof_packs"))
        .unwrap_or_default()
        .into_iter()
        .take(8)
        .collect();

    Some(RouteFileSummary {
        path,
        surface,
        proof_packs,
    })
}

fn string_array_field(value: &Value, field: &str) -> Option<Vec<String>> {
    Some(
        value
            .get(field)?
            .as_array()?
            .iter()
            .filter_map(Value::as_str)
            .map(str::to_string)
            .collect(),
    )
}

fn skipped_lane_summary(value: &Value) -> Option<SkippedLaneSummary> {
    Some(SkippedLaneSummary {
        lane: value.get("lane").and_then(Value::as_str)?.to_string(),
        reason: value
            .get("reason")
            .and_then(Value::as_str)
            .unwrap_or("unknown")
            .to_string(),
        matched_files: value
            .get("matched_files")
            .and_then(Value::as_array)
            .map_or(0, Vec::len),
        blocking: value.get("blocking").and_then(Value::as_bool) == Some(true),
        estimated_lem: value.get("estimated_lem").and_then(Value::as_u64),
        estimate_source: value
            .get("estimate_source")
            .and_then(Value::as_str)
            .map(str::to_string),
    })
}

fn summary_count(summary: Option<&Value>, field: &str, fallback: usize) -> usize {
    summary
        .and_then(|summary| summary.get(field))
        .and_then(Value::as_u64)
        .and_then(|value| usize::try_from(value).ok())
        .unwrap_or(fallback)
}

fn skipped_reason_counts(summary: Option<&Value>) -> Vec<(String, u64)> {
    summary
        .and_then(|summary| summary.get("skipped_reason_counts"))
        .and_then(Value::as_object)
        .map(|counts| {
            counts
                .iter()
                .filter_map(|(reason, count)| count.as_u64().map(|count| (reason.clone(), count)))
                .collect::<Vec<_>>()
        })
        .unwrap_or_default()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn proof_route_summary_reads_v5_counts_routes_and_skipped_reasons() {
        let value = serde_json::json!({
            "schema": "tokmd.proof_pack_route.v1",
            "schema_version": 5,
            "changed_files": [
                {
                    "changed_file": "docs/handoff.md",
                    "path": "docs/handoff.md",
                    "surface": "handoff_review_packet",
                    "required_packs": ["handoff_review_packet"],
                    "proof_packs": ["handoff_review_packet"]
                }
            ],
            "unmatched_files": ["docs/unrouted.md"],
            "summary": {
                "changed_file_count": 2,
                "routed_file_count": 1,
                "unmatched_file_count": 1,
                "skipped_lane_count": 1,
                "skipped_reason_counts": {
                    "deep_lane_requires_label": 1
                }
            },
            "skipped_by_policy": [
                {
                    "lane": "proptest_smoke",
                    "reason": "deep_lane_requires_label",
                    "matched_files": ["docs/handoff.md"],
                    "blocking": true,
                    "estimated_lem": 8,
                    "estimate_source": "static"
                }
            ]
        });

        let summary = summarize(&value);

        assert_eq!(summary.schema_version, Some(5));
        assert_eq!(summary.changed_file_count, 2);
        assert_eq!(summary.routed_file_count, 1);
        assert_eq!(summary.unmatched_file_count, 1);
        assert_eq!(summary.skipped_lane_count, 1);
        assert_eq!(summary.skipped_blocking_lanes, 1);
        assert_eq!(summary.surfaces, vec!["handoff_review_packet"]);
        assert_eq!(
            summary.skipped_reason_counts,
            vec![("deep_lane_requires_label".to_string(), 1)]
        );
        assert_eq!(summary.first_changed_files[0].path, "docs/handoff.md");
        assert_eq!(
            summary.first_changed_files[0].proof_packs,
            vec!["handoff_review_packet"]
        );
        assert_eq!(summary.first_unmatched_files, vec!["docs/unrouted.md"]);
        assert_eq!(summary.first_skipped_lanes[0].lane, "proptest_smoke");
        assert_eq!(summary.first_skipped_lanes[0].estimated_lem, Some(8));
        assert_eq!(
            summary.first_skipped_lanes[0].estimate_source.as_deref(),
            Some("static")
        );
    }

    #[test]
    fn proof_route_summary_accepts_v4_path_and_proof_pack_names() {
        let value = serde_json::json!({
            "schema": "tokmd.proof_pack_route.v1",
            "schema_version": 4,
            "changed_files": [
                {
                    "path": "docs/handoff.md",
                    "surface": "handoff_review_packet",
                    "proof_packs": ["handoff_review_packet"]
                }
            ],
            "unmatched_files": [],
            "summary": {
                "changed_file_count": 1,
                "routed_file_count": 1,
                "unmatched_file_count": 0,
                "skipped_lane_count": 0,
                "skipped_reason_counts": {}
            },
            "skipped_by_policy": []
        });

        let summary = summarize(&value);

        assert_eq!(summary.schema_version, Some(4));
        assert_eq!(summary.first_changed_files[0].path, "docs/handoff.md");
        assert_eq!(
            summary.first_changed_files[0].proof_packs,
            vec!["handoff_review_packet"]
        );
    }
}