| name | code-quality-gates |
| description | Expert quality gate decisions for iOS/tvOS: which gates matter for your project size, threshold calibration that catches bugs without blocking velocity, SwiftLint rule selection, and CI integration patterns. Use when setting up linting, configuring CI pipelines, or calibrating coverage thresholds. Trigger keywords: SwiftLint, SwiftFormat, coverage, CI, quality gate, lint, static analysis, pre-commit, threshold, warning |
| version | 3.0.0 |
Code Quality Gates — Expert Decisions
Expert decision frameworks for quality gate choices. Claude knows linting tools — this skill provides judgment calls for threshold calibration and rule selection.
Decision Trees
Which Quality Gates for Your Project
Project stage?
├─ Greenfield (new project)
│ └─ Enable ALL gates from day 1
│ • SwiftLint (strict)
│ • SwiftFormat
│ • SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
│ • Coverage > 80%
│
├─ Brownfield (legacy, no gates)
│ └─ Adopt incrementally:
│ 1. SwiftLint with --baseline (ignore existing)
│ 2. Format new files only
│ 3. Gradually increase thresholds
│
└─ Existing project with some gates
└─ Team size?
├─ Solo/Small (1-3) → Lint + Format sufficient
└─ Medium+ (4+) → Full pipeline
• Add coverage gates
• Add static analysis
• Add security scanning
Coverage Threshold Selection
What type of code?
├─ Domain/Business Logic
│ └─ 90%+ coverage required
│ • Business rules must be tested
│ • Silent bugs are expensive
│
├─ Data Layer (Repositories, Mappers)
│ └─ 85% coverage
│ • Test mapping edge cases
│ • Test error handling
│
├─ Presentation (ViewModels)
│ └─ 70-80% coverage
│ • Test state transitions
│ • Skip trivial bindings
│
└─ Views (SwiftUI)
└─ Don't measure coverage
• Snapshot tests or manual QA
• Unit testing views has poor ROI
SwiftLint Rule Strategy
Starting fresh?
├─ YES → Enable opt_in_rules aggressively
│ └─ Easier to disable than enable later
│
└─ NO → Adopting on existing codebase
└─ Use baseline approach:
1. Run: swiftlint lint --reporter json > baseline.json
2. Configure: baseline_path: baseline.json
3. New violations fail, existing ignored
4. Chip away at baseline over time
NEVER Do
Threshold Anti-Patterns
NEVER set coverage to 100%:
MIN_COVERAGE: 100
MIN_COVERAGE: 80
NEVER use zero-tolerance for warnings initially on legacy code:
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
SWIFT_TREAT_WARNINGS_AS_ERRORS = NO
swiftlint lint --baseline existing-violations.json
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
NEVER skip gates on "urgent" PRs:
if: github.event.pull_request.labels.contains('urgent')
run: echo "Skipping quality gates"
SwiftLint Anti-Patterns
NEVER disable rules project-wide without documenting why:
disabled_rules:
- force_unwrapping
- force_cast
- line_length
disabled_rules:
NEVER use inline disables without explanation:
let value = optionalValue!
let value = optionalValue!
NEVER silence all warnings in a file:
let x = calculateX()
CI Anti-Patterns
NEVER run expensive gates first:
jobs:
test:
...
lint:
...
jobs:
lint:
runs-on: macos-14
steps: [swiftlint, swiftformat]
build:
needs: lint
...
test:
needs: build
...
NEVER run quality gates only on PR:
on:
pull_request:
branches: [main]
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
Essential Configurations
Minimal SwiftLint (Start Here)
excluded:
- Pods
- .build
- DerivedData
opt_in_rules:
- force_unwrapping
- implicitly_unwrapped_optional
- empty_count
- first_where
- contains_over_first_not_nil
- fatal_error_message
line_length:
warning: 120
error: 200
ignores_urls: true
ignores_function_declarations: true
function_body_length:
warning: 50
error: 80
cyclomatic_complexity:
warning: 10
error: 15
type_body_length:
warning: 300
error: 400
file_length:
warning: 500
error: 800
Minimal SwiftFormat
--swiftversion 5.9
--exclude Pods,.build,DerivedData
--indent 4
--maxwidth 120
--wraparguments before-first
--wrapparameters before-first
--enable sortedImports
--enable trailingCommas
--enable redundantSelf
--enable redundantReturn
--enable blankLinesAtEndOfScope
--disable acronyms
--disable wrapMultilineStatementBraces
Xcode Build Settings (Quality Enforced)
// Fail on warnings
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
GCC_TREAT_WARNINGS_AS_ERRORS = YES
// Strict concurrency (Swift 6 prep)
SWIFT_STRICT_CONCURRENCY = complete
// Static analyzer
RUN_CLANG_STATIC_ANALYZER = YES
CLANG_STATIC_ANALYZER_MODE = deep
CI Patterns
GitHub Actions (Minimal Effective)
name: Quality Gates
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- run: brew install swiftlint swiftformat
- run: swiftlint lint --strict --reporter github-actions-logging
- run: swiftformat . --lint
build:
needs: lint
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Build with strict warnings
run: |
xcodebuild build \
-scheme App \
-destination 'platform=iOS Simulator,name=iPhone 15' \
SWIFT_TREAT_WARNINGS_AS_ERRORS=YES
test:
needs: build
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Test with coverage
run: |
xcodebuild test \
-scheme App \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-enableCodeCoverage YES \
-resultBundlePath TestResults.xcresult
- name: Check coverage threshold
run: |
COVERAGE=$(xcrun xccov view --report --json TestResults.xcresult | \
jq '.targets[] | select(.name | contains("App")) | .lineCoverage * 100')
echo "Coverage: ${COVERAGE}%"
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "❌ Coverage below 80%"
exit 1
fi
Pre-commit Hook (Local Enforcement)
#!/bin/bash
echo "Running pre-commit quality gates..."
if ! swiftlint lint --strict --quiet 2>/dev/null; then
echo "❌ SwiftLint failed"
exit 1
fi
if ! swiftformat . --lint 2>/dev/null; then
echo "❌ Code formatting issues. Run: swiftformat ."
exit 1
fi
echo "✅ Pre-commit checks passed"
Threshold Calibration
Finding the Right Coverage Threshold
Step 1: Measure current coverage
$ xcodebuild test -enableCodeCoverage YES ...
$ xcrun xccov view --report TestResults.xcresult
Step 2: Set threshold slightly below current
Current: 73% → Set threshold: 70%
Prevents regression without blocking
Step 3: Ratchet up over time
Week 1: 70%
Week 4: 75%
Week 8: 80%
Stop at: 80-85% (diminishing returns above)
SwiftLint Warning Budget
MAX_WARNINGS: 100
MAX_WARNINGS: 50
MAX_WARNINGS: 20
MAX_WARNINGS: 0
Quick Reference
Gate Priority Order
| Priority | Gate | Time | ROI |
|---|
| 1 | SwiftLint | ~30s | High — catches bugs |
| 2 | SwiftFormat | ~15s | Medium — consistency |
| 3 | Build (0 warnings) | ~2-5m | High — compiler catches issues |
| 4 | Unit Tests | ~5-15m | High — catches regressions |
| 5 | Coverage Check | ~1m | Medium — enforces testing |
| 6 | Static Analysis | ~5-10m | Medium — deep issues |
Red Flags
| Smell | Problem | Fix |
|---|
| Gates disabled for "urgent" PR | Culture problem | Gates are non-negotiable |
| 100% coverage requirement | Gaming metrics | 80-85% is optimal |
| All SwiftLint rules enabled | Too noisy | Curate impactful rules |
| Pre-commit takes > 30s | Devs skip it | Only fast checks locally |
| Different rules in CI vs local | Surprises | Same config everywhere |
SwiftLint Rules Worth Enabling
| Rule | Why |
|---|
force_unwrapping | Prevents crashes |
implicitly_unwrapped_optional | Prevents crashes |
empty_count | Performance (O(1) vs O(n)) |
first_where | Performance |
fatal_error_message | Better crash logs |
unowned_variable_capture | Prevents crashes |
unused_closure_parameter | Code hygiene |
SwiftLint Rules to Avoid
| Rule | Why Avoid |
|---|
explicit_type_interface | Swift has inference for a reason |
file_types_order | Team preference varies |
prefixed_toplevel_constant | Outdated convention |
extension_access_modifier | Rarely adds value |