/fitness-functions
Write architecture fitness functions — deterministic tests that enforce a project's hard rules (module boundaries, offline guarantees, content contracts) from inside its own test target. Use when a constraint lives only in prose (CLAUDE.md, code review) and should become
$ npx -y skills add rshankras/claude-code-apple-skills --skill fitness-functions --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/fitness-functions
Context preview
The summary Claude sees to decide when to auto-load this skill.
Write architecture fitness functions — deterministic tests that enforce a project's hard rules (module boundaries, offline guarantees, content contracts) from inside its own test target. Use when a constraint lives only in prose (CLAUDE.md, code review) and should become
SKILL.md
fitness-functions.SKILL.mdname: fitness-functions
description: Write architecture fitness functions — deterministic tests that enforce a project's hard rules (module boundaries, offline guarantees, content contracts) from inside its own test target. Use when a constraint lives only in prose (CLAUDE.md, code review) and should become un-arguable.
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash]
last_verified: 2026-07-24
review_by: 2027-07-01
Architecture Fitness Functions
A fitness function is an ordinary test that enforces an architectural rule instead of a behavior. It lives in the app's own test target, runs under every existing test gate forever, and turns "the agent said it's fine" into an exit code. This is the gauntlet's answer for invariants no unit test of behavior can see: a file quietly importing a framework it must not touch, a registry drifting from its documented size, user-facing copy breaking its format contract.
When This Skill Activates
Use this skill when:
- A project hard rule exists only in prose — CLAUDE.md constraints, file-header comments, review checklists ("deck games must run fully offline", "only the sync layer may import CloudKit")
- A code review keeps re-checking the same structural rule by hand
- An `/apple:review` structural finding is being graduated into permanent enforcement
- A count, boundary, or content format was broken silently once already
Why a Test, Not a Lint Rule
A per-line regex (SwiftLint `custom_rules`) can flag a *line*. A fitness function can assert a *set*: "the files importing MusicKit are exactly these four", "the mode registry has exactly 21 entries", "every case's copy fits the format". Sets, counts, and cross-file facts need code, and putting that code in the test target means no new tooling, no CI wiring — it rides the `test` gate that already exists.
The Three Patterns
Pattern 1: Import-Boundary Allowlist Scan
Enforces "only these files may import X." The test walks the shipped source tree and compares the *actual* importer set against an allowlist — so a fifth importer fails a test, not a code review.
Key rules (learned in production):
- **Resolve the source root from `#filePath`**, never a hardcoded path — the test stays correct wherever the repo is checked out.
- **Match exact trimmed lines** (`line == "import X"`), not `contains` — comments documenting the contract itself must not count as hits.
- **Compare full sets, not counts** — the failure message then names the drifted file.
- **Exclude non-shipping directories deliberately** (DEBUG-only spikes, generated code) and say why in a comment: the guarantee is about what ships.
- **The allowlist is a fence, not a headcount.** Widening it is a legitimate, *documented* decision: when a new file genuinely belongs inside the boundary, the test failing is the system working — record why in the allowlist comment, then add it.
- **Add a companion API-surface scan** for bypass routes the import check can't see. An import-only check misses a file calling the underlying API directly (every file already imports Foundation, which is enough to reach `URLSession` without importing the fenced framework). Scan for the raw API strings (`URLSession(`, `URLSession.shared`) outside the boundary too.
Template: `templates/ImportBoundaryTests.swift`
Pattern 2: Runtime Sentinel
Enforces "this code path never *attempts* X" — stronger than "happens to succeed without X." The canonical case is an offline guarantee: register a `URLProtocol` subclass that intercepts, counts, and fails every network request, drive the real production path, and assert the counter stayed at zero.
Key rules:
- Register the sentinel per-test and unregister in `defer`.
- The sentinel **fails** requests loudly (never lets them reach the network) — a regression fails in CI instead of silently succeeding against a live server.
- Assert `interceptedRequestCount == 0`: the contract is *zero attempts*, not zero successes.
- Drive the real path (real service, real composition), with only storage in-memory.
Template: `templates/NetworkSentinelTests.swift`
Pattern 3: Contract Pins
Pins facts that must change *deliberately*, never by drift.
**Count pins** — registry and configuration sizes:
// Phase 3.5 added 3 modes — 9 modes, 5 streaming.
// Phase 3.8 added the four reunion music games — 15 modes, 9 streaming.
// Phase 3.10 added the DJ-host quartet — 18 modes, 10 streaming.
#expect(PartyOccasion.bigParty.modes.count == 18)
#expect(PartyOccasion.bigParty.modes.filter(\.usesDJEngine).count == 10)
The comment trail is mandatory: each pinned number carries its history, so updating the pin is a reviewed decision with a written reason, not a chore silenced with an edit.
**Copy-contract pins** — structural rules on user-facing content, iterated over `CaseIterable` so new cases are covered automatically:
@Test func everyModeHasExactlyThreeBeats() {
for mode in GameMode.allCases {
#expect(mode.howItPlaysBeats.count == 3, "\(mode.rawValue) has \(mode.howItPlaysBeats.count) beats, expected 3")
}
}Plus a few **verbatim exemplars** pinned with exact equality — a future content pass must change those deliberately, not by accident.
Template: `templates/ContractPinTests.swift`
Process
Phase 1: Identify the Invariant
Read CLAUDE.md / APP.md / file-header comments for hard rules currently enforced by nothing. Good candidates state a *set or bound*: "only", "never", "exactly", "at most", "every".
Phase 2: Pick the Pattern
| The rule is about… | Pattern | |---|---| | Which files may use a framework/API | 1 — import-boundary scan (+ API-surface companion) | | What a code path may *do* at runtime | 2 — runtime sentinel | | A size, format, or exact content | 3 — contract pins |
Phase 3: Write the Suite
Copy the matching template into the existing unit-test target and adapt. One suite per invariant; name it after the guarantee (`OfflineGuaranteeTests`), not the m
Read more
name: fitness-functions description: Write architecture fitness functions — deterministic tests that enforce a project's hard rules (module boundaries, offline guarantees, content contracts) from inside its own test target. Use when a constraint lives only in prose (CLAUDE.md, code review) and should become un-arguable. allowed-tools: [Read, Write, Edit, Glob, Grep, Bash] last_verified: 2026-07-24 review_by: 2027-07-01
Architecture Fitness Functions
A fitness function is an ordinary test that enforces an architectural rule instead of a behavior. It lives in the app's own test target, runs under every existing test gate forever, and turns "the agent said it's fine" into an exit code. This is the gauntlet's answer for invariants no unit test of behavior can see: a file quietly importing a framework it must not touch, a registry drifting from its documented size, user-facing copy breaking its format contract.
When This Skill Activates
Use this skill when:
- A project hard rule exists only in prose — CLAUDE.md constraints, file-header comments, review checklists ("deck games must run fully offline", "only the sync layer may import CloudKit")
- A code review keeps re-checking the same structural rule by hand
- An `/apple:review` structural finding is being graduated into permanent enforcement
- A count, boundary, or content format was broken silently once already
Why a Test, Not a Lint Rule
A per-line regex (SwiftLint `custom_rules`) can flag a *line*. A fitness function can assert a *set*: "the files importing MusicKit are exactly these four", "the mode registry has exactly 21 entries", "every case's copy fits the format". Sets, counts, and cross-file facts need code, and putting that code in the test target means no new tooling, no CI wiring — it rides the `test` gate that already exists.
The Three Patterns
Pattern 1: Import-Boundary Allowlist Scan
Enforces "only these files may import X." The test walks the shipped source tree and compares the *actual* importer set against an allowlist — so a fifth importer fails a test, not a code review.
Key rules (learned in production):
- **Resolve the source root from `#filePath`**, never a hardcoded path — the test stays correct wherever the repo is checked out.
- **Match exact trimmed lines** (`line == "import X"`), not `contains` — comments documenting the contract itself must not count as hits.
- **Compare full sets, not counts** — the failure message then names the drifted file.
- **Exclude non-shipping directories deliberately** (DEBUG-only spikes, generated code) and say why in a comment: the guarantee is about what ships.
- **The allowlist is a fence, not a headcount.** Widening it is a legitimate, *documented* decision: when a new file genuinely belongs inside the boundary, the test failing is the system working — record why in the allowlist comment, then add it.
- **Add a companion API-surface scan** for bypass routes the import check can't see. An import-only check misses a file calling the underlying API directly (every file already imports Foundation, which is enough to reach `URLSession` without importing the fenced framework). Scan for the raw API strings (`URLSession(`, `URLSession.shared`) outside the boundary too.
Template: `templates/ImportBoundaryTests.swift`
Pattern 2: Runtime Sentinel
Enforces "this code path never *attempts* X" — stronger than "happens to succeed without X." The canonical case is an offline guarantee: register a `URLProtocol` subclass that intercepts, counts, and fails every network request, drive the real production path, and assert the counter stayed at zero.
Key rules:
- Register the sentinel per-test and unregister in `defer`.
- The sentinel **fails** requests loudly (never lets them reach the network) — a regression fails in CI instead of silently succeeding against a live server.
- Assert `interceptedRequestCount == 0`: the contract is *zero attempts*, not zero successes.
- Drive the real path (real service, real composition), with only storage in-memory.
Template: `templates/NetworkSentinelTests.swift`
Pattern 3: Contract Pins
Pins facts that must change *deliberately*, never by drift.
**Count pins** — registry and configuration sizes:
// Phase 3.5 added 3 modes — 9 modes, 5 streaming. // Phase 3.8 added the four reunion music games — 15 modes, 9 streaming. // Phase 3.10 added the DJ-host quartet — 18 modes, 10 streaming. #expect(PartyOccasion.bigParty.modes.count == 18) #expect(PartyOccasion.bigParty.modes.filter(\.usesDJEngine).count == 10)
The comment trail is mandatory: each pinned number carries its history, so updating the pin is a reviewed decision with a written reason, not a chore silenced with an edit.
**Copy-contract pins** — structural rules on user-facing content, iterated over `CaseIterable` so new cases are covered automatically:
@Test func everyModeHasExactlyThreeBeats() {
for mode in GameMode.allCases {
#expect(mode.howItPlaysBeats.count == 3, "\(mode.rawValue) has \(mode.howItPlaysBeats.count) beats, expected 3")
}
}Plus a few **verbatim exemplars** pinned with exact equality — a future content pass must change those deliberately, not by accident.
Template: `templates/ContractPinTests.swift`
Process
Phase 1: Identify the Invariant
Read CLAUDE.md / APP.md / file-header comments for hard rules currently enforced by nothing. Good candidates state a *set or bound*: "only", "never", "exactly", "at most", "every".
Phase 2: Pick the Pattern
| The rule is about… | Pattern | |---|---| | Which files may use a framework/API | 1 — import-boundary scan (+ API-surface companion) | | What a code path may *do* at runtime | 2 — runtime sentinel | | A size, format, or exact content | 3 — contract pins |
Phase 3: Write the Suite
Copy the matching template into the existing unit-test target and adapt. One suite per invariant; name it after the guarantee (`OfflineGuaranteeTests`), not the m
A collection of Claude Code skills for iOS, macOS, watchOS, visionOS, and Apple platform development. These skills help you plan and build apps, maintain code quality, ensure HIG compliance, and guide you from idea to App Store.
Repo: rshankras/claude-code-apple-skills
Other skills on rshankras-apple-skills.
- /app-store
App Store optimization and marketing skills for descriptions, screenshots, keywords, review responses, and comprehensive promotional strategy. Use when user needs help with App Store presence, ASO, marketing, or customer communication.
Open skill - /ad-attribution
Privacy-preserving ad measurement with AdAttributionKit (SKAdNetwork's successor) — install and re-engagement attribution, conversion-value strategy under crowd anonymity, and end-to-end postback testing. Use when running paid acquisition beyond Apple Ads, measuring
Open skill - /app-description-writer
Generate compelling App Store descriptions that convert browsers into users. Use when writing initial descriptions, improving existing copy, or drafting promotional text and What's New for a major update.
Open skill - /apple-search-ads
Apple Search Ads campaign strategy for indie developers — paid acquisition, keyword bidding, budget planning, and ROAS optimization. Use when user asks about running ads, paid user acquisition, or Apple Search Ads campaigns.
Open skill - /iap-finalizer
Take a one-time in-app purchase from MISSING_METADATA to READY_TO_SUBMIT in App Store Connect — set its price schedule and localized display name/description (and optional review screenshot) via the ASC REST API. Use at Phase 6 (Pre-Release), after the IAP is built in-app (Phase
Open skill - /keyword-optimizer
Optimize app title, subtitle, and keywords for maximum App Store discoverability. Use when launching a new app, improving search rankings, entering new markets/languages, or safely optimizing ASO for an app with existing traffic.
Open skill

