Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a6bdc3f
feat(shell): classified runner output opens with a [clean] or [errors…
santhreal Aug 23, 2026
04c2333
style(shell): rustfmt the result-contract test fixtures
santhreal Aug 23, 2026
89b681b
test(shell): cargo noise the filter drops still opens with a verdict
santhreal Aug 23, 2026
37c0fd6
fix(shell): satisfy clippy on the result-contract parse and counts
santhreal Aug 23, 2026
24eeb03
test(shell): classified captures open with the [clean]/[errors] line
santhreal Aug 23, 2026
7834a1f
test(shell): a clean ctest collapse opens with [clean] ctest
santhreal Aug 23, 2026
5d3c587
fix(shell): drop the trailing comma clippy flags on the ctest collaps…
santhreal Aug 23, 2026
ac1f88a
fix(shell): own the live cargo-contract scratch directory
santhreal Aug 23, 2026
c894f1b
test(coding-agent): record the result-contract prompt cost
santhreal Aug 23, 2026
0609e0f
test(coding-agent): a pending todo row is the shadowed mark
santhreal Aug 23, 2026
07077e7
Merge remote-tracking branch 'origin/main' into feat/result-contract
santhreal Aug 23, 2026
239ca78
fix(shell): harden result contract verdicts
santhreal Aug 23, 2026
fd2e8c7
Merge remote-tracking branch 'origin/main' into feat/result-contract
santhreal Aug 24, 2026
547f1b2
Merge remote-tracking branch 'origin/main' into feat/result-contract
santhreal Aug 24, 2026
72fa595
chore(changelog): regenerate the root changelog after the merge
santhreal Aug 24, 2026
6128001
Merge remote-tracking branch 'origin/main' into feat/result-contract
santhreal Aug 24, 2026
f774583
test(shell): pin the passthrough promise on a filter that still declines
santhreal Aug 24, 2026
79953f9
test(coding-agent): a pending task nobody is on draws the hollow chec…
santhreal Aug 24, 2026
6ec186e
Merge remote-tracking branch 'origin/main' into feat/result-contract
santhreal Aug 24, 2026
d5b612d
Merge remote-tracking branch 'origin/main' into feat/result-contract
santhreal Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

### Changed

- Classified runner output (cargo, bun, Go, ctest, dotnet, clippy, golangci-lint, Gradle lint, pytest, and tsc/eslint-family) now opens with a result-contract header: `[clean] <command>` or `[errors]` / `[errors N] <command>`. The header is the verdict and the body contains retained diagnostics.
- Multi-target `ast_grep` searches now execute concurrently while preserving globally ordered paging, totals, parse errors, cancellation, and target-order failures.
- The vibe screens, the image-inspection call and an LSP hover code block draw no border of their own inside a tool block, so a block keeps one left edge; a tree connector remains only where a row belongs to the row above it, in the eval value tree, the grep line gutter, the job tree and the LSP reference tree.
- A picture a terminal will not draw now leaves a row naming the file, the media type, the pixel size and the cause, in place of `[Image: image/png]`, including when a Kitty session cannot convert it to PNG.
Expand Down
1 change: 1 addition & 0 deletions crates/veyyon-shell/src/minimizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! The engine is inert unless a [`MinimizerConfig`] explicitly opts in.

pub mod config;
pub mod contract;
pub mod detect;
pub mod engine;
pub mod filters;
Expand Down
366 changes: 366 additions & 0 deletions crates/veyyon-shell/src/minimizer/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
//! The one result header a classified command writes.
//!
//! Filters decide a [`Verdict`]. This module writes the header and is the one
//! place that recognizes it again. A later pass that cannot tell our header
//! from program output will reclassify the capture, stack a second header, or
//! treat `[errors]` as a rustc diagnostic. Those are the same class of bug
//! `primitives::is_minimizer_annotation` already closes for every other
//! marker we write.
//!
//! Grammar, one line, always first:
//!
//! ```text
//! [clean] <subject>
//! [clean] <subject>: <detail>
//! [errors] <subject>
//! [errors] <subject>: <detail>
//! [errors N] <subject>
//! [errors N] <subject>: <detail>
//! ```
//!
//! `<subject>` is the command the filter classified (`cargo test`, `bun test`,
//! `ctest`). `<detail>` is optional and owns counts the filter already had
//! (`262 passed (1 suite)`). Unknown error counts use the bare `[errors]`
//! form rather than inventing a number.
//!
//! A command with no adapter does not go through this module. Unclassified
//! output stays a transcript.

use std::fmt::Write as _;

/// Whether the classified command is clean or produced errors.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Status {
/// No errors. Warnings, if any, belong in [`Verdict::detail`].
Clean,
/// At least one error, failure, or non-zero diagnostic the caller must act
/// on.
Errors,
}

/// A classified result. Filters construct one; [`render`] / [`apply`] write it.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Verdict {
pub status: Status,
pub subject: String,
/// Count of errors when the filter knew one. `None` means unknown.
/// Meaningless on [`Status::Clean`].
pub errors: Option<u64>,
pub detail: Option<String>,
}

/// A header line parsed back from text we wrote.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParsedHeader {
pub status: Status,
pub subject: String,
pub errors: Option<u64>,
pub detail: Option<String>,
}

/// A clean result for `subject`, with no detail.
#[must_use]
pub fn clean(subject: impl Into<String>) -> Verdict {
Verdict { status: Status::Clean, subject: subject.into(), errors: None, detail: None }
}

/// A clean result whose detail carries the filter's existing summary counts.
#[must_use]
pub fn clean_with(subject: impl Into<String>, detail: impl Into<String>) -> Verdict {
Verdict {
status: Status::Clean,
subject: subject.into(),
errors: None,
detail: Some(detail.into()),
}
}

/// An error result whose filter counted the failures.
#[must_use]
pub fn errors(subject: impl Into<String>, count: u64) -> Verdict {
Verdict { status: Status::Errors, subject: subject.into(), errors: Some(count), detail: None }
}

/// An error result whose filter could not count the failures.
#[must_use]
pub fn errors_unknown(subject: impl Into<String>) -> Verdict {
Verdict { status: Status::Errors, subject: subject.into(), errors: None, detail: None }
}

/// An error result with both a count and a retained summary detail.
#[must_use]
pub fn errors_with(subject: impl Into<String>, count: u64, detail: impl Into<String>) -> Verdict {
Verdict {
status: Status::Errors,
subject: subject.into(),
errors: Some(count),
detail: Some(detail.into()),
}
}

/// Write the header line, including the trailing newline.
#[must_use]
pub fn render(verdict: &Verdict) -> String {
let mut out = String::new();
match (verdict.status, verdict.errors) {
(Status::Clean, _) => out.push_str("[clean]"),
(Status::Errors, Some(count)) => {
let _ = write!(out, "[errors {count}]");
},
(Status::Errors, None) => out.push_str("[errors]"),
}
out.push(' ');
out.push_str(verdict.subject.trim());
if let Some(detail) = verdict
.detail
.as_deref()
.map(str::trim)
.filter(|d| !d.is_empty())
{
out.push_str(": ");
out.push_str(detail);
}
out.push('\n');
out
}

/// Parse a line as a header we wrote, or `None` for any other line.
#[must_use]
pub fn parse(line: &str) -> Option<ParsedHeader> {
let trimmed = line.trim();
let (status, errors, after_tag) = if let Some(rest) = trimmed.strip_prefix("[clean]") {
(Status::Clean, None, rest)
} else if let Some(rest) = trimmed.strip_prefix("[errors]") {
(Status::Errors, None, rest)
} else {
let rest = trimmed.strip_prefix("[errors ")?;
let (count_text, after_bracket) = rest.split_once(']')?;
if count_text.is_empty() || !count_text.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
let count = count_text.parse().ok()?;
(Status::Errors, Some(count), after_bracket)
};

let rest = after_tag.strip_prefix(' ')?.trim();
if rest.is_empty() {
return None;
}
let (subject, detail) = match rest.split_once(": ") {
Some((subject, detail)) if !subject.is_empty() => {
(subject.to_string(), Some(detail.to_string()))
},
_ => (rest.to_string(), None),
};
Some(ParsedHeader { status, subject, errors, detail })
}

/// True when this line is a result header this module wrote.
#[must_use]
pub fn is_result_header(line: &str) -> bool {
parse(line).is_some()
}

/// True when the first non-empty line of `text` matches `verdict`.
///
/// A syntactically valid header is not enough: command output is untrusted and
/// may contain header-shaped text. Only the exact verdict this filter computed
/// proves that the body is a replay of its own classified output.
fn already_classified_as(text: &str, verdict: &Verdict) -> bool {
let Some(parsed) = text
.lines()
.map(str::trim)
.find(|line| !line.is_empty())
.and_then(parse)
else {
return false;
};
let expected_detail = verdict
.detail
.as_deref()
.map(str::trim)
.filter(|detail| !detail.is_empty());
parsed.status == verdict.status
&& parsed.errors == verdict.errors
&& parsed.subject == verdict.subject.trim()
&& parsed.detail.as_deref() == expected_detail
}

/// True when a replayed result header agrees with the process exit status.
///
/// The dispatcher uses this before a filter reparses its own compact body. A
/// header-shaped program line cannot turn a failed process into `[clean]` (or
/// a successful process into `[errors]`), while a genuine replay retains
/// summary details that no longer exist in the compact body.
#[must_use]
pub fn replay_matches_exit(text: &str, exit_code: i32) -> bool {
let expected = if exit_code == 0 {
Status::Clean
} else {
Status::Errors
};
text
.lines()
.map(str::trim)
.find(|line| !line.is_empty())
.and_then(parse)
.is_some_and(|header| header.status == expected)
}

/// Classify by process exit: zero is clean, anything else is unknown-count
/// errors.
///
/// Filters that already counted failures should call [`errors`] / [`apply`]
/// themselves. This is the shared path for "the process told us, we did not
/// count" so cargo/check/fmt/install and the C++/dotnet cousins do not each
/// re-open the same if/else.
#[must_use]
pub fn from_exit(subject: impl Into<String>, exit_code: i32, body: &str) -> String {
let subject = subject.into();
let verdict = if exit_code == 0 {
clean(subject)
} else {
errors_unknown(subject)
};
apply(&verdict, body)
}

/// Prepend the header unless `body` already starts with the same verdict.
///
/// An empty body becomes the header alone. A body with the exact verdict is
/// returned unchanged (plus a trailing newline if it was missing one).
#[must_use]
pub fn apply(verdict: &Verdict, body: &str) -> String {
if already_classified_as(body, verdict) {
let mut out = body.to_string();
if !out.is_empty() && !out.ends_with('\n') {
out.push('\n');
}
return out;
}
let mut out = render(verdict);
let body = body.trim_start_matches('\n');
if body.is_empty() {
return out;
}
out.push_str(body);
if !out.ends_with('\n') {
out.push('\n');
}
out
}

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

#[test]
fn render_clean_with_and_without_detail() {
assert_eq!(render(&clean("ctest")), "[clean] ctest\n");
assert_eq!(
render(&clean_with("cargo test", "262 passed (1 suite)")),
"[clean] cargo test: 262 passed (1 suite)\n"
);
}

#[test]
fn render_errors_known_and_unknown() {
assert_eq!(render(&errors("bun test", 3)), "[errors 3] bun test\n");
assert_eq!(render(&errors_unknown("cargo check")), "[errors] cargo check\n");
assert_eq!(render(&errors_with("go test", 1, "1 failed")), "[errors 1] go test: 1 failed\n");
}

#[test]
fn parse_round_trips_every_form() {
for verdict in [
clean("ctest"),
clean_with("cargo test", "262 passed (1 suite, 17 warnings)"),
errors("bun test", 3),
errors_unknown("cargo check"),
errors_with("go test", 1, "1 failed"),
] {
let line = render(&verdict);
let parsed = parse(&line).unwrap_or_else(|| panic!("did not parse {line:?}"));
assert_eq!(parsed.status, verdict.status, "{line}");
assert_eq!(parsed.subject, verdict.subject, "{line}");
assert_eq!(parsed.errors, verdict.errors, "{line}");
assert_eq!(parsed.detail, verdict.detail, "{line}");
}
}

#[test]
fn parse_rejects_program_output() {
for line in [
"error[E0277]: the trait bound is not satisfied",
"error: could not compile `foo`",
"failures:",
"---- bad stdout ----",
"test result: FAILED. 0 passed; 1 failed",
"[clean]",
"[errors]",
"[errors x] cargo test",
"[errors ] cargo test",
"note: see [clean] docs",
"cargo test: 262 passed (1 suite)",
] {
assert!(parse(line).is_none(), "{line:?} is not a header");
}
}

#[test]
fn apply_is_idempotent() {
let verdict = clean_with("cargo test", "2 passed (1 suite)");
let first = apply(&verdict, "");
assert_eq!(first, "[clean] cargo test: 2 passed (1 suite)\n");
assert_eq!(apply(&verdict, &first), first);
assert_eq!(
apply(&errors("cargo test", 1), &first),
"[errors 1] cargo test\n[clean] cargo test: 2 passed (1 suite)\n"
);
}

#[test]
fn apply_keeps_a_failure_body() {
let body = "failures:\n bad_parse\n";
let out = apply(&errors("cargo test", 1), body);
assert_eq!(out, "[errors 1] cargo test\nfailures:\n bad_parse\n");
assert_eq!(apply(&errors("cargo test", 1), &out), out);
}

#[test]
fn apply_trusts_only_the_verdict_the_filter_computed() {
let cases = [
(clean("cargo test"), "[errors 1] cargo test\n"),
(errors("cargo test", 2), "[errors 1] cargo test\n"),
(errors("cargo test", 1), "[errors 1] ctest\n"),
(clean_with("cargo test", "2 passed"), "[clean] cargo test: 1 passed\n"),
];
for (verdict, spoofed) in cases {
let out = apply(&verdict, spoofed);
assert_eq!(out, format!("{}{spoofed}", render(&verdict)));
}
}

#[test]
fn replay_requires_status_to_match_the_process_exit() {
assert!(replay_matches_exit("[clean] cargo test: 2 passed\n", 0));
assert!(replay_matches_exit("[errors 2] cargo test\n", 101));
assert!(!replay_matches_exit("[clean] cargo test\n", 101));
assert!(!replay_matches_exit("[errors] cargo test\n", 0));
assert!(!replay_matches_exit("program output\n", 0));
}

#[test]
fn from_exit_classifies_zero_and_nonzero() {
assert_eq!(from_exit("cargo check", 0, ""), "[clean] cargo check\n");
assert_eq!(
from_exit("cargo check", 101, "error: nope\n"),
"[errors] cargo check\nerror: nope\n"
);
let classified = from_exit("cargo check", 0, "");
assert_eq!(
from_exit("cargo check", 1, &classified),
"[errors] cargo check\n[clean] cargo check\n"
);
}
}
Loading
Loading