Skip to content

feat: cargo_diag backend (BuildDigest, rustc/cargo build diagnostics) - #16

Merged
onlyxItachi merged 1 commit into
mainfrom
feat/v1.2-cargo-diag
Jul 17, 2026
Merged

feat: cargo_diag backend (BuildDigest, rustc/cargo build diagnostics)#16
onlyxItachi merged 1 commit into
mainfrom
feat/v1.2-cargo-diag

Conversation

@onlyxItachi

Copy link
Copy Markdown
Owner

Summary

Adds cargo_diag, a BuildDigest backend for SAVED cargo build --message-format=json
output (NDJSON): per-crate error/warning digests answering "where do this Rust
build's diagnostics concentrate" without spending agent context on rendered
compiler output. Formats cargo-diag + alias cargo-json; suffixes
.cargo-diag.jsonl + .jsonl; new domain constant
DOMAIN_BUILD_DIAG = "build_diag" in core/metrics.py.

Design decisions

  • Units are CRATES, one per distinct package_id seen across
    compiler-artifact AND compiler-message records, in first-appearance
    order — a crate that FAILED to compile emits messages but never an artifact
    and must still be a unit (the error fixture pins exactly this:
    the bin crate has artifacts == 0 but is fully addressable).
  • package_id parsing verified against the real capture before writing the
    parser
    (it changed across cargo versions). cargo 1.94 emits the Package ID
    Spec URL: path+file:///ws/mathlib#0.1.0 — the fragment is a bare version
    when the crate is named like its directory, name@version otherwise
    (registry PURLs). The parser handles the modern URL shape (bare-version and
    name-at-version fragments, git ?rev= queries, .git suffixes), the legacy
    name version (source) string, and falls back to the raw id (addressable,
    never crashing, never inventing a name) on unknown shapes. Unit names are
    short name@version (e.g. mathlib@0.1.0).
  • The honesty story of this backend: duration_us is None for EVERY
    unit.
    cargo's JSON stream carries no timing whatsoever, so this is a whole
    backend whose duration is honestly absent — summarize_report ranks by its
    existing file-order fallback (tested: sorted_by == "file_order", and no
    total_duration_us/coverage keys are fabricated), and get_metrics
    returns not_available_in_this_export for duration_us end-to-end. The
    usage prompt redirects build TIMING questions to ninja-log /
    clang-time-trace.
  • Counts are the opposite honesty case — genuine 0.0. Like ptxas's
    Used ... line, the message stream is a complete enumeration of the
    diagnostics this build emitted, so a crate that appears in the stream with
    no messages has MEASURED zeros. Verified live before shipping the claim:
    a fully cached rebuild (fresh=true artifacts) replays cached warnings as
    compiler-message records, so the enumeration stays complete even when
    nothing recompiles (this became a probe note).
  • Numeric digest, text stays out (translator, not judge): core set is
    errors / warnings / notes_helps (levels partitioned as: starts-with
    error including ICEs; warning; everything else — note/help/rustc's
    span-less failure-note). expand() carries the WHERE story numerically:
    per-level-per-file keys built from the primary span's workspace-relative
    file_name (warning@mathlib/src/lib.rs → 2.0, span-less diags in an
    explicit (no-span) bucket), per-code keys (code:E0308 → 1.0,
    code:dead_code → 1.0), plus artifacts/fresh_artifacts and the raw
    package_id. Reading diagnostic TEXT is the agent's editor/compiler job —
    the usage prompt says exactly that.
  • build-finished.success: the digest schema has no report-level slot and
    core stays unbent, so the report-level outcome rides expand() as a
    build_finished_success bool on every unit — absent entirely (never
    fabricated) if the capture was truncated before the build-finished record.
  • Loud on non-cargo input: a line that is not JSON (a Chrome trace is ONE
    multi-line document — its first line alone fails), not an object, or an
    object without cargo's reason key raises a named ValueError, with a
    redirect hint for the trap case (a perf-stat export is ALSO JSON-lines —
    format perf-stat-json).
  • Shared files: ONE import line in server/app.py::_register_backends(), ONE
    domain constant in core/metrics.py, one PROFILER_TOOLS entry
    ("cargo_diag": "cargo" — same executable as criterion, different artifact).
    Rebased over the criterion (Add criterion backend (Rust benchmark digest, directory-ref reports) #14) and prev-green (Prove previous-green CI comparison on real two-run artifacts (CIDigest) #15) merges; no
    format/suffix collisions (criterion claims criterion/criterion-json
    with report_is_directory=True; cargo_diag is a plain file backend).

Fixture provenance

Real cargo 1.94.0 captures, not hand-fabricated. The 2-crate workspace is
committed for provenance at tests/fixtures/cargo_diag_ws/ (mathlib lib
crate with deliberate unused_variables + dead_code warnings; mathapp
bin crate depending on it — clean main.rs plus the main.rs.error-variant
that produced the error fixture):

  • cargo_build_warnings_sample.cargo-diag.jsonl — fresh build of the clean
    workspace: 2 real warnings on mathlib, mathapp compiles clean,
    build-finished success=true.
  • cargo_build_error_sample.cargo-diag.jsonl — post-cargo clean build after
    introducing a real E0308 (calling add(2, "three")): mathlib's warnings,
    mathapp's error + rustc's failure-note, success=false, and no
    compiler-artifact for the failed crate.

Host-identity check: cargo unavoidably embeds the absolute workspace path in
package_id/manifest_path/artifact filenames, so the capture was run from
a neutral /tmp/perfdigest-ws root — the committed NDJSON contains no
hostname, username, or home path (grep-verified); nothing was hand-edited.
Span file_names are workspace-relative (cargo run from the workspace root —
verified), which is what the per-file concentration keys are built from.

Test evidence

New tests/test_cargo_diag.py, 14 tests: registry dispatch on both formats;
crate units named from real package_ids; duration-None honesty end-to-end
(not_available_in_this_export through get_metrics, all-None in
list_kernels); genuine-0.0 for the clean crate; real error/warning counts
from both fixtures; summarize file-order fallback with no fabricated coverage
keys; expand concentration keys (per-level@file, (no-span) bucket, per-code)

  • substring section filter; report-level build_finished_success via expand
    (true and false fixtures); failed-crate-has-no-artifact; list/expand index
    agreement; synthetic (clearly marked) package_id shape pinning for
    registry/git/legacy variants the path-dependency fixture cannot produce; loud
    ValueErrors on the three non-NDJSON traps; vocabulary hint for foreign terms.

Full suite: uv sync --extra dev && uv run --extra dev pytest -q
182 passed, 16 skipped (168 post-#14/#15 baseline + 14 new;
hardware-gated skip count unchanged).

Test plan

  • uv run pytest -q — 182 passed, 16 skipped (after rebase onto a82e7fd)
  • Cached-rebuild warning-replay claim verified against the live cargo
    toolchain before it shipped as a probe note
  • Manual smoke: platform_capabilities() (digest + capture rows) and
    suggest_profile_command('cargo_diag', ...) on this host

🤖 Generated with Claude Code

…ics)

Digests SAVED `cargo build --message-format=json` NDJSON streams: units are
CRATES (one per distinct package_id across compiler-artifact AND
compiler-message records — a crate that failed to compile emits messages but
no artifact and must still be a unit), named name-at-version parsed from
package_id.

package_id parsing was verified against a real cargo 1.94 capture before
writing the parser: modern cargo emits the Package ID Spec URL
(path+file:///ws/mathlib#0.1.0, bare-version fragment when the crate is named
like its directory; name-at-version fragment otherwise), NOT the legacy
'name version (source)' string — both shapes are handled, plus git PURLs
(?rev= query, .git suffix), with an addressable raw-id fallback for unknown
shapes.

The honesty story of this backend: cargo's stream carries NO timing, so
duration_us is None for EVERY unit (summarize_report falls back to file-order
ranking, and no coverage keys are fabricated). The diagnostic counts
(errors/warnings/notes_helps) are the opposite case: genuine 0.0 for a crate
that compiled clean, because the stream is a complete enumeration (the ptxas
'Used ...' rule). Verified live: a fully cached rebuild (fresh=true) replays
cached warnings, so the enumeration stays complete even when nothing
recompiles.

expand() carries the WHERE story without diagnostic text (translator, not
judge): per-level-per-file counts ('warning@mathlib/src/lib.rs' -> 2.0),
per-code counts ('code:E0308' -> 1.0), and the report-level
build_finished_success bool on every unit (the digest schema has no
report-level slot and core stays unbent).

New domain constant DOMAIN_BUILD_DIAG = "build_diag" in core/metrics.py;
one import line in server/app.py; one PROFILER_TOOLS entry ("cargo").

Fixtures are real cargo 1.94.0 captures from a tiny 2-crate workspace
(committed for provenance at tests/fixtures/cargo_diag_ws/, error variant
included): a warnings build (2 real warnings, success=true) and a
post-cargo-clean error build (real E0308 + failure-note, success=false),
captured from a neutral /tmp/perfdigest-ws root so the absolute paths cargo
unavoidably embeds in package_id carry no host-identifying information;
span file_names are workspace-relative (verified).

163 tests pass (149 baseline + 14 new), 16 hardware-gated skips unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@onlyxItachi
onlyxItachi merged commit 7e87b8a into main Jul 17, 2026
4 checks passed
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.

1 participant