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=]"
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- allowswiftformatto 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 thexcodebuilddestination (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). --fixmode: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 buildthenswift 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
--destinationif given, else pick an available iOS simulator fromxcrun simctl list devices available. If no iOS runtime is installed, report it and the non-interactive install pathxcodebuild -downloadPlatform iOSrather 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.