Skip to content

feat(engine): capture complete Cargo analysis observations - #162

Merged
EffortlessSteven merged 2 commits into
mainfrom
engine/issue-157
Aug 3, 2026
Merged

feat(engine): capture complete Cargo analysis observations#162
EffortlessSteven merged 2 commits into
mainfrom
engine/issue-157

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Summary

Closes #157.

  • Adds the internal CargoAnalysis evidence model in lintdiff-engine with explicit scope and completion state.
  • Preserves one observation per Cargo compiler-message, producer package/target identity, raw diagnostic/span data, child suggestions, and build-finished evidence.
  • Adds repository-root-aware path earning while preserving the legacy parse_cargo_messages wrapper behavior.
  • Keeps runtime failure evidence explicit without adding a public inventory/delta schema or CLI.

Proof

  • cargo fmt --all -- --check — pass
  • cargo clippy --workspace --all-targets --all-features -- -D warnings — pass
  • cargo test --workspace --all-features — pass
  • cargo test --workspace --doc — pass as part of workspace test
  • cargo bench --workspace --no-run — pass
  • cargo deny --all-features check — pass with existing dependency warnings
  • all five xtask checks — pass
  • cargo +nightly fuzz build diagnostics_parser — pass
  • cargo +nightly fuzz build diff_parser — pass
  • cargo +nightly fuzz build finding_fingerprint — pass
  • git diff --check — pass

Claim boundary

This PR provides the evidence spine needed by #158 and #102. It does not serialize inventory.v1, change current report construction, implement source correspondence or pairing, or authorize release/tag/publication.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@EffortlessSteven, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bb0ea94a-fcf8-460e-8f5a-540ac6fc6057

📥 Commits

Reviewing files that changed from the base of the PR and between ab6a565 and 137e3d8.

📒 Files selected for processing (1)
  • crates/lintdiff-engine/src/diagnostics/mod.rs

Summary by CodeRabbit

  • New Features
    • Added richer Cargo analysis results, including producer metadata, analysis scope, execution details, completion status, and diagnostic observations.
    • Added support for nested diagnostics, suggestions, raw spans, and normalized repository-relative paths.
    • Added handling for successful, failed, incomplete, and runtime-failure analyses.
    • Exposed new analysis parsing APIs for broader integration.

Walkthrough

The diagnostics parser now returns complete Cargo analyses with producer metadata, observations, raw and normalized spans, nested diagnostics, suggestions, completion states, and execution evidence. Existing diagnostic and status APIs remain available through compatibility wrappers.

Changes

Cargo analysis capture

Layer / File(s) Summary
Analysis models and public API
crates/lintdiff-engine/src/diagnostics/mod.rs, crates/lintdiff-engine/src/lib.rs
Adds public models for analysis scope, producer and target metadata, observations, spans, completion, execution evidence, and complete analyses. Re-exports the new parsing APIs and types.
Analysis parsing and diagnostic evidence
crates/lintdiff-engine/src/diagnostics/mod.rs
Parses Cargo metadata, compiler messages, completion states, raw and normalized spans, child diagnostics, and suggestions. Supports optional repository-root path normalization.
Compatibility behavior and parser tests
crates/lintdiff-engine/src/diagnostics/mod.rs
Adapts complete analyses to existing diagnostic APIs. Tests cover metadata, path handling, runtime failures, incomplete streams, and per-message observations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Cargo
  participant parse_cargo_analysis
  participant CargoAnalysis
  participant CompatibilityAPI
  Cargo->>parse_cargo_analysis: compiler-message and build-finished JSON
  parse_cargo_analysis->>CargoAnalysis: observations, metadata, spans, completion
  CargoAnalysis->>CompatibilityAPI: normalized legacy diagnostics
Loading

Possibly related issues

Possibly related PRs

Poem

A rabbit parses messages in a neat little row,
Keeps every span and the paths that they show.
“Build finished!” or “incomplete!” states now appear,
While old APIs remain hopping close by.
🐇 The Cargo analysis trail is clear.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary change: capturing complete Cargo analysis observations in the engine.
Description check ✅ Passed The description directly explains the analysis model, preserved evidence, compatibility behavior, scope, and verification results.
Linked Issues check ✅ Passed The changes address issue #157 by adding complete observations, stream completion states, execution metadata, path handling, and compatibility parsing.
Out of Scope Changes check ✅ Passed The changes remain within the parser and diagnostic model and do not add inventory schemas, CLI features, orchestration, or release functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch engine/issue-157

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/lintdiff-engine/src/diagnostics/mod.rs`:
- Around line 827-830: Update the diagnostic test around parse_cargo_analysis to
use parse_cargo_analysis_with_repo_root with a repository root, then provide
sibling spans covering line_start 0 and a valid positive line. Assert the
zero-based span has normalized == None and the valid-line span has normalized ==
Some, so the line_start > 0 guard is exercised rather than masked by a missing
repository path.
- Around line 644-654: Make repository_path platform-independent by normalizing
both raw_file_name and repo_root separators to forward slashes, detecting
absolute paths using the normalized representation, and removing the normalized
repository root via string-prefix matching rather than Path::strip_prefix;
update crates/lintdiff-engine/src/diagnostics/mod.rs lines 644-654 accordingly.
In the test fixture at crates/lintdiff-engine/src/diagnostics/mod.rs lines
841-855, retain the Windows-style case and add a POSIX-style case, with both
asserting the normalized result src/lib.rs.
- Line 378: Update the documentation comment near the Cargo JSONL parser to say
it “derives” repository-relative paths instead of “earns” them, and rename the
related test to repository_root_derives_context_correct_absolute_paths.
- Around line 187-263: Add concise rustdoc comments to every public field in
ProducerUnit, CargoTarget, AnalysisScope, ObservationSpan, DiagnosticSuggestion,
DiagnosticChild, and DiagnosticObservation, describing each field’s purpose and
preserving the existing struct-level documentation.
- Around line 274-283: Make the completion field in UpstreamExecution store
AnalysisCompletion directly instead of Option, and add
AnalysisCompletion::default() returning IncompleteStream so
UpstreamExecution::default() remains valid. Update all construction sites,
including parse_cargo_analysis_with_repo_root and
CargoAnalysis::runtime_failure, to assign direct values, remove the unreachable
InvalidShape line: 0 branch from parse_cargo_messages_with_status, and adjust
affected test assertions.
- Around line 405-419: The build-finished handling in parse_cargo_messages
currently reads invocation scope fields that Cargo does not emit. Remove the
toolchain, target, and features extraction from the build-finished branch,
provide the invocation scope separately, and thread it through
parse_cargo_messages into parse_cargo_analysis so AnalysisScope is populated
from that supplied context; update the fixture to match Cargo’s build-finished
schema containing only reason and success.
- Around line 538-543: Update parse_producer so ProducerUnit does not expose the
full Cargo profile under the misleading profile field while retaining only
opt_level; rename the stored field and its consumers to opt_level, or introduce
a dedicated profile structure that captures opt_level, debuginfo,
debug_assertions, overflow_checks, and test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a3fd5daa-2923-4522-856d-63cea4d5505d

📥 Commits

Reviewing files that changed from the base of the PR and between b098412 and ab6a565.

📒 Files selected for processing (2)
  • crates/lintdiff-engine/src/diagnostics/mod.rs
  • crates/lintdiff-engine/src/lib.rs

Comment on lines +187 to +263
/// The Cargo producer identity attached to one compiler-message emission.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ProducerUnit {
pub package_id: Option<String>,
pub manifest_path: Option<String>,
pub target: Option<CargoTarget>,
pub profile: Option<String>,
}

/// The package target that produced a compiler message.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CargoTarget {
pub name: Option<String>,
pub kind: Vec<String>,
pub crate_types: Vec<String>,
pub src_path: Option<String>,
pub edition: Option<String>,
}

/// Hard and contextual inputs that determine whether two analyses are comparable.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct AnalysisScope {
pub repository: Option<String>,
pub revision: Option<String>,
pub toolchain: Option<String>,
pub target: Option<String>,
pub features: Vec<String>,
pub package_selection: Vec<String>,
pub target_selection: Vec<String>,
pub lint_config_hash: Option<String>,
}

/// A source span retaining raw Cargo values alongside the current normalized span.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ObservationSpan {
pub raw_file_name: Option<String>,
pub raw_line_start: Option<u32>,
pub raw_line_end: Option<u32>,
pub raw_column_start: Option<u32>,
pub raw_column_end: Option<u32>,
pub normalized: Option<Span>,
pub is_primary: bool,
}

/// A suggestion emitted as part of a compiler child diagnostic.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiagnosticSuggestion {
pub file_name: Option<String>,
pub line_start: Option<u32>,
pub line_end: Option<u32>,
pub replacement: Option<String>,
pub applicability: Option<String>,
}

/// A child note, help message, or nested diagnostic.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiagnosticChild {
pub raw_level: String,
pub level: DiagnosticLevel,
pub message: String,
pub rendered: Option<String>,
pub spans: Vec<ObservationSpan>,
pub suggestions: Vec<DiagnosticSuggestion>,
}

/// One Cargo compiler-message observation, before filtering or policy.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiagnosticObservation {
pub producer: ProducerUnit,
pub raw_level: String,
pub raw_code: Option<String>,
pub message: String,
pub rendered: Option<String>,
pub spans: Vec<ObservationSpan>,
pub children: Vec<DiagnosticChild>,
pub diagnostic: Diagnostic,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for missing_docs lint configuration in the crate and workspace.
fd -t f 'lib.rs' crates/lintdiff-engine/src --exec sed -n '1,20p' {}
fd -t f 'Cargo.toml' --exec rg -n 'missing_docs|\[lints' {} \;

Repository: EffortlessMetrics/lintdiff

Length of output: 910


🏁 Script executed:

#!/bin/bash
set -u

echo "== Cargo lints / deny settings =="
for f in $(fd -t f 'Cargo.toml|^\.cargo/config.toml|Cargo.lock$' .); do
  echo "--- $f"
  rg -n 'missing_docs|deny\(.*docs|warn\(.*docs|forbid\(.*docs|\[lints\b|\[workspace\.lints' "$f" || true
done

echo
echo "== lintdiff-engine diagnostics relevant sections =="
sed -n '1,120p' crates/lintdiff-engine/src/diagnostics/mod.rs
sed -n '120,200p' crates/lintdiff-engine/src/diagnostics/mod.rs
sed -n '260,280p' crates/lintdiff-engine/src/diagnostics/mod.rs

Repository: EffortlessMetrics/lintdiff

Length of output: 7637


Add field-level documentation to the new public types.

AnalysisScope, ObservationSpan, DiagnosticSuggestion, DiagnosticChild, DiagnosticObservation, ProducerUnit, and CargoTarget document the struct but leave public fields undoced. Cargo does not enable missing_docs, but field docs keep this module consistent with existing public API shapes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lintdiff-engine/src/diagnostics/mod.rs` around lines 187 - 263, Add
concise rustdoc comments to every public field in ProducerUnit, CargoTarget,
AnalysisScope, ObservationSpan, DiagnosticSuggestion, DiagnosticChild, and
DiagnosticObservation, describing each field’s purpose and preserving the
existing struct-level documentation.

Comment on lines +274 to +283
/// Process and Cargo completion evidence for one analysis.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct UpstreamExecution {
pub command: Vec<String>,
pub exit_code: Option<i32>,
pub duration_ms: Option<u64>,
pub build_finished_seen: bool,
pub build_success: Option<bool>,
pub completion: Option<AnalysisCompletion>,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Make completion non-optional in UpstreamExecution.

Every construction site sets completion to Some(..). parse_cargo_analysis_with_repo_root sets it at line 447. CargoAnalysis::runtime_failure sets it at line 302. The Option therefore models a state that the code never produces. It forces parse_cargo_messages_with_status (lines 464-469) to build an InvalidShape error with the meaningless line: 0 for an unreachable case.

Store AnalysisCompletion directly and delete the unreachable error branch.

♻️ Proposed change
 pub struct UpstreamExecution {
     pub command: Vec<String>,
     pub exit_code: Option<i32>,
     pub duration_ms: Option<u64>,
     pub build_finished_seen: bool,
     pub build_success: Option<bool>,
-    pub completion: Option<AnalysisCompletion>,
+    pub completion: AnalysisCompletion,
 }

AnalysisCompletion then needs a Default implementation returning IncompleteStream so that UpstreamExecution::default() keeps working. Update the assertions in the tests accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lintdiff-engine/src/diagnostics/mod.rs` around lines 274 - 283, Make
the completion field in UpstreamExecution store AnalysisCompletion directly
instead of Option, and add AnalysisCompletion::default() returning
IncompleteStream so UpstreamExecution::default() remains valid. Update all
construction sites, including parse_cargo_analysis_with_repo_root and
CargoAnalysis::runtime_failure, to assign direct values, remove the unreachable
InvalidShape line: 0 branch from parse_cargo_messages_with_status, and adjust
affected test assertions.

parse_cargo_analysis_with_repo_root(reader, None)
}

/// Parse Cargo JSONL and earn repository-relative paths from a known root.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the doc comment wording.

"earn repository-relative paths" is not meaningful. Use "derive".

📝 Proposed change
-/// Parse Cargo JSONL and earn repository-relative paths from a known root.
+/// Parse Cargo JSONL and derive repository-relative paths from a known root.

The test name at line 842 uses the same word. Rename it to repository_root_derives_context_correct_absolute_paths.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Parse Cargo JSONL and earn repository-relative paths from a known root.
/// Parse Cargo JSONL and derive repository-relative paths from a known root.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lintdiff-engine/src/diagnostics/mod.rs` at line 378, Update the
documentation comment near the Cargo JSONL parser to say it “derives”
repository-relative paths instead of “earns” them, and rename the related test
to repository_root_derives_context_correct_absolute_paths.

Comment on lines +405 to 419
let reason = value.get("reason").and_then(Value::as_str);
if reason == Some("build-finished") {
build_finished = true;
build_success = v.get("success").and_then(|x| x.as_bool());
build_finished_seen = true;
build_success = value.get("success").and_then(Value::as_bool);
scope.toolchain = value
.get("toolchain")
.and_then(Value::as_str)
.map(str::to_string);
scope.target = value
.get("target")
.and_then(Value::as_str)
.map(str::to_string);
scope.features = string_array(value.get("features"));
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

cargo JSON build-finished message fields reason success schema

💡 Result:

When running Cargo with --message-format=json, the build-finished message is emitted at the end of the build process to indicate that Cargo has finished producing JSON messages [1][2]. The JSON schema for this message is: { "reason": "build-finished", "success": true } Fields: reason (string): Always set to "build-finished" [1][3]. success (boolean): Indicates whether the build completed successfully (true) or failed (false) [1][4]. This message is useful for external tools to identify when Cargo will no longer produce additional JSON-formatted messages, although other output (such as stdout/stderr from a process executed by cargo run) may still follow [1][5]. It was introduced in Rust 1.44 [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate diagnostics/mod.rs =="
git ls-files | rg 'crates/lintdiff-engine/src/diagnostics/mod\.rs$|Cargo\.lock$|Cargo\.toml$' | sed -n '1,80p'

echo
echo "== relevant source slice =="
sed -n '350,440p' crates/lintdiff-engine/src/diagnostics/mod.rs

echo
echo "== tests around build-finished =="
sed -n '760,830p' crates/lintdiff-engine/src/diagnostics/mod.rs

echo
echo "== struct/function signatures in diagnostics/mod.rs =="
ast-grep outline crates/lintdiff-engine/src/diagnostics/mod.rs --view expanded | sed -n '1,220p' 2>/dev/null || true

echo
echo "== search for parse build function usages =="
rg -n "parse .*build|BuildScope|build-scope|toolchain|target|features" crates/lintdiff-engine/src crates/lintdiff -S | sed -n '1,160p'

Repository: EffortlessMetrics/lintdiff

Length of output: 17213


Do not read toolchain, target, and features from build-finished.

Cargo JSON build-finished only contains reason and success, so these lines leave AnalysisScope fields empty for real Cargo output. Provide the invocation scope information separately and pass it into parse_cargo_messages/parse_cargo_analysis instead, then use a Cargo-schema-aligned fixture.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lintdiff-engine/src/diagnostics/mod.rs` around lines 405 - 419, The
build-finished handling in parse_cargo_messages currently reads invocation scope
fields that Cargo does not emit. Remove the toolchain, target, and features
extraction from the build-finished branch, provide the invocation scope
separately, and thread it through parse_cargo_messages into parse_cargo_analysis
so AnalysisScope is populated from that supplied context; update the fixture to
match Cargo’s build-finished schema containing only reason and success.

Comment on lines +538 to +543
profile: value
.get("profile")
.and_then(Value::as_object)
.and_then(|profile| profile.get("opt_level"))
.and_then(Value::as_str)
.map(str::to_string),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

cargo JSON compiler-artifact profile object fields opt_level debuginfo

💡 Result:

In Cargo's JSON machine-readable output (specifically the compiler-artifact message), the profile object provides metadata about the compiler settings used for a target [1][2]. The profile object contains the following fields: opt_level: A string representing the optimization level (e.g., "0", "1", "2", "3", "s", or "z") [1][3]. debuginfo: Specifies the level of debug information included [1][2]. This field can be an integer (0, 1, or 2) or a string (such as "line-directives-only" or "line-tables-only") [1][4]. If null, it defaults to 0 [1][2]. debug_assertions: A boolean indicating whether debug assertions (like those triggered by debug_assert!) are enabled [1][3]. overflow_checks: A boolean indicating whether integer overflow checks are enabled [1][3]. test: A boolean indicating whether the target was compiled with test support (e.g., if the --test flag was used) [1][3]. The JSON structure is used by external tools to understand the build environment [1][5]. While some third-party metadata crates historically represented debuginfo as a simple integer, modern Cargo may emit string-based debug information levels, which requires handling both types in consuming applications [4][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files 'crates/lintdiff-engine/src/diagnostics/mod.rs' 'crates/*Cargo.toml' 'Cargo.toml' | sed -n '1,80p'

echo
echo "== diagnostics outline =="
ast-grep outline crates/lintdiff-engine/src/diagnostics/mod.rs --view expanded 2>/dev/null | sed -n '1,160p' || true

echo
echo "== relevant diagnostics lines =="
cat -n crates/lintdiff-engine/src/diagnostics/mod.rs | sed -n '480,560p'

echo
echo "== DiagnosticObservation and related types =="
rg -n "struct DiagnosticObservation|profile:|opt_level|Compiler|compiler-" crates/lintdiff-engine/src/diagnostics/mod.rs crates/lintdiff-engine/src -g '*.rs' | sed -n '1,220p'

echo
echo "== search for cargo message crate usage =="
rg -n "cargo_metadata|cargo_metadata::|compiler-artifact|compiler-message|JsonMessage|Message" . -g '*.rs' -g 'Cargo.toml' | sed -n '1,240p'

Repository: EffortlessMetrics/lintdiff

Length of output: 45739


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files 'crates/lintdiff-engine/src/diagnostics/mod.rs' 'crates/*Cargo.toml' 'Cargo.toml' | sed -n '1,80p'

echo
echo "== diagnostics outline =="
ast-grep outline crates/lintdiff-engine/src/diagnostics/mod.rs --view expanded 2>/dev/null | sed -n '1,160p' || true

echo
echo "== relevant diagnostics lines =="
cat -n crates/lintdiff-engine/src/diagnostics/mod.rs | sed -n '480,560p'

echo
echo "== DiagnosticObservation and related types =="
rg -n "struct DiagnosticObservation|profile:|opt_level|Compiler|compiler-" crates/lintdiff-engine/src/diagnostics/mod.rs crates/lintdiff-engine/src -g '*.rs' | sed -n '1,220p'

echo
echo "== search for cargo message crate usage =="
rg -n "cargo_metadata|cargo_metadata::|compiler-artifact|compiler-message|JsonMessage|Message" . -g '*.rs' -g '*.toml' | sed -n '1,240p'

Repository: EffortlessMetrics/lintdiff

Length of output: 50382


🌐 Web query:

docs.rs cargo_metadata ArtifactProfile fields rustdoc

💡 Result:

In the cargo_metadata crate, the ArtifactProfile struct represents the profile settings used to determine compiler flags for a target [1]. This struct is marked as #[non_exhaustive], meaning its definition may change in future versions [1]. As of the latest documentation, ArtifactProfile contains the following fields [1][2]: - opt_level (String): The optimization level; common values include 0-3, s, or z [1][2]. - debuginfo (ArtifactDebuginfo): The type of debug information included in the artifact [1][2]. - debug_assertions (bool): The state of the cfg(debug_assertions) directive, which controls macros like debug_assert! [1][2]. - overflow_checks (bool): The state of overflow checks [1][2]. - test (bool): Indicates whether the profile is for a test [1][2]. Because the struct is marked as non-exhaustive, it cannot be constructed using traditional struct syntax and cannot be matched against without a wildcard (..), and struct update syntax is not supported [1]. A builder pattern is available if the builder feature is enabled in the crate [3].

Citations:


Preserve the full Cargo profile in ProducerUnit.

parse_producer stores only profile.opt_level in producer.profile, but Cargo compiler-artifact profile objects also include debuginfo, debug_assertions, overflow_checks, and test. Rename this field to opt_level, or replace it with a dedicated profile struct that preserves all fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lintdiff-engine/src/diagnostics/mod.rs` around lines 538 - 543, Update
parse_producer so ProducerUnit does not expose the full Cargo profile under the
misleading profile field while retaining only opt_level; rename the stored field
and its consumers to opt_level, or introduce a dedicated profile structure that
captures opt_level, debuginfo, debug_assertions, overflow_checks, and test.

Comment on lines +644 to +654
fn repository_path(raw_file_name: Option<&str>, repo_root: Option<&Path>) -> Option<NormPath> {
let raw_file_name = raw_file_name?;
let repo_root = repo_root?;
let raw = Path::new(raw_file_name);
if raw.is_absolute() {
let relative = raw.strip_prefix(repo_root).ok()?;
relative.to_str().map(NormPath::from_repo_path)
} else {
Some(NormPath::from_repo_path(raw_file_name))
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Path normalization relies on host-OS std::path semantics. repository_path uses Path::is_absolute and Path::strip_prefix, which interpret drive letters and backslashes only on Windows. The same Cargo stream therefore normalizes differently per platform, and the accompanying test encodes the Windows result as the expected value.

  • crates/lintdiff-engine/src/diagnostics/mod.rs#L644-L654: convert separators to forward slashes first, then strip the normalized repository root by string prefix instead of Path::strip_prefix.
  • crates/lintdiff-engine/src/diagnostics/mod.rs#L841-L855: keep the Windows fixture and add a POSIX fixture, so both cases assert src/lib.rs on every platform.

As per coding guidelines: "Maintain deterministic outputs: same inputs must produce byte-identical outputs with stable ordering and reproducible truncation".

📍 Affects 1 file
  • crates/lintdiff-engine/src/diagnostics/mod.rs#L644-L654 (this comment)
  • crates/lintdiff-engine/src/diagnostics/mod.rs#L841-L855
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lintdiff-engine/src/diagnostics/mod.rs` around lines 644 - 654, Make
repository_path platform-independent by normalizing both raw_file_name and
repo_root separators to forward slashes, detecting absolute paths using the
normalized representation, and removing the normalized repository root via
string-prefix matching rather than Path::strip_prefix; update
crates/lintdiff-engine/src/diagnostics/mod.rs lines 644-654 accordingly. In the
test fixture at crates/lintdiff-engine/src/diagnostics/mod.rs lines 841-855,
retain the Windows-style case and add a POSIX-style case, with both asserting
the normalized result src/lib.rs.

Source: Coding guidelines

Comment on lines +827 to +830
let span = observation.spans.first().ok_or("missing span")?;
assert_eq!(span.raw_line_start, Some(0));
assert!(span.normalized.is_none());
assert_eq!(observation.diagnostic.spans[0].line_start, 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

This assertion does not verify the line_start > 0 guard.

parse_cargo_analysis passes repo_root = None. repository_path then returns None for every input, so normalized is None regardless of raw_line_start. The assertion at line 829 passes for any span value.

Use parse_cargo_analysis_with_repo_root with a root, and assert that a span with line_start of 0 yields normalized == None while a sibling span with a valid line yields Some.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lintdiff-engine/src/diagnostics/mod.rs` around lines 827 - 830, Update
the diagnostic test around parse_cargo_analysis to use
parse_cargo_analysis_with_repo_root with a repository root, then provide sibling
spans covering line_start 0 and a valid positive line. Assert the zero-based
span has normalized == None and the valid-line span has normalized == Some, so
the line_start > 0 guard is exercised rather than masked by a missing repository
path.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab6a565bd0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let raw_file_name = raw_file_name?;
let repo_root = repo_root?;
let raw = Path::new(raw_file_name);
if raw.is_absolute() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle Windows paths independently of the host OS

When Windows-generated JSON is parsed on Ubuntu or macOS, std::path::Path does not recognize C:\repo\... as absolute, so this condition falls through and preserves the full C:/repo/src/lib.rs path instead of stripping the supplied root. The newly added Windows-path unit test therefore fails in the repository's Ubuntu cargo test --all-features job, and stored cross-platform analyses cannot normalize correctly; detect drive-letter and UNC paths using host-independent syntax before relativizing them.

Useful? React with 👍 / 👎.

.get("target")
.and_then(Value::as_str)
.map(str::to_string);
scope.features = string_array(value.get("features"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Read feature evidence from compiler artifacts

For a normal Cargo 1.95 JSON stream, build-finished carries only reason and success; enabled features and profile data are emitted on compiler-artifact, which this parser discards. Consequently scope.features remains an indistinguishable empty vector and producer.profile remains None even for analyses run with different features or profiles, preventing the hard-scope comparability checks this model is intended to support. Collect and correlate artifact evidence, while distinguishing unavailable features from a known-empty feature set.

Useful? React with 👍 / 👎.

Comment on lines +222 to +226
pub raw_file_name: Option<String>,
pub raw_line_start: Option<u32>,
pub raw_line_end: Option<u32>,
pub raw_column_start: Option<u32>,
pub raw_column_end: Option<u32>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Retain the complete Cargo span evidence

For diagnostics containing macro expansions, labels, or precise source evidence, this supposedly raw observation retains only file and line/column values and irreversibly drops Cargo's byte offsets, source text/highlights, label, expansion chain, and top-level suggestion fields. Because the original JSON is not retained elsewhere, downstream inventory and source-correspondence logic cannot distinguish macro call sites or reconstruct the evidence needed by the macro_generated_path case; extend the typed span model or retain the complete raw span payload.

Useful? React with 👍 / 👎.

@EffortlessSteven
EffortlessSteven merged commit f49f52b into main Aug 3, 2026
12 checks passed
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.21212% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/lintdiff-engine/src/diagnostics/mod.rs 96.21% 15 Missing ⚠️

📢 Thoughts on this report? Let us know!

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.

feat(engine): capture complete Cargo analysis observations

1 participant