mobile-ai-agents 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/AGENTS.md +228 -0
  2. package/CLAUDE.md +236 -0
  3. package/LICENSE +21 -0
  4. package/README.md +652 -0
  5. package/agents/android/android-crash-analyzer/README.md +134 -0
  6. package/agents/android/android-crash-analyzer/agent.md +206 -0
  7. package/agents/android/android-crash-analyzer/example-input.md +138 -0
  8. package/agents/android/android-crash-analyzer/example-output.md +225 -0
  9. package/agents/android/anr-investigation/README.md +78 -0
  10. package/agents/android/anr-investigation/agent.md +117 -0
  11. package/agents/android/anr-investigation/example-input.md +62 -0
  12. package/agents/android/anr-investigation/example-output.md +103 -0
  13. package/agents/android/axiom/agent.md +197 -0
  14. package/agents/android/code-reviewer/README.md +108 -0
  15. package/agents/android/code-reviewer/agent.md +210 -0
  16. package/agents/android/code-reviewer/example-input.md +116 -0
  17. package/agents/android/code-reviewer/example-output.md +115 -0
  18. package/agents/android/compose-navigation/README.md +76 -0
  19. package/agents/android/compose-navigation/agent.md +121 -0
  20. package/agents/android/compose-navigation/example-input.md +49 -0
  21. package/agents/android/compose-navigation/example-output.md +325 -0
  22. package/agents/android/compose-screen-builder/README.md +95 -0
  23. package/agents/android/compose-screen-builder/agent.md +170 -0
  24. package/agents/android/compose-screen-builder/example-input.md +40 -0
  25. package/agents/android/compose-screen-builder/example-output.md +568 -0
  26. package/agents/android/compose-ui-reviewer/README.md +73 -0
  27. package/agents/android/compose-ui-reviewer/agent.md +253 -0
  28. package/agents/android/compose-ui-reviewer/example-input.md +84 -0
  29. package/agents/android/compose-ui-reviewer/example-output.md +102 -0
  30. package/agents/android/crash-analyzer/README.md +53 -0
  31. package/agents/android/crash-analyzer/agent.md +295 -0
  32. package/agents/android/memory-leak-analyzer/README.md +79 -0
  33. package/agents/android/memory-leak-analyzer/agent.md +116 -0
  34. package/agents/android/memory-leak-analyzer/example-input.md +78 -0
  35. package/agents/android/memory-leak-analyzer/example-output.md +128 -0
  36. package/agents/cross-platform/accessibility-auditor/README.md +47 -0
  37. package/agents/cross-platform/accessibility-auditor/agent.md +268 -0
  38. package/agents/cross-platform/accessibility-auditor/example-input.md +84 -0
  39. package/agents/cross-platform/accessibility-auditor/example-output.md +132 -0
  40. package/agents/cross-platform/appforge/agent.md +303 -0
  41. package/agents/cross-platform/ci-cd-generator/README.md +65 -0
  42. package/agents/cross-platform/ci-cd-generator/agent.md +422 -0
  43. package/agents/cross-platform/ci-cd-generator/example-input.md +69 -0
  44. package/agents/cross-platform/ci-cd-generator/example-output.md +261 -0
  45. package/agents/cross-platform/crasher/agent.md +221 -0
  46. package/agents/cross-platform/figma/agent.md +247 -0
  47. package/agents/cross-platform/launchpad/agent.md +330 -0
  48. package/agents/cross-platform/mobile-harness/agent.md +422 -0
  49. package/agents/cross-platform/mobile-memory/agent.md +429 -0
  50. package/agents/cross-platform/perf/agent.md +261 -0
  51. package/agents/cross-platform/pipeline/agent.md +288 -0
  52. package/agents/cross-platform/release-notes-generator/README.md +63 -0
  53. package/agents/cross-platform/release-notes-generator/agent.md +218 -0
  54. package/agents/cross-platform/release-notes-generator/example-input.md +71 -0
  55. package/agents/cross-platform/release-notes-generator/example-output.md +96 -0
  56. package/agents/cross-platform/scribe/agent.md +180 -0
  57. package/agents/cross-platform/security-scanner/README.md +61 -0
  58. package/agents/cross-platform/security-scanner/agent.md +287 -0
  59. package/agents/cross-platform/security-scanner/example-input.md +77 -0
  60. package/agents/cross-platform/security-scanner/example-output.md +141 -0
  61. package/agents/cross-platform/sentinel/agent.md +217 -0
  62. package/agents/cross-platform/store-listing-writer/README.md +58 -0
  63. package/agents/cross-platform/store-listing-writer/agent.md +380 -0
  64. package/agents/cross-platform/store-listing-writer/example-input.md +67 -0
  65. package/agents/cross-platform/store-listing-writer/example-output.md +131 -0
  66. package/agents/flutter/bloc-feature-builder/README.md +101 -0
  67. package/agents/flutter/bloc-feature-builder/agent.md +225 -0
  68. package/agents/flutter/bloc-feature-builder/example-input.md +47 -0
  69. package/agents/flutter/bloc-feature-builder/example-output.md +838 -0
  70. package/agents/flutter/dart/agent.md +223 -0
  71. package/agents/flutter/widget-generator/README.md +66 -0
  72. package/agents/flutter/widget-generator/agent.md +264 -0
  73. package/agents/flutter/widget-generator/example-input.md +71 -0
  74. package/agents/flutter/widget-generator/example-output.md +163 -0
  75. package/agents/gaming/forge/agent.md +197 -0
  76. package/agents/gaming/unreal/agent.md +200 -0
  77. package/agents/ios/crash-analyzer/README.md +69 -0
  78. package/agents/ios/crash-analyzer/agent.md +300 -0
  79. package/agents/ios/crash-analyzer/example-input.md +83 -0
  80. package/agents/ios/crash-analyzer/example-output.md +126 -0
  81. package/agents/ios/swift/agent.md +206 -0
  82. package/agents/ios/swift-reviewer/README.md +68 -0
  83. package/agents/ios/swift-reviewer/agent.md +231 -0
  84. package/agents/ios/swift-reviewer/example-input.md +100 -0
  85. package/agents/ios/swift-reviewer/example-output.md +127 -0
  86. package/agents/react-native/bridge/agent.md +212 -0
  87. package/agents/react-native/performance-optimizer/README.md +81 -0
  88. package/agents/react-native/performance-optimizer/agent.md +284 -0
  89. package/agents/react-native/performance-optimizer/example-input.md +104 -0
  90. package/agents/react-native/performance-optimizer/example-output.md +132 -0
  91. package/agents/unity/shader-generator/README.md +77 -0
  92. package/agents/unity/shader-generator/agent.md +270 -0
  93. package/agents/unity/shader-generator/example-input.md +70 -0
  94. package/agents/unity/shader-generator/example-output.md +160 -0
  95. package/agents/unreal/blueprint-advisor/README.md +71 -0
  96. package/agents/unreal/blueprint-advisor/agent.md +311 -0
  97. package/agents/unreal/blueprint-advisor/example-input.md +73 -0
  98. package/agents/unreal/blueprint-advisor/example-output.md +217 -0
  99. package/cli/index.js +1091 -0
  100. package/docs/agent-guide.md +138 -0
  101. package/docs/chatgpt-integration.md +219 -0
  102. package/docs/core-loops.md +143 -0
  103. package/docs/cursor-integration.md +143 -0
  104. package/docs/getting-started.md +132 -0
  105. package/docs/index.html +711 -0
  106. package/docs/installation.md +271 -0
  107. package/docs/mobile-harness.md +160 -0
  108. package/docs/mobile-mcp.md +172 -0
  109. package/docs/mobile-memory.md +349 -0
  110. package/docs/release-process.md +85 -0
  111. package/docs/releases/release-v1.0.0.md +165 -0
  112. package/docs/roadmap/github-issues.md +239 -0
  113. package/docs/skill-categories.md +125 -0
  114. package/docs/vscode-copilot-integration.md +104 -0
  115. package/docs/wiki.md +615 -0
  116. package/install.sh +507 -0
  117. package/mobile-karpathy.md +79 -0
  118. package/package.json +57 -0
  119. package/skills/android/README.md +31 -0
  120. package/skills/android/android-tdd.md +89 -0
  121. package/skills/android/anr-investigation.md +251 -0
  122. package/skills/android/code-review.md +98 -0
  123. package/skills/android/compose-migration.md +48 -0
  124. package/skills/android/compose-review.md +78 -0
  125. package/skills/android/kotlin-modernize.md +51 -0
  126. package/skills/android/memory-leak-investigation.md +256 -0
  127. package/skills/android/proguard-rules.md +48 -0
  128. package/skills/cross-platform/accessibility-audit.md +63 -0
  129. package/skills/cross-platform/api-versioning.md +59 -0
  130. package/skills/cross-platform/clean-code-audit.md +239 -0
  131. package/skills/cross-platform/crash-triage.md +53 -0
  132. package/skills/cross-platform/deeplink-debug.md +57 -0
  133. package/skills/cross-platform/feature-slice.md +48 -0
  134. package/skills/cross-platform/grill-mobile.md +64 -0
  135. package/skills/cross-platform/mobile-mcp-qa.md +193 -0
  136. package/skills/cross-platform/mobile-memory-graph.md +179 -0
  137. package/skills/cross-platform/mobile-memory-save.md +210 -0
  138. package/skills/cross-platform/mobile-memory-search.md +154 -0
  139. package/skills/cross-platform/perf-audit.md +61 -0
  140. package/skills/cross-platform/prd-verification.md +324 -0
  141. package/skills/cross-platform/release-prep.md +64 -0
  142. package/skills/cross-platform/security-audit.md +281 -0
  143. package/skills/cross-platform/store-listing.md +75 -0
  144. package/skills/flutter/README.md +32 -0
  145. package/skills/flutter/dart-modernize.md +47 -0
  146. package/skills/flutter/flutter-review.md +59 -0
  147. package/skills/flutter/flutter-tdd.md +73 -0
  148. package/skills/flutter/widget-extract.md +41 -0
  149. package/skills/flutter/widget-gen.md +131 -0
  150. package/skills/gaming/blueprint-to-cpp.md +51 -0
  151. package/skills/gaming/game-perf.md +54 -0
  152. package/skills/gaming/shader-gen.md +44 -0
  153. package/skills/gaming/unity-tdd.md +85 -0
  154. package/skills/ios/README.md +45 -0
  155. package/skills/ios/data-persistence.md +151 -0
  156. package/skills/ios/ios-tdd.md +90 -0
  157. package/skills/ios/networking.md +139 -0
  158. package/skills/ios/performance.md +122 -0
  159. package/skills/ios/swift-concurrency.md +57 -0
  160. package/skills/ios/swift-review.md +124 -0
  161. package/skills/ios/swiftui-review.md +49 -0
  162. package/skills/ios/swiftui-state.md +122 -0
  163. package/skills/ios/unit-testing.md +144 -0
  164. package/skills/ios/xcode-warnings.md +37 -0
  165. package/skills/react-native/README.md +42 -0
  166. package/skills/react-native/bridge-audit.md +104 -0
  167. package/skills/react-native/expo-optimize.md +55 -0
  168. package/skills/react-native/new-arch-migrate.md +53 -0
  169. package/skills/react-native/performance.md +124 -0
  170. package/skills/react-native/rn-review.md +53 -0
  171. package/skills/react-native/rn-tdd.md +76 -0
  172. package/skills/shared/README.md +46 -0
  173. package/skills/shared/accessibility-audit.md +132 -0
  174. package/skills/shared/crash-analysis.md +110 -0
  175. package/skills/shared/security-scan.md +149 -0
  176. package/skills/unity/README.md +31 -0
  177. package/skills/unity/shader-review.md +110 -0
  178. package/templates/agent-template.md +88 -0
  179. package/templates/prompt-template.md +50 -0
  180. package/templates/skill-template.md +64 -0
  181. package/templates/workflow-template.md +48 -0
  182. package/workflows/app-launch.md +66 -0
  183. package/workflows/appforge-workflow.md +232 -0
  184. package/workflows/ci-setup.md +77 -0
  185. package/workflows/crash-to-fix.md +71 -0
  186. package/workflows/feature-ship.md +61 -0
  187. package/workflows/game-level.md +72 -0
  188. package/workflows/mobile-harness.md +319 -0
  189. package/workflows/mobile-mcp-qa.md +228 -0
  190. package/workflows/mobile-memory-workflow.md +243 -0
  191. package/workflows/new-project-setup.md +93 -0
  192. package/workflows/new-screen.md +68 -0
  193. package/workflows/perf-sprint.md +78 -0
  194. package/workflows/release-cycle.md +71 -0
@@ -0,0 +1,85 @@
1
+ # Skill — /unity-tdd
2
+
3
+ **Platform:** Unity (C#)
4
+ **Slash Command:** `/unity-tdd`
5
+ **Composable With:** agents/gaming/forge/agent.md
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ NUnit + Unity Test Runner TDD loop covering both EditMode (pure logic) and PlayMode (runtime behavior) tests. Generates failing tests first, then the minimal implementation.
12
+
13
+ ---
14
+
15
+ ## Skill Prompt
16
+
17
+ ```
18
+ Run the Unity TDD loop using NUnit and Unity Test Runner:
19
+
20
+ STEP 1 — RED: Write the failing test.
21
+ - EditMode test: for pure C# logic (no MonoBehaviour, no scene needed).
22
+ [TestFixture] class, [Test] methods, [SetUp]/[TearDown].
23
+ - PlayMode test: for runtime behavior requiring scene, physics, or coroutines.
24
+ Use UnityTest with IEnumerator return, yield return null to advance frame.
25
+ - Use [UnitySetUp] / [UnityTearDown] for async setup in PlayMode.
26
+
27
+ STEP 2 — GREEN: Minimal implementation to pass.
28
+ STEP 3 — REFACTOR: Separate concerns, extract interfaces for testability.
29
+
30
+ TESTABILITY PATTERNS FOR UNITY:
31
+ - Avoid MonoBehaviour in business logic: extract to plain C# classes.
32
+ - Use interfaces for dependencies (IEnemyAI, IHealthSystem) to enable mocking.
33
+ - ScriptableObjects for data: easy to instantiate in tests without scene.
34
+ - Time.deltaTime dependency: inject a clock abstraction (IClock.DeltaTime).
35
+
36
+ Format:
37
+ --- RED ---
38
+ <NUnit test code>
39
+
40
+ --- GREEN ---
41
+ <minimal C# implementation>
42
+
43
+ --- REFACTOR ---
44
+ <final implementation>
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Example
50
+
51
+ **Input:** "TDD a HealthSystem that reduces health on damage and fires OnDeath when reaching 0"
52
+
53
+ **Output:**
54
+ ```csharp
55
+ --- RED ---
56
+ [TestFixture]
57
+ public class HealthSystemTests {
58
+ [Test]
59
+ public void TakeDamage_ReducesHealth() {
60
+ var health = new HealthSystem(maxHealth: 100);
61
+ health.TakeDamage(30);
62
+ Assert.AreEqual(70, health.Current);
63
+ }
64
+
65
+ [Test]
66
+ public void TakeDamage_ToZero_FiresOnDeath() {
67
+ var health = new HealthSystem(maxHealth: 100);
68
+ bool died = false;
69
+ health.OnDeath += () => died = true;
70
+ health.TakeDamage(100);
71
+ Assert.IsTrue(died);
72
+ }
73
+ }
74
+
75
+ --- GREEN ---
76
+ public class HealthSystem {
77
+ public int Current { get; private set; }
78
+ public event Action OnDeath;
79
+ public HealthSystem(int maxHealth) => Current = maxHealth;
80
+ public void TakeDamage(int amount) {
81
+ Current = Mathf.Max(0, Current - amount);
82
+ if (Current == 0) OnDeath?.Invoke();
83
+ }
84
+ }
85
+ ```
@@ -0,0 +1,45 @@
1
+ # iOS Skills
2
+
3
+ Reusable skill prompt modules for iOS / macOS (Swift / SwiftUI) code reviews and analysis. The most comprehensive platform coverage in the repo — 6 skills covering memory, concurrency, networking, testing, performance, and persistence.
4
+
5
+ ## Index
6
+
7
+ | Skill | File | When to Use |
8
+ |---|---|---|
9
+ | Swift Code Review | [swift-review.md](swift-review.md) | ARC retain cycles, `[weak self]`, `[unowned self]`, `@MainActor`, force-unwrap |
10
+ | SwiftUI State Management | [swiftui-state.md](swiftui-state.md) | `@State`/`@StateObject`/`@ObservedObject` selection, expensive `body`, lifecycle |
11
+ | iOS Networking | [networking.md](networking.md) | URLSession, async/await, HTTP status checks, certificate pinning, threading |
12
+ | iOS Unit Testing | [unit-testing.md](unit-testing.md) | Swift Testing vs XCTest, async tests, mocking with protocols |
13
+ | iOS Performance | [performance.md](performance.md) | Main thread blocking, Core Data N+1, image decoding, background task expiration |
14
+ | iOS Data Persistence | [data-persistence.md](data-persistence.md) | UserDefaults vs Keychain vs Core Data vs SwiftData, file encryption |
15
+
16
+ ## Usage
17
+
18
+ Paste a skill prompt at the start of any LLM session:
19
+
20
+ ```
21
+ # Focused review
22
+ [paste skill prompt from swift-review.md]
23
+
24
+ Now review this Swift class: [paste code]
25
+ ```
26
+
27
+ ## Composable Pattern
28
+
29
+ Combine skills for a comprehensive review:
30
+
31
+ ```
32
+ [paste swift-review.md skill]
33
+ [paste swiftui-state.md skill]
34
+
35
+ Review this SwiftUI ViewModel: [paste code]
36
+ ```
37
+
38
+ ## Composable With
39
+
40
+ - [`agents/ios/swift-reviewer`](../../agents/ios/swift-reviewer/) — full structured review
41
+ - [`agents/ios/crash-analyzer`](../../agents/ios/crash-analyzer/) — crash debugging
42
+
43
+ ## Contributing
44
+
45
+ New skills for iOS should cover one concern. Ideas: `combine-patterns.md`, `observable-macro.md` (iOS 17+), `swift-testing-advanced.md`. Copy `templates/skill-template.md`.
@@ -0,0 +1,151 @@
1
+ # Skill — iOS Data Persistence
2
+
3
+ **Platform:** iOS / macOS (Swift)
4
+ **Category:** Code Quality
5
+ **Composable With:** agents/ios/swift-reviewer/agent.md, skills/shared/security-scan.md
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ Reviews iOS data persistence code across Core Data, SwiftData, Keychain, and UserDefaults. Covers security, thread safety, migration strategy, and common pitfalls for each storage layer.
12
+
13
+ ## When to Use
14
+
15
+ - When reviewing any code that reads from or writes to local storage
16
+ - When adding a new persistence layer to an existing app
17
+ - As a pre-release check for apps handling sensitive user data
18
+
19
+ ---
20
+
21
+ ## Skill Prompt
22
+
23
+ ```
24
+ When reviewing iOS data persistence code, enforce these rules by storage layer:
25
+
26
+ USERDEFAULTS
27
+ - NEVER store: auth tokens, passwords, session IDs, credit card numbers, health data, or any PII.
28
+ UserDefaults is stored as a plaintext plist at /Library/Preferences — readable with device access.
29
+ Fix: use Keychain for any sensitive value.
30
+ - @AppStorage triggers a view body re-evaluation on every write. Flag @AppStorage for large or
31
+ frequently-written values — use a ViewModel with debounced persistence instead.
32
+ - Flag: UserDefaults.standard.synchronize() — deprecated, a no-op in modern iOS, and a sign
33
+ of cargo-cult code from older patterns.
34
+ - Appropriate uses: user preferences (theme, language), feature flag overrides, non-sensitive app state.
35
+
36
+ KEYCHAIN
37
+ - Use Security framework (SecItemAdd, SecItemCopyMatching) or a wrapper library (KeychainAccess).
38
+ - Set kSecAttrAccessible to kSecAttrAccessibleWhenUnlockedThisDeviceOnly for most cases.
39
+ Avoid kSecAttrAccessibleAlways — it persists across unlocks and is available in background.
40
+ - Flag: Keychain items shared across apps without explicit kSecAttrAccessGroup — unintended access.
41
+ - For biometric-protected items: use kSecAccessControlBiometryCurrentSet, not BiometryAny —
42
+ BiometryCurrentSet invalidates the item if a new fingerprint is enrolled (more secure).
43
+ - Always handle errSecItemNotFound gracefully — don't crash if a Keychain item doesn't exist yet.
44
+
45
+ CORE DATA
46
+ - Context threading: NEVER access a managed object from a different context's thread.
47
+ viewContext is main-thread only. For background work:
48
+ container.performBackgroundTask { backgroundContext in ... }
49
+ Or: NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
50
+ - Flag: NSManagedObject passed between threads without objectID re-fetch.
51
+ Fix: pass objectID across threads, then: let obj = backgroundContext.object(with: objectID)
52
+ - Flag: fetchRequest without a sortDescriptor — results are non-deterministic across saves.
53
+ - Flag: fetchRequest with no predicate fetching a large table — use predicates and fetchLimit.
54
+ - Migration: always add a NSMersistentStoreDescription with shouldMigrateStoreAutomatically = true
55
+ and a lightweight migration model when adding/removing attributes.
56
+ - Cascade delete rules: set delete rules on relationships to prevent orphaned objects.
57
+
58
+ SWIFTDATA (iOS 17+)
59
+ - @Model classes must be used with a ModelContainer — never create @Model instances outside a context.
60
+ - Queries (@Query) run on the main actor by default. For background inserts, use ModelActor:
61
+ @ModelActor actor DataImporter { func insert(_ items: [Item]) throws { ... } }
62
+ - Flag: @Model classes with large Data properties (images, files) — store file URLs, not raw data.
63
+ - Migration: use VersionedSchema and SchemaMigrationPlan for structural changes.
64
+
65
+ FILES
66
+ - Sensitive files must be stored in the Application Support directory with NSFileProtectionComplete:
67
+ try data.write(to: url, options: .completeFileProtection)
68
+ This encrypts the file when the device is locked.
69
+ - Flag: files written to the Documents directory without file protection — accessible via iTunes
70
+ sharing and potentially unencrypted on non-passcode devices.
71
+ - Flag: large files written synchronously on the main thread.
72
+ Fix: use a background Task or DispatchQueue.global(qos: .utility).
73
+
74
+ For each issue: state the storage layer, file/line, what is wrong, the security or correctness impact, and the corrected Swift snippet.
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Example
80
+
81
+ **Input:**
82
+ ```swift
83
+ func saveToken(_ token: String) {
84
+ UserDefaults.standard.set(token, forKey: "authToken")
85
+ UserDefaults.standard.synchronize()
86
+ }
87
+
88
+ func loadToken() -> String? {
89
+ UserDefaults.standard.string(forKey: "authToken")
90
+ }
91
+ ```
92
+
93
+ **Output:**
94
+ ```
95
+ CRITICAL — Auth token stored in UserDefaults (plaintext)
96
+ Impact: The token is stored at ~/Library/Preferences/<BundleID>.plist.
97
+ Any app with device access, jailbreak, or iTunes backup can read it.
98
+ Fix:
99
+ import Security
100
+
101
+ enum KeychainKey { static let authToken = "com.example.authToken" }
102
+
103
+ func saveToken(_ token: String) throws {
104
+ let data = Data(token.utf8)
105
+ let query: [CFString: Any] = [
106
+ kSecClass: kSecClassGenericPassword,
107
+ kSecAttrAccount: KeychainKey.authToken,
108
+ kSecValueData: data,
109
+ kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
110
+ ]
111
+ SecItemDelete(query as CFDictionary) // delete existing before adding
112
+ let status = SecItemAdd(query as CFDictionary, nil)
113
+ guard status == errSecSuccess else { throw KeychainError.saveFailed(status) }
114
+ }
115
+
116
+ func loadToken() throws -> String? {
117
+ let query: [CFString: Any] = [
118
+ kSecClass: kSecClassGenericPassword,
119
+ kSecAttrAccount: KeychainKey.authToken,
120
+ kSecReturnData: true,
121
+ kSecMatchLimit: kSecMatchLimitOne
122
+ ]
123
+ var result: AnyObject?
124
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
125
+ guard status == errSecSuccess, let data = result as? Data else {
126
+ return status == errSecItemNotFound ? nil : { throw KeychainError.loadFailed(status) }()
127
+ }
128
+ return String(data: data, encoding: .utf8)
129
+ }
130
+
131
+ WARNING — UserDefaults.synchronize() is a deprecated no-op
132
+ Remove it. UserDefaults persists automatically.
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Composition Example
138
+
139
+ ```
140
+ Combine with skills/shared/security-scan.md for a full data security audit:
141
+ [paste data-persistence skill] + [paste security-scan skill] → paste your persistence layer.
142
+
143
+ Or compose with skills/ios/networking.md when reviewing token storage after login flows.
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Notes
149
+
150
+ - SwiftData is the recommended persistence layer for new iOS 17+ apps. For existing Core Data apps, migration is optional but worthwhile for new features.
151
+ - KeychainAccess (open-source library) wraps the Security framework in a clean API — recommended for readability over raw SecItem calls.
@@ -0,0 +1,90 @@
1
+ # Skill — /ios-tdd
2
+
3
+ **Platform:** iOS (Swift)
4
+ **Slash Command:** `/ios-tdd`
5
+ **Composable With:** agents/ios/swift/agent.md
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ Runs the red-green-refactor TDD loop for iOS — XCTest unit tests for Swift logic and XCUITest for SwiftUI component interaction. Generates the failing test first, then the minimal implementation.
12
+
13
+ ---
14
+
15
+ ## Skill Prompt
16
+
17
+ ```
18
+ Run the iOS TDD loop for the described feature or function:
19
+
20
+ STEP 1 — RED: Write a failing XCTest.
21
+ - For business logic: XCTestCase with setUp/tearDown, XCTAssert* assertions.
22
+ - For async code: use async/await test methods with `await` and XCTAssertEqual.
23
+ - For ViewModel: mock dependencies via protocol injection.
24
+ - For SwiftUI: use ViewInspector or XCUITest depending on complexity.
25
+ - Test must fail for the right reason (assertion failure, not compile error).
26
+
27
+ STEP 2 — GREEN: Write the minimal Swift implementation to make the test pass.
28
+ - No over-engineering. No features not tested yet.
29
+
30
+ STEP 3 — REFACTOR: Clean up. Extract protocols, add computed properties, improve naming.
31
+
32
+ Format:
33
+ --- RED ---
34
+ <XCTest code>
35
+
36
+ --- GREEN ---
37
+ <minimal Swift implementation>
38
+
39
+ --- REFACTOR ---
40
+ <final Swift implementation>
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Example
46
+
47
+ **Input:** "TDD a PriceFormatter that formats Double as currency string '£12.50'"
48
+
49
+ **Output:**
50
+ ```swift
51
+ --- RED ---
52
+ class PriceFormatterTests: XCTestCase {
53
+ var sut: PriceFormatter!
54
+ override func setUp() { sut = PriceFormatter(currencyCode: "GBP") }
55
+
56
+ func test_format_producesCorrectCurrencyString() {
57
+ XCTAssertEqual(sut.format(12.5), "£12.50")
58
+ }
59
+
60
+ func test_format_handlesZero() {
61
+ XCTAssertEqual(sut.format(0), "£0.00")
62
+ }
63
+ }
64
+
65
+ --- GREEN ---
66
+ struct PriceFormatter {
67
+ let currencyCode: String
68
+ func format(_ amount: Double) -> String {
69
+ let formatter = NumberFormatter()
70
+ formatter.numberStyle = .currency
71
+ formatter.currencyCode = currencyCode
72
+ return formatter.string(from: NSNumber(value: amount)) ?? "\(amount)"
73
+ }
74
+ }
75
+
76
+ --- REFACTOR ---
77
+ struct PriceFormatter {
78
+ private let formatter: NumberFormatter
79
+
80
+ init(currencyCode: String) {
81
+ formatter = NumberFormatter()
82
+ formatter.numberStyle = .currency
83
+ formatter.currencyCode = currencyCode
84
+ }
85
+
86
+ func format(_ amount: Double) -> String {
87
+ formatter.string(from: NSNumber(value: amount)) ?? "—"
88
+ }
89
+ }
90
+ ```
@@ -0,0 +1,139 @@
1
+ # Skill — iOS Networking
2
+
3
+ **Platform:** iOS / macOS (Swift)
4
+ **Category:** Code Quality
5
+ **Composable With:** agents/ios/swift-reviewer/agent.md, skills/shared/security-scan.md
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ Reviews iOS networking code for correctness, safety, and idiomatic Swift patterns. Covers URLSession, async/await, error handling, response decoding, certificate pinning, and cancellation.
12
+
13
+ ## When to Use
14
+
15
+ - Reviewing any class that makes HTTP requests
16
+ - When migrating Alamofire or Combine networking to async/await
17
+ - As a pre-code-review checklist for API client classes
18
+
19
+ ---
20
+
21
+ ## Skill Prompt
22
+
23
+ ```
24
+ When reviewing iOS Swift networking code, enforce these rules:
25
+
26
+ ASYNC/AWAIT PATTERNS
27
+ - Prefer URLSession.shared.data(from:) async throws over dataTask completion handlers in new code.
28
+ - Flag completion handler callbacks that aren't marked @escaping when they outlive the call site.
29
+ - For cancellable requests, store a Task handle as a property and cancel in deinit or onDisappear:
30
+ private var fetchTask: Task<Void, Never>?
31
+ func load() { fetchTask?.cancel(); fetchTask = Task { try? await fetch() } }
32
+ - Do not use URLSession in a struct that can be copied — retain the Task handle in a class or actor.
33
+
34
+ ERROR HANDLING
35
+ - Network errors should be modelled as a typed enum, not raw Error or String:
36
+ enum NetworkError: Error { case invalidResponse(Int), decodingFailed(Error), noConnection }
37
+ - Flag catch { } with no body — silently swallowed network errors mask bugs.
38
+ - Flag `try?` on network calls — it silently discards all error information. Use `do/catch`.
39
+ - HTTP status codes must be checked: a 404 or 500 returns a URLResponse, not an error.
40
+ guard let httpResponse = response as? HTTPURLResponse,
41
+ (200..<300).contains(httpResponse.statusCode) else { throw NetworkError.invalidResponse(code) }
42
+
43
+ DECODING
44
+ - Always decode on a background thread, not MainActor, then publish the result on Main:
45
+ let decoded = try JSONDecoder().decode(T.self, from: data) // runs on caller's context
46
+ await MainActor.run { self.result = decoded }
47
+ - Use codingKeys or a custom dateDecodingStrategy — never rely on default ISO8601 parsing
48
+ without explicitly setting it: decoder.dateDecodingStrategy = .iso8601
49
+ - Flag forced try! on JSONDecoder().decode() — server responses can always be malformed.
50
+
51
+ SECURITY
52
+ - Flag URLSession configurations with URLSessionConfiguration that disables TLS validation:
53
+ session.delegate implementing didReceive challenge without real certificate evaluation → MitM risk.
54
+ - Flag http:// (cleartext) base URLs — use https:// exclusively.
55
+ - For sensitive endpoints (auth, payment), certificate pinning must be implemented in
56
+ URLSession.urlSession(_:didReceive:completionHandler:).
57
+
58
+ THREADING
59
+ - URLSession completion handlers run on an arbitrary background queue by default.
60
+ Any UI update after a completion handler must be dispatched to the main queue.
61
+ With async/await + @MainActor, this is handled automatically — prefer it.
62
+ - Flag URLSession.shared used in extensions or when a custom configuration is needed
63
+ (timeouts, caching policy, background transfers) — create a dedicated session instead.
64
+
65
+ MEMORY
66
+ - URLSession holds a strong reference to its delegate. The delegate must NOT hold a strong
67
+ reference back to the session object — this creates a retain cycle.
68
+ Fix: make the delegate weak (if possible) or use URLSession(configuration:) without a delegate
69
+ and use async/await instead.
70
+
71
+ For each issue: state the file/line, what is wrong, the risk, and the corrected Swift snippet.
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Example
77
+
78
+ **Input:**
79
+ ```swift
80
+ func fetchUser(id: String) {
81
+ URLSession.shared.dataTask(with: URL(string: "http://api.example.com/users/\(id)")!) { data, _, _ in
82
+ if let data = data {
83
+ self.user = try! JSONDecoder().decode(User.self, from: data)
84
+ }
85
+ }.resume()
86
+ }
87
+ ```
88
+
89
+ **Output:**
90
+ ```
91
+ Line 2 — Cleartext HTTP URL
92
+ Risk: All traffic is intercepted in plaintext. Use https://.
93
+
94
+ Line 2 — Force unwrap on URL(string:)
95
+ Risk: If the URL string is ever malformed (bad id encoding), the app crashes.
96
+ Fix: guard let url = URL(string: "https://api.example.com/users/\(id)") else { return }
97
+
98
+ Line 3 — No HTTP status code check
99
+ Risk: A 404 or 500 response will appear as "success" — the body may not be a User.
100
+
101
+ Line 3 — Strong self capture in completion handler (retain cycle risk)
102
+ Risk: If self is deallocated before the network call completes, accessing self.user crashes (if unowned) or leaks (if strong).
103
+ Fix: [weak self] guard let self else { return }
104
+
105
+ Line 4 — try! on JSONDecoder
106
+ Risk: If the server returns an error body or schema changes, the app crashes.
107
+ Fix: do { self.user = try JSONDecoder().decode(User.self, from: data) } catch { /* handle */ }
108
+
109
+ Line 3 — UI mutation on background queue
110
+ Risk: self.user likely drives the UI. Mutating it off the main thread is a data race.
111
+ Fix: DispatchQueue.main.async { self.user = decoded }
112
+
113
+ Idiomatic async/await replacement:
114
+ @MainActor
115
+ func fetchUser(id: String) async throws {
116
+ guard let url = URL(string: "https://api.example.com/users/\(id)") else { return }
117
+ let (data, response) = try await URLSession.shared.data(from: url)
118
+ guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
119
+ throw NetworkError.invalidResponse((response as? HTTPURLResponse)?.statusCode ?? 0)
120
+ }
121
+ user = try JSONDecoder().decode(User.self, from: data)
122
+ }
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Composition Example
128
+
129
+ ```
130
+ Combine with skills/shared/security-scan.md for a full networking security audit:
131
+ [paste networking skill] + [paste security-scan skill] → paste your API client class.
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Notes
137
+
138
+ - Alamofire adds its own validation layer (`validate()`) — when reviewing Alamofire code, check that `.validate()` is called on every request chain.
139
+ - Background URLSession (for large uploads/downloads) requires a separate `URLSessionConfiguration.background(withIdentifier:)` — document this if used.
@@ -0,0 +1,122 @@
1
+ # Skill — iOS Performance
2
+
3
+ **Platform:** iOS / macOS (Swift / SwiftUI / UIKit)
4
+ **Category:** Performance & Optimization
5
+ **Composable With:** agents/ios/swift-reviewer/agent.md, skills/ios/swiftui-state.md
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ Reviews iOS code for common performance pitfalls: main thread blocking, expensive view body, Core Data N+1 queries, image loading overhead, and background task misuse. Use as a pre-profiling code review before reaching for Instruments.
12
+
13
+ ## When to Use
14
+
15
+ - When a screen feels laggy or slow to appear
16
+ - Before profiling with Instruments (eliminate obvious issues first)
17
+ - When reviewing any code that touches Core Data, images, or the main thread
18
+
19
+ ---
20
+
21
+ ## Skill Prompt
22
+
23
+ ```
24
+ When reviewing iOS Swift code for performance, check for:
25
+
26
+ MAIN THREAD VIOLATIONS
27
+ - Disk I/O (FileManager, UserDefaults, CoreData fetch without async) on the main thread blocks
28
+ the UI thread. Any operation that could take >1ms must be on a background actor or DispatchQueue.global().
29
+ - URLSession.shared.dataTask result handling that mutates UI without dispatching to main.
30
+ - JSONDecoder().decode() on large payloads on the main thread — move to a Task { } or background queue.
31
+ - Flag: try FileManager.default.contentsOfDirectory(...) called directly in viewDidLoad or body.
32
+
33
+ SWIFTUI VIEW BODY
34
+ - body is called frequently. It must be a pure, cheap computation.
35
+ - Flag: DateFormatter() or NumberFormatter() instantiated inside body — these are expensive to create.
36
+ Fix: store as @State private var formatter = ... or as a static property.
37
+ - Flag: sorting, filtering, or mapping large arrays inside body.
38
+ Fix: use .onChange(of:) to recompute and store in @State, or use a ViewModel computed property with caching.
39
+ - Flag: body reading from UserDefaults directly — triggers a body re-evaluation on every read
40
+ if wrapped in @AppStorage, but UserDefaults is also synchronous disk access.
41
+
42
+ CORE DATA / SWIFTDATA
43
+ - N+1 query: fetching a list of objects, then accessing a relationship on each object in a loop
44
+ without prefetching. Each relationship access fires a new fetch.
45
+ Fix: set fetchRequest.relationshipKeyPathsForPrefetching = ["items"] to batch-load relationships.
46
+ - Fetching on the main context (viewContext) — heavy fetches block the UI.
47
+ Fix: use NSManagedObjectContext.perform { } or fetch on a background context with
48
+ PersistenceController.shared.container.performBackgroundTask { context in ... }
49
+ - Saving on every change instead of batching — each save triggers a write to disk.
50
+ Fix: batch saves with a debounced save call or save on significant lifecycle events only.
51
+
52
+ IMAGE LOADING
53
+ - Loading UIImage(named:) for large images synchronously on the main thread blocks rendering.
54
+ Fix: use async image loading (AsyncImage in SwiftUI, or a library like Kingfisher/Nuke).
55
+ - Decoding full-resolution images for thumbnail display — decoding a 4K image to show in a 80×80 cell wastes memory and CPU.
56
+ Fix: use ImageIO to decode at target size:
57
+ let options: [CFString: Any] = [kCGImageSourceThumbnailMaxPixelSize: 80,
58
+ kCGImageSourceCreateThumbnailFromImageAlways: true]
59
+ - Not reusing images — storing UIImage objects without a cache in a list causes repeated disk reads.
60
+
61
+ BACKGROUND TASKS
62
+ - BGTaskScheduler tasks that do work on the main thread inside the task handler.
63
+ Fix: dispatch all work to a background thread inside the handler; call setTaskCompleted() in ALL code paths.
64
+ - Long-running background tasks (>30 seconds) that don't call setTaskCompleted() fast enough — watchdog kills the app (0x8badf00d).
65
+ - Doing CoreLocation, HealthKit, or CloudKit fetches in a UIBackgroundTask without proper expiration handling.
66
+
67
+ COLLECTION VIEW / TABLE VIEW
68
+ - dequeueReusableCell not called — creating new cells on every cellForRow is O(n) allocation per scroll event.
69
+ - Synchronous image loading inside cellForRow/itemForIndexPath — each scroll event blocks the main thread.
70
+ Fix: load images asynchronously and cancel on cell reuse (store the task in prepareForReuse).
71
+ - Recalculating cell heights in heightForRowAt with expensive layout passes — cache heights.
72
+
73
+ For each issue: state the file/line, the performance impact (what it blocks or how often it runs), and the corrected code.
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Example
79
+
80
+ **Input:**
81
+ ```swift
82
+ struct PostListView: View {
83
+ let posts: [Post]
84
+
85
+ var body: some View {
86
+ let sorted = posts.sorted { $0.date > $1.date } // every rebuild
87
+ List(sorted) { post in
88
+ Text(DateFormatter().string(from: post.date)) // new formatter per cell
89
+ }
90
+ }
91
+ }
92
+ ```
93
+
94
+ **Output:**
95
+ ```
96
+ body — sorted array computed on every rebuild
97
+ Impact: .sorted is O(n log n). With 500 posts, this runs on every body call triggered
98
+ by any parent state change, causing visible lag during scroll.
99
+ Fix:
100
+ // In ViewModel or parent:
101
+ @State private var sortedPosts = posts.sorted { $0.date > $1.date }
102
+ // Update only when posts changes with .onChange(of: posts) { sortedPosts = ... }
103
+
104
+ body — DateFormatter instantiated per cell per rebuild
105
+ Impact: DateFormatter initialization is expensive (locale setup, calendar, timezone).
106
+ With 20 visible cells and frequent rebuilds, this creates hundreds of objects per second.
107
+ Fix:
108
+ private static let dateFormatter: DateFormatter = {
109
+ let f = DateFormatter()
110
+ f.dateStyle = .medium
111
+ return f
112
+ }()
113
+ // Or use .formatted() with a FormatStyle (no allocation):
114
+ Text(post.date.formatted(date: .abbreviated, time: .omitted))
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Notes
120
+
121
+ - Instruments > Time Profiler is the ground truth for main thread blocking — use this skill for pre-profiling review, not as a replacement for measurement.
122
+ - SwiftUI's view body performance has improved significantly in iOS 17 with the `@Observable` macro — less state change propagation overhead compared to `ObservableObject`.
@@ -0,0 +1,57 @@
1
+ # Skill — /swift-concurrency
2
+
3
+ **Platform:** iOS (Swift)
4
+ **Slash Command:** `/swift-concurrency`
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ Safely converts completion handler callbacks to Swift async/await. Handles nested callbacks, error mapping, DispatchQueue patterns, and Combine pipelines.
11
+
12
+ ---
13
+
14
+ ## Skill Prompt
15
+
16
+ ```
17
+ Convert the provided Swift completion handler code to async/await safely:
18
+
19
+ CONVERSION PATTERNS:
20
+ 1. Simple callback → async function:
21
+ func fetchUser(id: String, completion: @escaping (User?, Error?) -> Void)
22
+ → func fetchUser(id: String) async throws -> User
23
+
24
+ 2. Result-based callback → async throws:
25
+ func loadData(completion: @escaping (Result<Data, Error>) -> Void)
26
+ → func loadData() async throws -> Data
27
+
28
+ 3. Nested callbacks → sequential await:
29
+ Flatten callback pyramids into sequential async calls.
30
+
31
+ 4. DispatchQueue.main.async callback → @MainActor:
32
+ func updateUI() { DispatchQueue.main.async { self.label.text = "done" } }
33
+ → @MainActor func updateUI() { label.text = "done" }
34
+
35
+ 5. Withstand continuations for legacy APIs:
36
+ Use withCheckedThrowingContinuation for APIs you can't change:
37
+ func legacyFetch() async throws -> Data {
38
+ try await withCheckedThrowingContinuation { continuation in
39
+ legacyAPI.fetch { data, error in
40
+ if let error { continuation.resume(throwing: error) }
41
+ else { continuation.resume(returning: data!) }
42
+ }
43
+ }
44
+ }
45
+
46
+ 6. Combine → AsyncStream / async sequence:
47
+ publisher.values async sequence in for await loops.
48
+
49
+ SAFETY CHECKLIST:
50
+ - [ ] continuation.resume called exactly once (not zero, not twice)
51
+ - [ ] No @escaping closures capturing self strongly after async conversion
52
+ - [ ] @MainActor added to functions that update UI
53
+ - [ ] Task { } used only when fire-and-forget is intentional; add error handling
54
+
55
+ Show before and after for each function converted. Flag any pattern that cannot be
56
+ safely converted without changing the calling contract.
57
+ ```