Skip to content

Summarize Cargo failures in tool metadata#1636

Closed
kunpeng-ai-lab wants to merge 2 commits into
Hmbown:mainfrom
kunpeng-ai-lab:feature/cargo-failure-summary
Closed

Summarize Cargo failures in tool metadata#1636
kunpeng-ai-lab wants to merge 2 commits into
Hmbown:mainfrom
kunpeng-ai-lab:feature/cargo-failure-summary

Conversation

@kunpeng-ai-lab
Copy link
Copy Markdown

Summary

Adds compact Cargo failure summaries to tool metadata so coding turns preserve the actionable test/compiler failure signal without carrying all raw output.

This is separate from #1585 and #1634. It does not change execution policy or patch behavior.

Changes

  • Add cargo_failure_summary helper for Cargo output classification.
  • Classify failures as test_failure, compile_error, or cargo_failure.
  • Extract failing tests, Rust error codes, primary errors, panic locations, test result: lines, and final Cargo errors.
  • Attach compact summary and cargo_failure_summary metadata to failing run_tests results.
  • Attach the same metadata to exec_shell / exec_shell_wait Cargo command results, including background wait deltas.

Validation

  • cargo fmt --check
  • cargo test -p deepseek-tui --bin deepseek-tui cargo_failure_summary
  • cargo test -p deepseek-tui --bin deepseek-tui run_tests
  • cargo test -p deepseek-tui --bin deepseek-tui shell_delta_result_includes_cargo_failure_summary
  • cargo check --workspace

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a cargo_failure_summary module to extract actionable failure signals from Cargo output, categorizing them into test failures, compilation errors, or general cargo failures. This functionality is integrated into the shell and test runner tools to enrich command metadata. Reviewers suggested several improvements: using an enum for failure types to enhance type safety, expanding the recognized Cargo command aliases (e.g., cargo t, cargo b), and broadening the detection of primary error lines. Additionally, feedback pointed out a redundant empty-string check and a potential bug in the character truncation logic when handling very small limits.

Comment on lines +14 to +29
pub(crate) struct CargoFailureSummary {
pub(crate) kind: String,
pub(crate) summary: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) failing_tests: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) error_codes: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) primary_errors: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) panic_locations: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) test_result: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) final_error: Option<String>,
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a String for the kind field is less idiomatic in Rust than using an enum. An enum with #[serde(rename_all = "snake_case")] would provide better type safety and clarity for the possible failure categories.

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum CargoFailureKind {
    TestFailure,
    CompileError,
    CargoFailure,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct CargoFailureSummary {
    pub(crate) kind: CargoFailureKind,
    pub(crate) summary: String,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub(crate) failing_tests: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub(crate) error_codes: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub(crate) primary_errors: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub(crate) panic_locations: Vec<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub(crate) test_result: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub(crate) final_error: Option<String>,
}

Comment on lines +97 to +99
if summary.trim().is_empty() {
return None;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This check is redundant because build_summary always returns a string starting with "Cargo failure kind: ...", so it will never be empty or just whitespace if a failure was classified.

Comment on lines +113 to +119
fn looks_like_cargo_command(command: &str) -> bool {
let lower = command.to_ascii_lowercase();
lower.contains("cargo test")
|| lower.contains("cargo check")
|| lower.contains("cargo build")
|| lower.contains("cargo clippy")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current check misses common Cargo aliases like cargo t, cargo c, cargo b, and cargo r. It also excludes cargo run, which can fail during compilation. Consider expanding the list of recognized commands.

fn looks_like_cargo_command(command: &str) -> bool {
    let lower = command.to_ascii_lowercase();
    [
        "cargo test",
        "cargo check",
        "cargo build",
        "cargo clippy",
        "cargo run",
        "cargo t",
        "cargo c",
        "cargo b",
        "cargo r",
    ]
    .iter()
    .any(|&cmd| lower.contains(cmd))
}

Comment on lines +139 to +143
fn is_primary_error_line(line: &str) -> bool {
line.starts_with("error[")
|| line.starts_with("error: could not compile")
|| line.starts_with("error: aborting due to")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function misses many rustc error messages that don't have an error code (e.g., error: cannot find value...). Including all lines starting with error: (while excluding known test failure summaries) would capture more actionable signals for the primary_errors list.

fn is_primary_error_line(line: &str) -> bool {
    line.starts_with("error[")
        || (line.starts_with("error:") && !line.starts_with("error: test failed"))
}

Comment on lines +200 to +212
fn truncate_chars(text: &str, max_chars: usize) -> String {
if text.chars().count() <= max_chars {
return text.to_string();
}
let mut end = text.len();
for (count, (idx, _)) in text.char_indices().enumerate() {
if count == max_chars.saturating_sub(3) {
end = idx;
break;
}
}
format!("{}...", &text[..end])
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a minor bug where if max_chars is less than 3, the function returns "...", which is longer than the requested limit. Additionally, text.chars().count() is an O(N) operation that can be avoided by checking the length during iteration.

Suggested change
fn truncate_chars(text: &str, max_chars: usize) -> String {
if text.chars().count() <= max_chars {
return text.to_string();
}
let mut end = text.len();
for (count, (idx, _)) in text.char_indices().enumerate() {
if count == max_chars.saturating_sub(3) {
end = idx;
break;
}
}
format!("{}...", &text[..end])
}
fn truncate_chars(text: &str, max_chars: usize) -> String {
if let Some((idx, _)) = text.char_indices().nth(max_chars) {
if max_chars < 3 {
return text[..idx].to_string();
}
let truncate_at = text
.char_indices()
.nth(max_chars - 3)
.map(|(i, _)| i)
.unwrap_or(0);
format!("{}...", &text[..truncate_at])
} else {
text.to_string()
}
}

@kunpeng-ai-lab kunpeng-ai-lab force-pushed the feature/cargo-failure-summary branch from 91f2eed to f1a7c34 Compare May 14, 2026 13:03
@kunpeng-ai-lab
Copy link
Copy Markdown
Author

Updated #1636 to address the review comments:

  • Replaced stringly-typed kind with a CargoFailureKind enum using snake_case serde output.
  • Removed the redundant empty-summary check after build_summary.
  • Expanded Cargo command detection to include cargo run and common aliases (cargo t, cargo c, cargo b, cargo r).
  • Broadened primary error extraction to include uncoded error: lines while excluding error: test failed summaries.
  • Fixed truncate_chars for small limits and avoided the upfront chars().count() scan.
  • Added regression tests for Cargo aliases, uncoded rustc errors, and tiny truncate limits.
  • Rebased onto latest upstream main.

Validation:

  • cargo fmt --check
  • cargo test -p deepseek-tui --bin deepseek-tui cargo_failure_summary (6 passed)
  • cargo test -p deepseek-tui --bin deepseek-tui run_tests (3 passed)
  • cargo check --workspace

@kunpeng-ai-lab
Copy link
Copy Markdown
Author

Follow-up after an independent code quality review:

  • Preserved the existing top-level summary for shell results instead of overwriting it with Cargo-specific text.
  • Kept Cargo diagnostics under cargo_failure_summary.summary.
  • Suppressed generic Cargo summaries when no actionable signal is extracted, so context compaction keeps the more useful stdout/stderr summary.
  • Tightened Cargo command detection to token-level parsing, including cargo +toolchain ..., global flags, and avoiding false positives like echo cargo test && false.

Validation rerun:

  • cargo fmt --check
  • cargo test -p deepseek-tui --bin deepseek-tui cargo_failure_summary (8 passed)
  • cargo test -p deepseek-tui --bin deepseek-tui shell_delta_result (3 passed)
  • cargo test -p deepseek-tui --bin deepseek-tui run_tests (3 passed)
  • cargo check --workspace

@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented May 23, 2026

This PR was opened before the v0.8.41 rebrand and is now stale. Feel free to rebase onto current main and reopen. 鲸鱼兄弟们等你 🐋

@kunpeng-ai-lab
Copy link
Copy Markdown
Author

Rebased onto current main and reopened as #1973: #1973

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants