-
Notifications
You must be signed in to change notification settings - Fork 2
feat: v0.2 enhancements — LSP, multi-base diffs, directory overrides, analytics #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
f89496b
c9a8a2a
c188b79
c41517f
8c936db
5c219f0
0c9d09f
d3e4d3a
8aaa6f0
bdac0d3
583d4ec
ccf0f41
8325454
fdd81d1
a53d5fa
31170b8
804b4ce
4b39685
d8a69f3
b8e2532
7ebce05
4cb1876
fb13941
3dd5155
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,164 @@ | ||||||||
| # Plan: Fix xtask Test Failures | ||||||||
|
|
||||||||
| **Created:** 2026-04-04 | ||||||||
| **Updated:** 2026-04-04 | ||||||||
| **Priority:** P0 (blocking CI / PR #5) | ||||||||
| **Status:** ready-for-execution | ||||||||
|
|
||||||||
| ## Problem | ||||||||
|
|
||||||||
| 3 tests in `xtask` fail, blocking CI and PR #5 merge:``` | ||||||||
| tests::run_with_args_dispatches_ci_with_fake_cargo - FAIL | ||||||||
| tests::run_with_args_dispatches_conform_quick - FAIL | ||||||||
| tests::run_with_args_dispatches_mutants_with_fake_cargo - FAIL | ||||||||
| ``` | ||||||||
|
|
||||||||
| ## Root Cause Analysis | ||||||||
|
|
||||||||
| ### Issue 1: Missing Binary for Conformance Tests | ||||||||
|
|
||||||||
| `run_with_args_dispatches_conform_quick` calls `conform::run_conformance(true)` which runs conformance tests. Tests 3 (Survivability) and 9 (Tool error code) need the `diffguard` binary, but: | ||||||||
|
|
||||||||
| - `CARGO_BIN_EXE_diffguard` is set by `cargo test` to point to the xtask binary, not diffguard | ||||||||
| - `cargo_bin_path()` returns wrong path | ||||||||
| - `run_diffguard()` fails to find the binary | ||||||||
|
|
||||||||
| ### Issue 2: Mutex Poisoning Cascade | ||||||||
|
|
||||||||
| When `run_with_args_dispatches_conform_quick` panics, subsequent tests that use `ENV_LOCK.lock().unwrap()` fail with `PoisonError`. | ||||||||
|
|
||||||||
| The panic happens in `main.rs::tests` but doesn't use `ENV_LOCK`. However, the panic still affects test state. | ||||||||
|
|
||||||||
| ### Issue 3: `cargo_bin_path()` Resolution | ||||||||
|
|
||||||||
| In `conform_real.rs`, `cargo_bin_path()` prefers `CARGO_BIN_EXE_diffguard` env var, but when running `cargo test -p xtask`, this points to the xtask test binary, not diffguard. | ||||||||
|
|
||||||||
| ## Solution | ||||||||
|
|
||||||||
| ### Task 1: Fix Binary Path Resolution | ||||||||
|
|
||||||||
| **File:** `xtask/src/conform_real.rs` | ||||||||
| **Lines:** 1296-1330 | ||||||||
|
|
||||||||
| Modify `cargo_bin_path()` to: | ||||||||
| 1. Check for `CARGO_BIN_EXE_diffguard` env var first | ||||||||
| 2. Fall back to workspace-relative `target/debug/diffguard` path | ||||||||
| 3. Build binary if missing | ||||||||
|
|
||||||||
| ```rust | ||||||||
| fn cargo_bin_path() -> String { | ||||||||
| // Prefer env var if set and valid (points to actual diffguard binary) | ||||||||
| if let Ok(bin) = std::env::var("CARGO_BIN_EXE_diffguard") { | ||||||||
| // Verify it's the diffguard binary, not xtask | ||||||||
| if bin.contains("diffguard") && !bin.contains("xtask") { | ||||||||
| return bin; | ||||||||
| } | ||||||||
| } | ||||||||
| // Fall back to workspace-relative path | ||||||||
| workspace_root() | ||||||||
| .join("target") | ||||||||
| .join("debug") | ||||||||
| .join(if cfg!(windows) { "diffguard.exe" } else { "diffguard" }) | ||||||||
| .to_str() | ||||||||
| .unwrap() | ||||||||
| .to_string() | ||||||||
| } | ||||||||
| ``` | ||||||||
|
Comment on lines
+48
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Proposed binary path validation has critical flaws. The string-based check on line 53 is fragile and can fail in valid scenarios:
Consider a path-based approach instead: 🔧 Proposed fix using Path-based validation fn cargo_bin_path() -> String {
// Prefer env var if set and valid (points to actual diffguard binary)
if let Ok(bin) = std::env::var("CARGO_BIN_EXE_diffguard") {
- // Verify it's the diffguard binary, not xtask
- if bin.contains("diffguard") && !bin.contains("xtask") {
- return bin;
+ // Verify it's actually the diffguard binary by checking the filename
+ let path = std::path::Path::new(&bin);
+ if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
+ let expected = if cfg!(windows) { "diffguard.exe" } else { "diffguard" };
+ if filename == expected && path.exists() {
+ return bin;
+ }
}
}
// Fall back to workspace-relative path
workspace_root()
.join("target")
.join("debug")
.join(if cfg!(windows) { "diffguard.exe" } else { "diffguard" })
- .to_str()
- .unwrap()
- .to_string()
+ .to_string_lossy()
+ .to_string()
}🤖 Prompt for AI Agents |
||||||||
|
|
||||||||
| ### Task 2: Add Build Step in `run_conformance` | ||||||||
|
|
||||||||
| **File:** `xtask/src/conform_real.rs` | ||||||||
|
|
||||||||
| At the start of `run_conformance()`, call `ensure_diffguard_built()`: | ||||||||
|
|
||||||||
| ```rust | ||||||||
| pub fn run_conformance(quick: bool) -> Result<()> { | ||||||||
| ensure_diffguard_built()?; // Add this line | ||||||||
| // ... rest of function | ||||||||
| } | ||||||||
| ``` | ||||||||
|
|
||||||||
| ### Task 3: Implement `ensure_diffguard_built` | ||||||||
|
|
||||||||
| ```rust | ||||||||
| fn ensure_diffguard_built() -> Result<()> { | ||||||||
| let binary = cargo_bin_path(); | ||||||||
| if !std::path::Path::new(&binary).exists() { | ||||||||
| eprintln!("Building diffguard binary for conformance tests..."); | ||||||||
| let status = Command::new("cargo") | ||||||||
| .args(["build", "--bin", "diffguard"]) | ||||||||
| .current_dir(workspace_root()) | ||||||||
| .status() | ||||||||
| .context("cargo build --bin diffguard")?; | ||||||||
| if !status.success() { | ||||||||
| bail!("Failed to build diffguard binary"); | ||||||||
| } | ||||||||
| } | ||||||||
| Ok(()) | ||||||||
| } | ||||||||
| ``` | ||||||||
|
|
||||||||
| ### Task 4: Handle Poisoned Mutex | ||||||||
|
|
||||||||
| **Files:** `xtask/src/main.rs`, `xtask/src/conform_real.rs` | ||||||||
|
|
||||||||
| Replace `.unwrap()` with recovery pattern: | ||||||||
|
|
||||||||
| ```rust | ||||||||
| // Before: | ||||||||
| let _guard = ENV_LOCK.lock().unwrap(); | ||||||||
|
|
||||||||
| // After: | ||||||||
| let _guard = ENV_LOCK.lock().unwrap_or_else(|e| { | ||||||||
| // Clear poison and continue | ||||||||
| e.into_inner() | ||||||||
| }); | ||||||||
| ``` | ||||||||
|
|
||||||||
| This allows tests to continue even if a previous test panicked while holding the lock. | ||||||||
|
|
||||||||
| ### Task 5: Fix Test Isolation | ||||||||
|
|
||||||||
| **File:** `xtask/src/main.rs` | ||||||||
|
|
||||||||
| The test `run_with_args_dispatches_conform_quick` should not panic when conformance fails. It should return a result or handle the error gracefully: | ||||||||
|
|
||||||||
| ```rust | ||||||||
| #[test] | ||||||||
| fn run_with_args_dispatches_conform_quick() { | ||||||||
| // Build first | ||||||||
| let binary = workspace_root() | ||||||||
| .join("target") | ||||||||
| .join("debug") | ||||||||
| .join(if cfg!(windows) { "diffguard.exe" } else { "diffguard" }); | ||||||||
| if !binary.exists() { | ||||||||
| let status = Command::new("cargo") | ||||||||
| .args(["build", "--bin", "diffguard"]) | ||||||||
| .status() | ||||||||
| .expect("cargo build"); | ||||||||
| assert!(status.success(), "Failed to build diffguard"); | ||||||||
| } | ||||||||
|
|
||||||||
| run_with_args(["xtask", "conform", "--quick"]).expect("run conform"); | ||||||||
| } | ||||||||
|
Comment on lines
+126
to
+143
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider using a test setup function instead of inline build logic. The test still uses ♻️ Optional refactor for better test isolation// In a test utils module or at top of test module
fn ensure_test_binary_built() -> std::path::PathBuf {
let binary = workspace_root()
.join("target")
.join("debug")
.join(if cfg!(windows) { "diffguard.exe" } else { "diffguard" });
if !binary.exists() {
let status = Command::new("cargo")
.args(["build", "--bin", "diffguard"])
.status()
.expect("cargo build");
assert!(status.success(), "Failed to build diffguard");
}
binary
}
#[test]
fn run_with_args_dispatches_conform_quick() {
let _binary = ensure_test_binary_built();
run_with_args(["xtask", "conform", "--quick"]).expect("run conform");
}This makes the setup reusable across multiple tests that need the binary. 🤖 Prompt for AI Agents |
||||||||
| ``` | ||||||||
|
|
||||||||
| ## Verification Steps | ||||||||
|
|
||||||||
| 1. Run `cargo build --bin diffguard` | ||||||||
| 2. Run `cargo test -p xtask -- --test-threads=1` | ||||||||
| 3. Run `cargo run -p xtask -- conform` | ||||||||
| 4. Run `cargo run -p xtask -- ci` | ||||||||
|
|
||||||||
| ## Success Criteria | ||||||||
|
|
||||||||
| - [ ] All 13 xtask tests pass | ||||||||
| - [ ] All 15 conformance tests pass | ||||||||
| - [ ] `cargo run -p xtask -- ci` passes | ||||||||
| - [ ] No changes to actual diffguard logic (only test harness) | ||||||||
|
|
||||||||
| ## Notes | ||||||||
|
|
||||||||
| - This is blocking PR #5 | ||||||||
| - The fix is isolated to xtask test infrastructure | ||||||||
| - No production code changes needed | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add trailing newline. The file should end with a single newline character per markdown conventions. 📝 Proposed fix - No production code changes needed
+📝 Committable suggestion
Suggested change
🧰 Tools🪛 markdownlint-cli2 (0.22.0)[warning] 164-164: Files should end with a single newline character (MD047, single-trailing-newline) 🤖 Prompt for AI Agents |
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -118,4 +118,4 @@ Rules defined in `diffguard.toml`. See `diffguard.toml.example`. Key fields: | |
|
|
||
| ## MSRV | ||
|
|
||
| Rust 1.75 (Minimum Supported Rust Version) | ||
| Rust 1.92 (Minimum Supported Rust Version) | ||
|
Comment on lines
119
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Find the new crate directories and their Cargo.toml files
find crates -type d -name "diffguard-analytics" -o -name "diffguard-lsp" 2>/dev/nullRepository: EffortlessMetrics/diffguard Length of output: 118 🏁 Script executed: # List all Cargo.toml files to understand crate structure
git ls-files | grep -E "Cargo.toml$" | head -20Repository: EffortlessMetrics/diffguard Length of output: 383 🏁 Script executed: # Read CLAUDE.md to check the architecture diagram
cat -n CLAUDE.md | grep -A 30 "architecture\|Architecture\|diagram"Repository: EffortlessMetrics/diffguard Length of output: 2337 🏁 Script executed: # Check dependencies in diffguard-analytics
cat crates/diffguard-analytics/Cargo.tomlRepository: EffortlessMetrics/diffguard Length of output: 914 🏁 Script executed: # Check dependencies in diffguard-lsp
cat crates/diffguard-lsp/Cargo.tomlRepository: EffortlessMetrics/diffguard Length of output: 1102 🏁 Script executed: # Search for MSRV guidelines in project documentation
rg "MSRV|Minimum.*Rust|rust-version|1\.75|1\.92" --type md --type tomlRepository: EffortlessMetrics/diffguard Length of output: 921 🏁 Script executed: # Check if diffguard-lsp has main.rs or is a library/application
find crates/diffguard-lsp/src -type f -name "*.rs" | head -5Repository: EffortlessMetrics/diffguard Length of output: 206 🏁 Script executed: # Check if there's mention of diffguard-lsp in workspace members or architecture docs
rg "diffguard-lsp|diffguard-analytics" CLAUDE.md AGENTS.md README.md 2>/dev/null || echo "Not found in docs"Repository: EffortlessMetrics/diffguard Length of output: 520 Update architecture diagram to include the two new crates. The dependency hierarchy diagram in lines 29–51 currently shows five crates but omits
This ensures readers can verify both new crates respect the documented downward-only dependency hierarchy. 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -55,7 +55,7 @@ Configuration controls the linter behavior and rules. | |||||
| ```toml | ||||||
| [defaults] | ||||||
| base = "origin/main" | ||||||
| scope = "added" # added|changed | ||||||
| scope = "added" # added|changed|modified|deleted (changed kept for compatibility) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stale "Key Features" and project overview descriptions still say "added or changed" only. Line 5 says "applying rules only to added or changed lines" and Line 8 says "Scans only added/changed lines" — both omit Additionally, "(changed kept for compatibility)" is opaque without stating that 📝 Suggested corrections-**Diff-Aware:** Scans only added/changed lines (no repo-wide noise).
+**Diff-Aware:** Scans added, modified, and deleted lines in a diff (no repo-wide noise).-diffguard is a diff-scoped governance linter written in Rust. It is designed for modern PR automation, applying rules only to added or changed lines in a Git diff.
+diffguard is a diff-scoped governance linter written in Rust. It is designed for modern PR automation, applying rules to added, modified, or deleted lines in a Git diff.-scope = "added" # added|changed|modified|deleted (changed kept for compatibility)
+scope = "added" # added|modified|deleted|changed (changed is a deprecated alias for modified, kept for compatibility)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| fail_on = "error" # error|warn|never | ||||||
|
|
||||||
| [[rule]] | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix markdown formatting for the code fence.
The code fence should be surrounded by blank lines and have a language specified for proper rendering.
📝 Proposed fix for markdown formatting
🧰 Tools
🪛 LanguageTool
[typographical] ~11-~11: To join two clauses or introduce examples, consider using an em dash.
Context: ..._with_args_dispatches_ci_with_fake_cargo - FAIL tests::run_with_args_dispatches_con...
(DASH_RULE)
[typographical] ~12-~12: To join two clauses or introduce examples, consider using an em dash.
Context: ...::run_with_args_dispatches_conform_quick - FAIL tests::run_with_args_dispatches_mut...
(DASH_RULE)
[typographical] ~13-~13: To join two clauses or introduce examples, consider using an em dash.
Context: ..._args_dispatches_mutants_with_fake_cargo - FAIL ``` ## Root Cause Analysis ### Is...
(DASH_RULE)
🪛 markdownlint-cli2 (0.22.0)
[warning] 14-14: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
[warning] 14-14: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents