← Back to Blog

Dogfooding: Loct Fixes Its Own Bug

AI agent runs loct on loct, discovers and fixes false positive

Use Case: Dogfooding Loctree - AI Agent Fixes Its Own False Positive

Real session transcript of Claude Code agent running loctree on its own codebase, discovering a false positive in cycle detection, and implementing the fix.

Project: loctree (Rust CLI) Loct version: 0.5.9-dev Date: 2025-12-01 Task: Dogfood loctree, review findings, fix discovered issues

The Challenge

User: "lekarzu lecz siΔ™ sam - wykonaj `loct` na naszym repo z root'a i przejrzyj findings"

Translation: "Physician, heal thyself - run loct on our repo from root and review findings"

Agent Session

Step 1: Running Loctree on Itself

$ ./loctree_rs/target/release/loctree loctree_rs/src -A --ext rs --circular

Circular imports detected (2 cycles):
  Cycle 1: tests/fixtures/js-cyclic-deep/a.js -> b.js -> c.js -> a.js
  Cycle 2: analyzer/report.rs -> snapshot.rs -> analyzer/root_scan.rs ->
           analyzer/runner.rs -> analyzer/output.rs -> analyzer/sarif.rs

Step 2: Investigating the Cycles

Cycle 1: Test fixture - intentional, used to test cycle detection itself. βœ…

Cycle 2: Suspicious. Let me trace the imports in report.rs:

$ grep -n "use crate::" loctree_rs/src/analyzer/report.rs

# No crate imports in production code!
# But wait...

$ grep -n "snapshot" loctree_rs/src/analyzer/report.rs
153:    use crate::snapshot::CommandBridge;

Line 153 is inside #[cfg(test)] mod tests { ... } - a test-only import!

Step 3: Root Cause Analysis

// loctree_rs/src/analyzer/report.rs

use serde::Serialize;  // Only external import

pub struct CommandGap { ... }
pub struct AiInsight { ... }
// ... pure type definitions ...

#[cfg(test)]
mod tests {
    use super::*;
    use crate::snapshot::CommandBridge;  // ← FALSE POSITIVE SOURCE!

    #[test]
    fn my_test() { ... }
}

The Rust analyzer was including imports from #[cfg(test)] modules in cycle detection, creating false positives for test-only dependencies.

Step 4: Implementing the Fix

Added strip_cfg_test_modules() function to filter out test modules before extracting imports:

/// Strip `#[cfg(test)]` annotated modules from content to avoid false positive cycles.
fn strip_cfg_test_modules(content: &str) -> String {
    let mut result = String::new();
    let mut chars = content.chars().peekable();
    let mut in_cfg_test_attr = false;

    while let Some(ch) = chars.next() {
        // Look for #[cfg(test)]
        if ch == '#' && chars.peek() == Some(&'[') {
            // ... collect attribute ...

            if attr_inner.starts_with("#[cfg(test)")
                || attr_inner.starts_with("#[cfg(all(") && attr_inner.contains("test")
            {
                in_cfg_test_attr = true;
                result.truncate(pos);  // Remove the attribute
            }
            continue;
        }

        // If after #[cfg(test)], look for `mod` and skip the entire block
        if in_cfg_test_attr && ch == 'm' {
            // ... detect "mod" keyword ...
            // ... skip balanced braces ...
        }

        result.push(ch);
    }
    result
}

Modified analyze_rust_file() to use filtered content:

pub(crate) fn analyze_rust_file(content: &str, relative: String, ...) -> FileAnalysis {
    // Strip #[cfg(test)] modules to avoid false positive cycles
    let production_content = strip_cfg_test_modules(content);

    for caps in regex_rust_use().captures_iter(&production_content) {
        // ... extract imports from production code only ...
    }
}

Step 5: Adding Tests

#[test]
fn strip_cfg_test_excludes_test_imports() {
    let content = r#"
use serde::Serialize;

pub struct MyType { pub name: String }

#[cfg(test)]
mod tests {
    use super::*;
    use crate::snapshot::CommandBridge;  // Should be excluded!
}
"#;
    let analysis = analyze_rust_file(content, "report.rs".to_string(), &[]);

    assert_eq!(analysis.imports.len(), 1);  // Only serde
    assert!(!analysis.imports.iter().any(|i| i.source.contains("snapshot")));
}

#[test]
fn strip_cfg_test_preserves_non_test_cfg() {
    // #[cfg(target_os = "macos")] modules should still be included
}

#[test]
fn strip_cfg_test_handles_nested_blocks() {
    // Properly handles nested {} braces inside test modules
}

Step 6: Validation

$ cargo clippy -- -D warnings
    Finished `dev` profile in 1.33s

$ cargo test "analyzer::rust"
running 10 tests
test analyzer::rust::tests::strip_cfg_test_excludes_test_imports ... ok
test analyzer::rust::tests::strip_cfg_test_handles_nested_blocks ... ok
test analyzer::rust::tests::strip_cfg_test_preserves_non_test_cfg ... ok
# ... 7 more tests ...
test result: ok. 10 passed; 0 failed

Step 7: Verifying the Fix

$ cargo build --release
$ ./loctree_rs/target/release/loctree loctree_rs/src -A --ext rs --circular

Circular imports detected (1 cycles):
  Cycle 1: analyzer/output.rs -> snapshot.rs -> analyzer/root_scan.rs -> analyzer/runner.rs

The false positive from report.rs is gone!

Real Finding: Architectural Coupling

The remaining cycle is a genuine finding:

output.rs β†’ snapshot::GitContext
snapshot.rs β†’ analyzer::output::RootArtifacts, process_root_context, write_report

These are function-local imports in production code (run_init() and write_auto_artifacts()), not test-only. This works in Rust due to lazy evaluation but represents tight coupling.

Verdict: Real architectural issue, could be refactored by moving GitContext to shared types module.

Summary

MetricValue
False positives eliminated1
Real issues discovered1 (architectural coupling)
Lines of code added~100
Tests added3
Time to implement~15 minutes

Key Insight

Loctree's Rust analyzer was treating #[cfg(test)] imports the same as production imports. This is technically correct (they ARE imports), but misleading for cycle detection since test dependencies don't affect production runtime.

Meta Note

This session demonstrates the power of dogfooding: running your own tool on your own codebase reveals edge cases that external testing might miss.

The AI agent was able to:

  1. Diagnose the issue using loctree's own cycle detection
  2. Trace the false positive to its source
  3. Implement a targeted fix
  4. Test the fix with comprehensive unit tests
  5. Validate the improvement by re-running the tool

This is the ultimate test: a tool that can find and fix its own bugs.


πš…πš’πš‹πšŽπšŒπš›πšŠπšπšπšŽπš. with AI Agents by VetCoders (c)2024-2026 LibraxisAI