Stacks
The AI coding setup for native iOS apps.
For Swift we pair a few tools that help the agent write the app with checks that refuse what it gets wrong: force unwraps, retain cycles and dead code.
Start here
This sits on top of the agents stack
In short
- VMGM Software builds native iOS apps with an AI coding agent, set up with MCP servers, a Swift skill and strict checks.
- The Mobbin MCP lets the agent look at real iOS app screens before it designs one.
- Emil Kowalski’s write-swift skill teaches the agent modern Swift: Swift 6 concurrency, value types and Swift Testing.
- The Xcode MCP lets the agent build, run tests and read SwiftUI previews in Xcode, and Argent lets it tap through the app in the simulator and profile it.
- SwiftLint, swift-format and Periphery run in pre-commit and CI, so a force unwrap, a formatting slip or dead code fails the build.
What is in the iOS stack?
- DesignMCP
Mobbin MCP
by Mobbin
Searches screens and flows from real, shipped iOS apps, so the agent does not design every screen the same default way.
How we use it: Before the agent builds or redesigns a screen, it searches how other iOS apps do it and writes down what to borrow and what to skip.
bashclaude mcp add mobbin --scope user --transport http https://api.mobbin.com/mcp - Modern SwiftSkill
write-swift
by Emil Kowalski
How to write Swift the way the language wants now: value types, Swift 6 data-race safety and concurrency, some versus any, Swift Testing, macros, and features newer than the agent’s training data.
How we use it: The agent reads it whenever it writes or reviews Swift, and when it hits a concurrency error, a hang or a retain cycle.
bashnpx skills add emilkowalski/skills --skill write-swift - Motion (optional)Skill
apple-design
by Emil Kowalski
Apple’s approach to fluid motion, from its WWDC design talks: springs, gestures, sheets and motion you can grab and reverse mid-flight. It is written for the web, so its examples are CSS, not SwiftUI.
How we use it: Optional. Add it when the app needs custom motion: the agent takes the rules on springs and gestures and applies them in SwiftUI.
bashnpx skills add emilkowalski/skills --skill apple-design - Build and previewMCP
Xcode MCP
by Apple
Apple’s own MCP server, in Xcode 26.3 and later. The agent can build the project, run tests, read build errors, search Apple’s docs and look at SwiftUI previews.
How we use it: The agent builds and runs the tests through Xcode itself, and checks a SwiftUI preview before it says a view is done.
bashclaude mcp add --transport stdio xcode -- xcrun mcpbridge - Test and profileMCP
Argent
by Software Mansion
Lets the agent tap through the app in the iOS simulator, take screenshots and profile it with Xcode Instruments.
How we use it: The agent taps through every change it makes, and profiles a slow screen before and after the fix.
bashnpx @swmansion/argent@latest init - LintCheck
SwiftLint
by Realm
Over 200 rules for Swift. Run strict, so a warning fails the build, with the opt-in rules that catch real bugs: force unwraps, weak delegates, unowned captures.
How we use it: Strict in pre-commit and CI, with a rule that bans swiftlint:disable comments, so the agent cannot switch a rule off to get past it.
bashbrew install swiftlint - FormatCheck
swift-format
by Apple
Apple’s formatter for Swift. It ships with the Swift 6 toolchain, so there is nothing to install.
How we use it: It owns formatting, so SwiftLint does not have to. CI runs it in strict lint mode and fails on any file it would change.
bashswift format lint --strict --recursive . - Dead codeCheck
Periphery
by Periphery
Finds unused code: declarations, parameters, enum cases and imports nothing uses.
How we use it: Agents leave old code behind when they replace it. Periphery runs in CI and fails when something is no longer used.
bashbrew install periphery
How do they work together?
- Design. The agent searches Mobbin for how real iOS apps do the screen, then builds it with our own colours and fonts.
- Build. It writes the Swift with the write-swift skill, builds through the Xcode MCP and checks the SwiftUI preview.
- Test. Argent taps through the change in the simulator and sends back screenshots, and profiles the screen when it feels slow.
- Check. SwiftLint, swift-format and Periphery run before the commit and again in CI. If one fails, the agent fixes it and tries again.
Set up guardrails for AI coding agents in this Swift repository: lint rules that catch what agents get wrong, a dead-code check, the compiler at its strictest, all enforced so that nothing can bypass them, and a ratchet if the codebase already has violations. Do the work yourself. Ask me before changing CI, the Swift language version, deleting files, or making a call only I can make. Commit nothing until I have seen your report. Principles - Use existing, maintained tools: SwiftLint, Apple's swift-format, Periphery and the compiler's own checks. Do not write Swift-based custom SwiftLint rules; a regex custom rule is fine for a ban. - A rule is worth enforcing only if it catches something real in this codebase. A style preference that fights the code's existing style is not. - Every check fails the build. No warnings: a warning nobody has to act on is noise. Nothing can be switched off from inside the code. - Existing violations are handled by a ratchet, never by leaving a useful rule off or at warning. - One lint step in CI, one config file per tool, each rule from one source. 1. Survey - Identify the project: Xcode project or workspace, Swift Package Manager, or both; targets and platforms; Swift tools version and language mode; how dependencies arrive; the CI runner (a macOS runner is needed to build). - Check what already exists: SwiftLint (version, config, how it runs: build phase, plugin, CI), swift-format or SwiftFormat, Periphery, pre-commit hooks, CI steps. - Check the compiler settings on every target: warnings as errors, the Swift language mode, strict concurrency checking. - Time a clean build and the current lint run. - List every way a check can be bypassed today: `// swiftlint:disable` comments, rules at warning with strict off, lint that runs only in Xcode and not in CI, a build phase that lints but does not fail, targets the lint config excludes, CI steps allowed to fail. 2. Measure without changing the repo Use a scratch config file (swiftlint lint --config scratch.yml --reporter json). Do not edit the real configs yet. Confirm the installed version's flags with swiftlint lint --help before relying on one. Candidates: - SwiftLint's default rules. - Opt-in rules that catch real bugs rather than style, for example: force_unwrapping, implicitly_unwrapped_optional, unowned_variable_capture, weak_delegate, overridden_super_call, prohibited_super_call, fatal_error_message, empty_count, first_where, contains_over_filter_count. Read each rule's description on the SwiftLint rule directory before proposing it; skip pure formatting rules, which swift-format owns. - Analyzer rules unused_import and unused_declaration only if Periphery is not chosen: they need a compiler log (swiftlint analyze --compiler-log-path) and overlap with it. - A ban on disable comments: a custom_rules entry whose regex matches swiftlint:disable in comments, at error. Check whether a disable comment can switch off this rule itself; if it can, also add the same pattern to a grep that runs in CI. - swift-format in lint mode (swift format lint --strict) with the repo's formatting settings, so formatting is one tool, not SwiftLint rules too. - Periphery for unused code: periphery scan. Check periphery scan --help for an option that fails the run on results and for a baseline; note which exist. - Compiler: warnings as errors (SWIFT_TREAT_WARNINGS_AS_ERRORS in Xcode, -warnings-as-errors for a package), and strict concurrency checking or the Swift 6 language mode. Measure how many warnings and errors each would produce; do not change them yet. For each rule or setting report: violations, files, how many autocorrect, one example. Time the runs. 3. Decide Put each rule in one of two groups: - Enforce: it catches real problems here, such as crashes, retain cycles, data races, unsafe unwraps, dead code. - Off: style that conflicts with this codebase's conventions, false positives, or a duplicate of another tool. Each one gets a one-line reason in the config. A rule with zero violations stays enforced: it costs nothing and catches the first regression. The Swift 6 language mode and strict concurrency can be a large migration. Treat them separately: say what turning them on would take, and let me decide. Then, from the total violations of the rules you enforce: - Few (roughly under 50, fixable in a day): fix them now. No ratchet. - More: ratchet. Enforce every chosen rule at error and baseline the existing violations. Show me the table and your decision before changing anything. 4. Install, after I agree - One .swiftlint.yml at the root, covering every target that has code. List every disabled rule with its reason. Set strict: true so warnings fail. - If ratcheting SwiftLint: write the baseline once (write_baseline in the config, or the matching flag), commit it, and point baseline at it. From then on new violations fail. SwiftLint does not fail on a baseline entry that no longer occurs, so add a CI check that regenerates the baseline and fails if it differs from the committed one, then commit the smaller file whenever it shrinks. Never regenerate it to absorb new violations. - If ratcheting Periphery or compiler warnings and the tool has no baseline: commit the current count and fail CI if it rises; lower the number as it falls. - swift-format: a .swift-format file with the repo's settings; CI runs swift format lint --strict over all sources. - Turn on warnings as errors for every target now if the count is zero or fixed in this change; otherwise ratchet the count. - Pre-commit: a committed hook (a script in the repo, enabled by a documented one-line setup command) that runs swift-format lint and SwiftLint on the staged Swift files with the same configs as CI, and shows its output. CI runs everything over the whole codebase on a macOS runner. No CI step may be allowed to fail. - Before running autocorrect across the codebase, show me how many files it would change. 5. Verify - For each enforced rule, plant a violation in a scratch file, confirm the run fails with a message that says what to do instead, then remove it. Include a planted swiftlint:disable comment. - If ratcheting, confirm that a new violation fails, that fixing a baselined one fails CI until the committed baseline shrinks, and that a pre-commit run on one file passes. - Report lint and build time before and after. 6. Write the decisions into a "## Guardrails" section of AGENTS.md The configs are the list of rules, and each disabled rule's reason sits beside it there; do not restate them. The section holds what no config can: baselines or none, disable comments or none, the compiler settings chosen and the Swift 6 migration plan if one was deferred, triggers that would reopen a decision, the burn-down order with counts if ratcheting, rules considered and declined with the reason and what would change it, and a "Last reviewed: <date> at <commit>" line. Keep it under about 40 lines. Report The survey, the rule table with decisions, ratchet or not with counts, the compiler settings before and after, lint and build time before and after, the files changed, and anything you could not do.
Frequently asked questions
- Does Argent work on native Swift apps?
Yes. Argent drives the iOS simulator whatever the app is written in, and its profiling uses Xcode Instruments. Only its React profiler is for React Native apps.
- Why lint so strictly?
The Swift compiler lets through force unwraps, retain cycles and dead code, and an agent writes all three. A rule at warning gets ignored, so every check fails the build. The prompt above sets this up in an existing app, with a baseline for the problems it already has.
- Do I need the Xcode MCP and Argent?
They do different jobs. The Xcode MCP builds, runs tests and reads previews inside Xcode. Argent uses the app the way a person does: it taps, scrolls and types in the simulator.
- What about Expo or React Native?
Use the Expo stack. This one is for apps written in Swift.
None of these makers pay us to list them. Setup commands checked on .
We set this up on your team’s codebase, with the rules that make the agent use it well.
Talk to us about your team