- name
- swift-verify
- description
- Run the right local verification gate for a Swift project - detect XcodeGen vs plain .xcodeproj vs SwiftPM vs Tuist, then regenerate (if applicable), swiftformat --lint, swiftlint --strict, and xcodebuild test on the cheapest valid destination (macOS for pure-Swift) or swift build/test for SwiftPM. Reports each stage; --fix lets SwiftFormat rewrite.
- argument-hint
- [path] [--fix] [--no-test] [--destination=<dest>]
- allowed-tools
- ["Bash","Read","Glob","Grep"]
- disable-model-invocation
- true
# Swift Verify
You run a Swift project's local quality gate, adapting the pipeline to how the project is built. This is the portable analog of a hand-written `scripts/verify.sh`: it discovers the build system and tools, runs them in the order that does not waste work, and reports each stage honestly.
This is the Swift-native specialization of a generic preflight; it does not duplicate ai-workflow's `/workflow-preflight` (Node/Python/.NET/Go/Rust). For release-blocker auditing (entitlements, Xcode Cloud, build numbers) use `/swift-preflight`; for general bug-finding use `/code-review`.
## Arguments
`$ARGUMENTS`:
- `path` - repo root (default cwd).
- `--fix` - allow `swiftformat` to rewrite in place (default is lint-only / non-destructive).
- `--no-test` - skip the build/test stage (format + lint only; good for doc-only changes).
- `--destination=<dest>` - override the `xcodebuild` destination (e.g. `platform=iOS Simulator,name=iPhone 16`).
## Phase 0: Detect build system + tools
- **XcodeGen**: `project.yml` + `command -v xcodegen`.
- **Tuist**: `Project.swift` + `command -v tuist`.
- **Plain Xcode**: `*.xcodeproj` / `*.xcworkspace`, no generator.
- **SwiftPM**: `Package.swift` (library or executable).
Tool availability: `command -v swiftformat swiftlint xcodegen xcodebuild swift`. If a required tool is missing, report it and the install hint (`brew install swiftformat swiftlint xcodegen`) and continue with the stages you can run - do not abort the whole gate for one missing optional tool.
Config presence: `.swiftformat`, `.swiftlint.yml`. If absent, run the tool with defaults but note that the project has no pinned config (so local and CI can diverge).
## Phase 1: Regenerate (generator projects only)
If XcodeGen: `xcodegen generate` (quiet). If Tuist: `tuist generate`.
Why first: newly added `.swift` files are not in the `.xcodeproj` until regen, and SourceKit diagnostics for them are stale. Regenerating makes `xcodebuild` authoritative. On a plain `.xcodeproj` or SwiftPM, skip this stage.
**Caution:** regeneration rewrites the pbxproj from `project.yml`. If the working tree has uncommitted pbxproj hand-edits, warn the user that regen will revert them (this is exactly the drift trap - see `/swift-preflight` Check 1). Do not regen over a dirty pbxproj without noting it.
## Phase 2: Format
- Default / lint mode: `swiftformat --lint .` (reports, exits non-zero on diffs, writes nothing).
- `--fix` mode: `swiftformat .` (rewrites in place).
Run format **before** lint so SwiftLint does not flag issues the formatter would have fixed. Note the known SwiftFormat↔SwiftLint `modifier_order` standoff (`nonisolated(unsafe) private static`): if both fire on the same line, that is the documented conflict, not a real defect - prefer moving the helper to file scope, or a scoped per-line disable pair.
## Phase 3: Lint (strict)
`swiftlint lint --strict --quiet`. Strict = warnings fail the run, matching CI. Report each violation with `file:line`. Do not suggest `// swiftlint:disable` as a fix unless the rule genuinely cannot be satisfied (e.g. an `override class func` that cannot be `static`); even then require a one-line justification comment. Never suggest `--no-verify` or blanket disables.
## Phase 4: Build / test
Pick the **cheapest valid destination**:
- **SwiftPM**: `swift build` then `swift test` (no simulator needed).
- **App / framework with macOS support**: `xcodebuild -scheme <scheme> -destination "platform=macOS,arch=arm64" test`. macOS is cheapest (no simulator boot) and is the right default for pure-Swift logic (models, services, persistence, networking).
- **iOS-only target**: requires a simulator runtime. Use `--destination` if given, else pick an available iOS simulator from `xcrun simctl list devices available`. If no iOS runtime is installed, report it and the non-interactive install path `xcodebuild -downloadPlatform iOS` rather than failing silently; fall back to building (not testing) if no destination is bootable.
For multiplatform unit-test targets generated by XcodeGen, remember the macOS `TEST_HOST` override is required (`$(BUILT_PRODUCTS_DIR)/App.app/Contents/MacOS/App`); if `xcodebuild test` fails to launch the host on macOS, surface that as the likely cause.
Skip this phase entirely if `--no-test`.
## Phase 5: Report
```
Swift Verify — <repo> (XcodeGen · macOS destination)
xcodegen generate ok
swiftformat --lint 3 files would change (run with --fix to rewrite)
swiftlint --strict 2 violations
App/Services/AuthService.swift:42 force_unwrapping
App/Views/ReaderView.swift:88 modifier_order
xcodebuild test 52 passed, 0 failed
Result: FAIL (format + lint)
Next: /swift-verify --fix to auto-format, then fix the 2 lint violations.
```
Report the true outcome of every stage, including the exact failing command output (trim to the relevant lines). If a stage was skipped (missing tool, `--no-test`, no bootable destination), say so explicitly - never imply a skipped stage passed. Exit-code semantics: clean = all stages green; any failing stage = overall FAIL.
View on GitHub