🎤 Harmonize is coming to a conference near you! We'll be presenting it at NSSpain (Logroño, September 16–18, 2026) and Swift Connection (Paris, November 2–3, 2026). Come say hi!
Harmonize is a modern linter for Swift that allows you to assert, validate, and harmonize your code’s structure and architecture by writing lint rules as unit tests—using Quick, XCTest, or Swift Testing.
This allows your team to keep your codebase clean, maintainable, and consistent as it grows, without relying on manual code reviews.
Harmonize aims to solve the limitations of regex-based linters such as SwiftLint, which focus primarily on Swift style and simple conventions. Inspired by Konsist for Kotlin and ArchUnit for Java, Harmonize provides a richer, semantic way to enforce your project's architecture and structural guidelines.
AI-generated code can help teams move faster, but it can also introduce architectural flaws and subtle bugs that are hard to spot in manual code reviews. Harmonize gives your codebase deterministic guardrails by turning your team’s architectural and structural rules into unit tests. When AI-generated code violates those rules, the tests fail, giving your AI agent clear feedback to fix its own mistakes.
With Harmonize, you can write a lint rule similarly as you would write a unit test:
import Harmonize
import Quick
final class ViewModelsInheritBaseViewModelSpec: QuickSpec {
override func spec() {
describe("ViewModels") {
let viewModels = Harmonize.productionCode().classes()
.withNameEndingWith("ViewModel")
it("should inherit from BaseViewModel") {
viewModels.assertTrue(message: "All ViewModels must inherit from BaseViewModel") {
$0.inherits(from: "BaseViewModel")
}
}
}
}
}import Harmonize
import XCTest
final class ViewModelsInheritBaseViewModelSpec: XCTestCase {
func testViewModels() throws {
let viewModels = Harmonize.productionCode().classes()
.withNameEndingWith("ViewModel")
viewModels.assertTrue(message: "All ViewModels must inherit from BaseViewModel") {
$0.inherits(from: "BaseViewModel")
}
}
}import Harmonize
import Testing
@Test
func viewModelsInheritBaseViewModel() {
let viewModels = Harmonize.productionCode().classes()
.withNameEndingWith("ViewModel")
viewModels.assertTrue(message: "All ViewModels must inherit from BaseViewModel") {
$0.inherits(from: "BaseViewModel")
}
}This lint rule enforces all ViewModels to inherit from BaseViewModel. Since it runs as a unit test, it will fail once it detects a violation. You can add exceptions or a baseline to this rule using the withoutName function:
let viewModels = Harmonize.productionCode().classes()
.withNameEndingWith("ViewModel")
.withoutName(["LegacyViewModel"])You can create similar rules for any architectural or structural pattern that you want to enforce.
Unlike regex-based linters such as SwiftLint, Harmonize provides you with a rich and simple API to directly access any component in your codebase—including files, packages, classes, functions, and properties—and make assertions about them.
Instead of passing a plain message string in the error message of a lint rule, you can pass a Rule with additional context. This helps AI agents understand and fix the violations more reliably:
viewModels.assertTrue(rule: rule) {
$0.inherits(from: "BaseViewModel")
}
private static let rule = Rule(
description: "ViewModels inherit from BaseViewModel.",
rationale: "BaseViewModel provides the lifecycle callbacks and stores Combine cancellables used by our ViewModels.",
fixHint: "Declare the class as `final class MyViewModel: BaseViewModel`.",
badExample: "final class MyViewModel { }",
goodExample: "final class MyViewModel: BaseViewModel { }"
)When the assertion fails, each violation is reported at its source location along with the full rule context:
RULE: ViewModels inherit from BaseViewModel.
WHY: BaseViewModel provides the lifecycle callbacks and stores Combine cancellables used by our ViewModels.
HOW TO FIX: Declare the class as `final class MyViewModel: BaseViewModel`.
❌ BAD:
final class MyViewModel { }
✅ GOOD:
final class MyViewModel: BaseViewModel { }
To add Harmonize using Swift Package Manager, follow these steps:
In Xcode:
- Go to File > Add Package Dependencies....
- Enter the repository URL:
https://github.com/perrystreetsoftware/Harmonize.git. - Add it as a dependency to your test target
Or, manually add it to your Package.swift file:
.package(url: "https://github.com/perrystreetsoftware/Harmonize.git", from: "0.2.0"),You can optionally create a dedicated Swift package for your Harmonize lint rules to separate them from the rest of the unit tests.
Add an empty .harmonize.yaml file to the root of your project.
This file is required for Harmonize to correctly detect the project root and apply your lint rules.
You can also optionally specify which files or folders you want to exclude from all lint rules, using the excludes key:
excludes:
- Package.swiftSince Harmonize lint rules run as unit tests, you can integrate them easily into your existing CI/CD pipeline and automate them, similarly as you would automate your unit tests. Here’s an example of a GitHub Action that runs all your Harmonize lint rules when a pull request is opened:
name: Run Harmonize Lint Rules
on:
pull_request:
types: [opened, synchronize]
jobs:
harmonize:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Run Harmonize Rules (macOS)
run: |
xcodebuild -scheme YourHarmonizeTestScheme -sdk macosx testIn your AGENTS.md or CLAUDE.md file, depending on the coding agent you use, add the following instruction to run the lint rules after every task:
- After implementing a task, run the Harmonize lint rules using the `run-harmonize` skill.Add the run-harmonize/SKILL.md into your project and adjust the Swift package path if your rules live somewhere other than SwiftPackages/HarmonizeRules:
---
name: run-harmonize
description: Runs the Harmonize lint rules and fixes violations. Use after implementing a task.
---
Harmonize rules are unit tests in `SwiftPackages/HarmonizeRules/Tests/HarmonizeRulesTests/`.
## How to run
```bash
# All rules
swift test --package-path SwiftPackages/HarmonizeRules
# One rule, by its class name
swift test --package-path SwiftPackages/HarmonizeRules --filter ViewModelsInheritBaseViewModel
```
## How to fix violations
For each failing rule:
1. Read the failure. `RULE` says what is enforced, `WHY` explains the reasoning,
`HOW TO FIX` says what to change, `❌ BAD` and `✅ GOOD` show the pattern to replace and the one to use.
2. Fix the production code the failure points to. Do not add it to the baseline.
3. Re-run only that rule with `--filter <RuleName>`.
4. Repeat until it passes, then run the full suite once more.- Architectural Linting for Swift made Easy
- Goodbye Code Reviews, Hello Harmonize: Enforce Your Architecture in Swift
See how our Woof demo app is using Harmonize to enforce its architecture. Woof mirrors the patterns we use in our production iOS apps, so its lint rules are a good starting point for your own project.
All contributions are welcome through pull requests, issues, or discussions.