← Back to Projects

DebtDrone: AST-Based Technical Debt Analysis

A high-performance CLI for measuring cognitive complexity using Abstract Syntax Trees.

Go / AST / Static Analysis / DevOps / CLI

Install

brew install endrilickollari/tap/debtdrone

The Problem

Modern software development moves fast, but technical debt moves faster. Most engineering teams rely on "linter" tools to gate code quality, but these tools have a fatal flaw: they measure syntax, not structure.

Traditional static analysis tools typically use Regex pattern matching or simple Line of Code (LOC) counts. This leads to:

  • False Positives: A complex-looking function might be simple logic, while a short, nested closure is a nightmare to debug.
  • Context Blindness: Standard tools cannot distinguish between a deeply nested loop (high cognitive load) and a flat switch statement (low cognitive load).
  • Developer Fatigue: When tools cry "wolf" on safe code, developers stop listening, and real architectural rot sets in.

I needed a tool that analyzed code the way a compiler does—understanding the relationships between nodes, not just the text on the screen.

The Solution

I built DebtDrone, a CLI tool that uses Abstract Syntax Trees (ASTs) to measure Cognitive Complexity rather than just Cyclomatic Complexity. It supports over 11 languages (including Go, Rust, TypeScript, and Python) with a single binary.

Architecture: Parsing vs. Reading

Instead of scanning text files, DebtDrone uses the Tree-sitter engine to generate a concrete syntax tree for every file. This allows us to traverse the code structure and apply penalties based on nesting depth.

For example, an if statement at the root level costs +1. An if statement nested inside a loop costs +2. This accurately reflects the mental effort required to understand the control flow.

Implementation Details

The core analysis engine utilizes a unified interface for traversing trees across different languages. Here is how I handle Go complexity analysis by inspecting AST nodes:

func (a *GoAnalyzer) Analyze(ctx context.Context, filename string, content []byte) (*models.FileComplexity, error) {
    // Parse the file into an AST
    tree, err := a.parser.Parse(ctx, content)
    if err != nil {
        return nil, err
    }
    defer tree.Close()

    root := tree.RootNode()
    
    // Traverse nodes to calculate Cognitive Complexity
    // Penalize nesting: deeper levels = higher cost
    complexity := 0
    cursor := sitters.NewTreeCursor(root)
    
    for {
        node := cursor.CurrentNode()
        if isComplexityIncrementer(node.Type()) {
            complexity += (1 + calculateNestingDepth(node))
        }
        
        if !cursor.GotoFirstChild() {
            for !cursor.GotoNextSibling() {
                if !cursor.GotoParent() {
                    return &models.FileComplexity{Score: complexity}, nil
                }
            }
        }
    }
}

Engineering Challenges: Cross-Platform Distribution

One of the biggest hurdles was distributing a CGO-heavy application (due to the C-based Tree-sitter bindings) as a static binary.

I solved this by implementing a Dockerized cross-compilation pipeline using goreleaser-cross. This allows us to compile native binaries for macOS (Darwin), Windows, and Linux (AMD64/ARM64) from a single standard CI runner, ensuring the tool works everywhere without dependencies.

The Result

DebtDrone v1.0.0 is now a production-ready utility that delivers:

  • Precision: Zero false positives on complex type signatures or generics.
  • Performance: Scans large multi-language repositories in under 200ms.
  • Privacy: Runs 100% locally with no code uploaded to the cloud.
  • Actionability: Strict quality gates (e.g., --fail-on critical) allow DevOps teams to block "complexity bombs" before they merge into main.

It effectively turns "code quality" from a subjective opinion into an objective, measurable metric that engineers trust.