How Sourcery Makes AI 10x More Useful in Large iOS Codebases

Stop letting AI write unusable code – give it the context it craves.

You’ve seen it. You’ve suffered through it. You fed your AI coding assistant a seemingly simple task, and it spat out a steaming pile of Swift garbage.

Why? Because context is king, and most AI tools are flying blind in large iOS codebases.

There are many reasons why AI assisted coding for iOS sucks, and today I am going to focus on one of them.

AI output is as good as its input and it can’t produce desired code if not provided with enough context. This matters especially on large code bases where you can’t just simply copy all the source files to AI context.

Different tools use different techniques. For example aider uses “repo map” which is built using tree sitter. Unfortunately, aider does not have swift support for repo map.

Another tool is the one everybody knows - cursor. It uses RAG for building context, trying to include the most relevant bits of code to user’s request.

I had situations where AI would start writing code for a component that already existed – it just didn’t know.

File trees alone don’t cut it. Projects with complex business logic? Forget it. AI won’t magically know how to use your internal components.

Smaller projects? You could write descriptions, but they’re outdated instantly.

Info
Sourcery is a metaprogramming tool that gives you full access to your Swift project’s type information at compile time.

Back in the days before swift macro and automatically synthesized equatable and hashable conformances in swift, we had to write a lot of boilerplate.

That is when I learned about beautiful tool called Sourcery. This is a tool for meta programming created by Krzysztof Zabłocki.

It allows you to write code that generates other code. Think of it like a template engine that has full access to your Swift project’s type information at compile time.

Simply put - this is a template engine that, at runtime, knows about all types in your source files. You could, for example, generate an enum containing cases for every type in your codebase.

Bad AI Example

// You ask: "Create a view for displaying a user profile."

// AI generates:
struct UserProfileView: View {
  // ...completely ignores your existing User and ProfileView components...
}

Good AI Example with Sourcery Context

//Sourcery generates index file which has all the types

// You ask: "Create a view for displaying a user profile."

// AI generates:
struct UserProfileView: View {
    var user: User //AI knows we already have User type

    var body: some View {
        // AI uses existing components because it *knows* about them!
        ExistingProfileView(user: user)
    }
}

Sourcery is a command-line tool. You have several options:

  1. Homebrew (Recommended): brew install sourcery
  2. CocoaPods: Add pod 'Sourcery', :subspecs => ['CLI-Only'] to your Podfile
  3. Binary Download: Get prebuilt binary from the releases page
Quick Setup
  1. Install via Homebrew: brew install sourcery
  2. Create your template
  3. Run: sourcery --sources Sources --templates Template.stencil --output .project-context

Create a template in AllTypes.stencil with the following content:

AllTypes.stencil

// Found {{ types.all.count }} Types

enum AllTypes {
{% for type in types.all %}
case {{ type.name | lowercase }}
{% endfor %}
}

Then run Sourcery with your source path and template path:

Command Line Usage

sourcery --sources Sources --templates AllTypes.stencil --output .project-context

That can also be configured in .sourcery.yml:

sourcery.yml

sources:
- ./Sources
templates:
- ./AllTypes.stencil
output:
- .project-context

Then you just run sourcery and that is it.

It will generate .project-context file in the root directory.

Generated Output

// Found 2 Types

enum AllTypes {
case contentView
case appDelegate
}

For complex templates, or if you’re new to Stencil, consider the .swifttemplate format, which uses Swift itself.

Here’s a quick look at a Type instance’s properties:

  • imports
  • accessLevel
  • name
  • variables
  • methods

Full list can be found Type Class Reference

Type Properties Example

{% for variable in type.variables %}
    {% if variable.defaultValue %}
        print("Property: \(variable.name) has a default value")
    {% else %}
        print("Property: \(variable.name) doesn't have a default value")
    {% endif %}
{% endfor %}

To make documentation comments available to sourcery you need to run it with special flag:

Documentation Flag

sourcery --parseDocumentation

Also, don’t forget to run Sourcery in watch mode – it’ll automatically regenerate on source file changes:

Watch Mode

sourcery --watch

To make it more tailored to our needs we can use annotations. for example we can mark specific type to be ignored in our template. First we need to add annotation to that type:

Annotation Example

// sourcery: skipCodeIndex
class SkippedClass {
// This class will be skipped by Sourcery
}

Then we need to filter this annotation in template:

Template Filter

enum AllTypes {
{% for type in types.all %}
{% if not type.annotations.skipCodeIndex %}
case {{ type.name | lowercase }}
{% endif %}
{% endfor %}
}

We can even add some extra instructions right in code:

Extra Instructions

class SpecialClass {
var foo: Foo
// sourcery: extraInfo = "your text"
var bar: Bar
}

And then we can access it in template:

Accessing Annotations

{% if variable.annotations.extraInfo %}// {{ variable.annotations.extraInfo }}{% endif %}

Here you can find materials for the workshop for Sourcery Pages · krzysztofzablocki/SourceryWorkshops Wiki · GitHub

Optionally, you can add content of your .cursorrules directly into template and then output generated file to .cursorrules so it has your rules and up to date index.

Cursor recently released a new feature which is going to significantly improve quality of the context that is provided to AI. And context is everything because quality of the output directly depends on the quality of the input.

Cursor enables AI configuration via global settings and project rules (.cursor/rules), supporting semantic descriptions and automatic attachment. Created through Cmd+Shift+P, rules feature glob matching and folder-specific settings for Cursor Chat, Ctrl/⌘ K, and code style preferences.

AI agent doesn’t need to know about codebase structure all the time. Most of the time it actually works on changes for some specific feature, and only during some planning/architecture phase it probably needs to know about overall project structure. With this new feature agent can decide when to read our index file. Here is how it would looks like

Cursor Configuration

---
description: Code index that lists all the types defined in the project.
globs: **/*.{swift,md}
---

enum AllTypes {
{% for type in types.all %}
case {{ type.name | lowercase }}
{% endfor %}
}

save this file in Templates/code_index.stencil and then define a .sourcery.yml with the following content:

Sourcery Configuration

sources:
- MyApp/Sources
- MyFramework/Sources
templates:
- Templates/code_index.stencil
output:
.cursor/rules/.full_code_index.mdc

Here is my index file swift template with some extra filtering to make it smaller:

Sourcery lets you define feature-level, domain-specific, and complex indexes. This means not only listing types, but actually personalizing it and giving your AI exactly the context it needs, when it needs it.

Run sourcery --watch. It’s very handy for AI-assisted development.

Why This System Wins:

  • Never Waste Hours Manually Documenting Types Again: Sourcery Does It For You.
  • Architectural Guardrails: AI suggestions respect domain boundaries, improving overall design and reusability.
  • Always-in-Sync Context: Integrate with CI for continuous, up-to-date context.
  • Prove ROI: Track AI-assisted coding metrics to show concrete improvements.

P.S. Developers are building tools for themselves like never before. The barrier to entry has basically vanished. We can whip up solutions that turbocharge our coding in minutes. Sure, we’ve always hacked together our own stuff - but now anyone can do it. Super exciting time.