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.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add charlesjones-dev/claude-code-plugins-dev --skill swift-verify --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Swift Verify?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/charlesjones-dev-swift-verify)More formats (shields.io, HTML) on the badges page.
---
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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!