{"slug": "building-aianalyzer-a-swift-static-analysis-tool-that-knows-when-to-trust-its-ai", "title": "Building AIAnalyzer: A Swift Static Analysis Tool That Knows When to Trust Its Own AI", "summary": "A developer with 18 years of iOS experience built AIAnalyzer, a Swift CLI tool that detects architectural code smells and offers AI-generated fix suggestions through a hybrid system that decides whether to trust a fast local model or escalate to a stronger cloud one. The tool uses four provider types behind a shared protocol, including a hybrid mode that scores confidence locally and falls back to Gemini only when needed. When run against its own source, AIAnalyzer found the exact architectural smell it was built to catch.", "body_md": "I've spent 18 years shipping iOS apps — enterprise VoIP, automotive, retail — and one problem kept resurfacing regardless of the domain: codebases quietly rot, and Swift never had great tooling to catch it early. Most static analysis tools are style linters, not architecture watchdogs. Nothing was really looking for God Objects, tangled responsibilities, or classes that had silently outgrown their purpose.\n\nSo I built AIAnalyzer — a Swift CLI, distributed via Homebrew, that does two things: detects real architectural smells using SwiftSyntax, and offers AI-generated fix suggestions through a system that decides, in real time, whether to trust a fast local model or escalate to a stronger cloud one.\n\nThe problem with \"just use AI for everything\"\n\nBolting an LLM onto a dev tool is easy. Bolting one on responsibly is a different problem. Two extremes are both bad:\n\nCloud-only: great quality, but every request leaves the machine. For teams under compliance constraints — banks, healthcare, defense — this is a non-starter.\n\nLocal-only: private and fast, but smaller on-device models sometimes give shallow or wrong suggestions, and you have no way of knowing when that's happening.\n\nI wanted a third option: a system that tries local first, evaluates its own output quality, and only spends the cloud API call when it actually needs to.\n\nThe architecture\n\nAIAnalyzer's AI layer has four provider types behind a shared AIProvider protocol:\n\n```\nswift\n\npublic protocol AIProvider {\n\n    /// Requests a refactoring suggestion based on the provided context.\n    /// - Parameter context: The issue and source code details.\n    /// - Returns: An `AISuggestion` containing the AI's response.\n    /// - Throws: An `AIProviderError` if the request fails.\n    func suggest(for context: AIRequestContext) async throws -> AISuggestion\n}\n```\n\nA factory dynamically builds the right provider based on configuration:\n\n``` js\nswift\n\nlet provider: AIProvider\n\nswitch configuration.serviceConfig.providerType {\ncase .gemini:\n    // Cloud provider\n    provider = GeminiProvider(apiKey: apiKey, model: configuration.serviceConfig.model)\n\ncase .ollama:\n    // Local provider backed by Ollama, running a full LLM on-device\n    provider = OllamaProvider(endpoint: configuration.serviceConfig.ollamaEndpoint, modelName: configuration.serviceConfig.ollamaModel)\n\ncase .local:\n    // Local Core ML provider — a lighter-weight on-device option with heuristic fallback\n    provider = LocalLLMProvider(modelPath: configuration.localModelConfig.localModelPath, modelName: configuration.localModelConfig.localModelName)\n\ncase .hybrid:\n    // Orchestrator combining a local preference with an optional cloud fallback\n    provider = HybridAIProvider(\n        localPreferred: localPreferred,\n        localFallback: localFallback,\n        cloud: cloud,\n        preferLocal: true\n    )\n}\n```\n\nGemini — cloud API, highest quality, requires network + key\n\nOllama — full LLM running fully on-device via Ollama, zero network calls\n\nLocal (Core ML) — a lighter-weight on-device option with its own heuristic fallback, useful when you want on-device inference without running a full Ollama-hosted model\n\nHybrid — attempts a local-preferred provider first, scores confidence based on output characteristics, escalates to Gemini only when confidence is low, and falls back to a local-fallback or static message if all else fails\n\nThe interesting engineering problem isn't \"call an LLM\" — it's the confidence heuristic that decides whether to trust the local pass, and having two different flavors of local (a full LLM via Ollama, and a lighter Core ML path) gives the Hybrid mode real flexibility depending on what's available on a given machine. That layered fallback is the actual differentiator, and it's the part I'd want to keep iterating on if this becomes a bigger project.\n\nTurning the tool on itself\n\nOnce detection worked, I ran AIAnalyzer against its own source — and found the exact smell it was built to catch. AnalyzerApp.swift had grown to 695 lines, quietly doing argument parsing, config loading, AI provider construction, and orchestration all at once. A God Object, in the God Object detector's own codebase.\n\nFixing it meant:\n\nMigrating hand-rolled CLI parsing to Apple's swift-argument-parser\n\nSplitting responsibilities into AnalyzerCommand, EnvironmentResolver, AISuggesterFactory, and AnalysisOrchestrator\n\nAlong the way, I also found and fixed a real false-positive bug: type classification was using substring matching on class names, so DetailViewControllerDelegate was getting misclassified as an oversized ViewController. Fixed by combining syntactic inheritance checks with strict suffix matching instead of loose contains() checks.\n\nWhat shipped\n\n59 passing tests, including boundary and integration cases\n\nFully automated release pipeline — tagging a version builds the binary, computes the checksum, and pushes the Homebrew formula update via GitHub Actions with zero manual steps\n\nSARIF output, so findings integrate directly into GitHub code scanning\n\nPublicly installable: brew install elangbamjohnson/tap/aianalyzer\n\nWhat I'd tell someone building something similar\n\nIf you're integrating AI into a dev tool, the confidence-based fallback pattern is worth the extra complexity. It's tempting to just pick cloud or local and move on, but the real value — for privacy-conscious teams especially — is in a system that's honest about when it doesn't know enough, rather than confidently returning a weak local answer or defaulting to the network every single time.\n\nIf you want to see more of what I've been building — including a\n\ndeeper case study on this project —\n\nmy portfolio's at ([https://elangbamjohnson.github.io/](https://elangbamjohnson.github.io/))\n\nRepo's here ([https://github.com/elangbamjohnson/AIAnalyzer](https://github.com/elangbamjohnson/AIAnalyzer))", "url": "https://wpnews.pro/news/building-aianalyzer-a-swift-static-analysis-tool-that-knows-when-to-trust-its-ai", "canonical_source": "https://dev.to/elangbamjohnson/building-aianalyzer-a-swift-static-analysis-tool-that-knows-when-to-trust-its-own-ai-46d7", "published_at": "2026-07-30 10:33:35+00:00", "updated_at": "2026-07-30 11:03:41.401244+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "large-language-models", "ai-agents"], "entities": ["AIAnalyzer", "SwiftSyntax", "Gemini", "Ollama", "Core ML", "Homebrew"], "alternates": {"html": "https://wpnews.pro/news/building-aianalyzer-a-swift-static-analysis-tool-that-knows-when-to-trust-its-ai", "markdown": "https://wpnews.pro/news/building-aianalyzer-a-swift-static-analysis-tool-that-knows-when-to-trust-its-ai.md", "text": "https://wpnews.pro/news/building-aianalyzer-a-swift-static-analysis-tool-that-knows-when-to-trust-its-ai.txt", "jsonld": "https://wpnews.pro/news/building-aianalyzer-a-swift-static-analysis-tool-that-knows-when-to-trust-its-ai.jsonld"}}