Back to projects
August 2026

smell-analyzer

AST Code Quality Detector & Rate-Limited LLM Refactoring Filter

Go · go/ast · go/parser · go/format · Worker Pool Concurrency · Token-Bucket Rate Limiter · Gemini REST API · CLI

Highlights

  • AST compiler pre-filter cuts LLM token consumption by >70%
  • Concurrent worker pool with token bucket rate limiting (RPM)
  • Interactive in-place file patching ([y/N/q]) with automated gofmt validation

System Overview

smell-analyzer is a fast Go CLI tool that parses source code into Abstract Syntax Trees (AST), calculates Cyclomatic Complexity and Max Nesting Depth metrics, and acts as an intelligent compiler pre-filter before sending only flagged functions to an LLM for interactive, in-place refactoring.

The Problem & Architectural Rationale

When attempting to clean up legacy codebases, developers often dump entire source files into LLMs. This consumes excessive tokens, skyrockets API costs, causes context bloat, and introduces hallucinations or broken dependencies.

Architecture & Components

1. AST Parsing & Ingestion

Ingests raw Go source files and parses them into in-memory *ast.File syntax trees using the Go standard library go/parser and go/token.

2. Metric Calculation

Walks AST function declarations (*ast.FuncDecl) and computes Cyclomatic Complexity (branching paths: if, for, range, case, binary logical operators) and maximum nested block depth.

3. Threshold Pre-Filtering

Isolates only functions that violate complexity rules (e.g. Cyclomatic Complexity > 4 or Nesting Depth > 3) and extracts isolated code snippets, discarding clean code from LLM prompts.

4. Worker Pool & Rate Limiter

Dispatches flagged functions into a concurrent job channel processed by a configurable pool of worker goroutines (--concurrency) regulated by a token-bucket rate limiter (--rate-limit RPM).

5. Interactive In-Place Rewriter

Renders colored terminal diffs and prompts the user ([y/N/q]) to accept or reject refactoring proposals, modifying source files on disk via AST node line replacement and formatting via gofmt.

Core Implementation

GO
// Calculate Cyclomatic Complexity by inspecting AST branch nodes
func CalculateCyclomaticComplexity(body *ast.BlockStmt) int {
    complexity := 1
    ast.Inspect(body, func(n ast.Node) bool {
        switch n := n.(type) {
        case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause:
            complexity++
        case *ast.BinaryExpr:
            if n.Op == token.LAND || n.Op == token.LOR {
                complexity++
            }
        }
        return true
    })
    return complexity
}

AST branching analysis computing function complexity

Outcomes & Results

  • Reduced LLM prompt token overhead by over 70% by extracting only offending AST nodes.
  • Eliminated API rate limit 429 errors through deterministic token bucket worker scheduling.
  • Guaranteed zero invalid syntax writes through pre-commit gofmt parsing.