Avie: A SPM Dependency Graph Auditor (Tested on Vapor, TCA, and SwiftSyntax)

Hey everyone,

I wanted to share a CLI tool I’ve been working on lately called Avie. It’s an SPM dependency graph auditor, and I basically built it to help teams catch structural issues, stop dependency bloat, and keep their lockfiles clean.

Most tools out there rely on source scanning or regex heuristics. Avie takes a different approach—it builds a mathematical directed graph straight from swift package show-dependencies and dump-package. By using BFS and DFS reachability under the hood, it actually proves structural issues in the graph before they have a chance to mess up your build pipeline.

Right now, it flags four main things:

  • AVIE001 (Unreachable Pins): Catches packages just sitting in Package.resolved with zero edges connecting them to the root.

  • AVIE002 (Test Leakage): Spots when test-only frameworks (like Quick or Nimble) accidentally become reachable from a production target.

  • AVIE003 (Excessive Transitive Fanout): Flags direct dependencies that secretly drag in a massive transitive graph.

  • AVIE004 (Binary Targets): Highlights any unauditable pre-compiled XCFrameworks introduced into your tree.

I wanted to see how it handled the real world, so I ran it against a few major pure-SPM repos. Here’s what happened:

1. Lockfile Drift (TCA) First up was pointfreeco/swift-composable-architecture. Avie instantly caught a classic case of lockfile drift (AVIE001). It looks like some DocC plugins were tested and removed from the manifest, but their "ghost pins" are still lingering in Package.resolved, which quietly slows down resolution times.

2. Transitive Fanout (Vapor) With vapor/vapor, the tool flagged packages like async-http-client for pulling in 15-20+ transitive dependencies each (AVIE003). Don't get me wrong—for foundational frameworks like NIO and Vapor, massive modularization and high fanout is a totally intentional best practice. But if you're an app developer importing a library, having this visibility is a lifesaver for preventing unexpected app-size bloat and wild graph complexity.

3. Clean Baseline (SwiftSyntax) Just to make sure the engine wasn't hallucinating errors, I tested it on apple/swift-syntax. Since SwiftSyntax is strictly designed with zero external dependencies to support compiler bootstrapping, Avie correctly reported a perfectly clean graph with zero depth.

4. Auditing Modular iOS Apps (DuckDuckGo)

To test how Avie handles large-scale, consumer-facing applications, I ran it against the DuckDuckGo iOS app. Since the root is an .xcodeproj, I audited one of their local SPM packages (./LocalPackages/SyncUI-iOS).

Avie immediately caught two distinct structural issues:

  1. Multiple Ghost Pins (AVIE001): Packages like swift-syntax and swift-argument-parser were left in the local Package.resolved despite being completely unreachable from the root. This highlights how easily lockfile rot accumulates in modularized iOS apps when teams move fast.

  2. Binary Target Detection (AVIE004): It flagged apple-toolbox for containing a pre-compiled .binaryTarget. For product teams, surfacing unauditable XCFrameworks is a critical compliance and security gate.

What's Next Right now, v1 is distributed via Homebrew and only supports pure SPM packages (running right alongside your Package.swift). Support for Xcode-managed projects (.xcodeproj/.xcworkspace) is on the roadmap for v2. It also has a PR Diff Mode that compares two graph snapshots so you can catch architectural regressions in CI.

I originally built this for application teams trying to keep their consumer iOS/macOS apps clean, but I’d really love to hear what the community thinks. Any feedback on the architecture, the specific rules, or weird SPM edge cases I should account for would be awesome.

You can check it out here: Avie

Thanks for reading

6 Likes