@mmerterden/multi-agent-pipeline 20.6.0 → 20.8.0

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 (326) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +328 -329
  5. package/package.json +2 -2
  6. package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
  7. package/pipeline/scripts/_notices.mjs +12 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +1 -1
  9. package/pipeline/skills/.skill-manifest.json +106 -106
  10. package/pipeline/skills/shared/README.md +71 -71
  11. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
  12. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  13. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  14. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  15. package/pipeline/skills/shared/external/android-architecture/SKILL.md +2 -0
  16. package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
  17. package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
  18. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  19. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  20. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  21. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  22. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  23. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  24. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  25. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  26. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  27. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  28. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  29. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  30. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  31. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  32. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  33. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  34. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  35. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  37. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  38. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  39. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  40. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  41. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  42. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  43. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  44. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  45. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  46. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  47. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  48. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  49. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  50. package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
  51. package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
  52. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  53. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  54. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  55. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
  56. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  57. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  58. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  59. package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
  60. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
  61. package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
  62. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  63. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  64. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  65. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  66. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  67. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  68. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  69. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  70. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  71. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  72. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  73. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  74. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  75. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  76. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  77. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  78. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  79. package/pipeline/skills/shared/external/council/SKILL.md +1 -0
  80. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  81. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  82. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  83. package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
  84. package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
  85. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  86. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  87. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  88. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  89. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  90. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  91. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  92. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  93. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  94. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  95. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  96. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  97. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  98. package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
  99. package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
  100. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
  101. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  102. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  103. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  104. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  105. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  106. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  107. package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
  108. package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
  109. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  110. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  111. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  112. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  113. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  114. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
  115. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  116. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  117. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  118. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  119. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
  120. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  121. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  122. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  123. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  124. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  125. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  126. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  127. package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
  128. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  129. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  130. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  131. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  132. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  133. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  134. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
  135. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
  136. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
  137. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
  138. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
  139. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
  140. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
  141. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
  142. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
  143. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
  144. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
  145. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
  146. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
  147. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
  148. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
  149. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
  150. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  151. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  152. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  153. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  154. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  155. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  156. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  157. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  158. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  159. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  160. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  161. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  162. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  163. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
  164. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  165. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  166. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  167. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  168. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  169. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  170. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  171. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  172. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  173. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  174. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  175. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  176. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  177. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  178. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  179. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  180. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  181. package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
  182. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  183. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  184. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  185. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  186. package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
  187. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
  188. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  189. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  190. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  191. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
  192. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
  193. package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
  194. package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
  195. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  196. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  197. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  198. package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
  199. package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
  200. package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
  201. package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
  202. package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
  203. package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
  204. package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
  205. package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
  206. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  207. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  208. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  209. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  210. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  211. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  212. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  213. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  214. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  215. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  216. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  217. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  218. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  219. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  220. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  221. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  222. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  223. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  224. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  225. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  226. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  227. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  228. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  229. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  230. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  231. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  232. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  233. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  234. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  235. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  236. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  237. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  238. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  239. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  240. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  241. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  242. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  243. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  244. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  245. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  246. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  247. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  248. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  249. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  250. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  251. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  252. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  253. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  254. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  255. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  256. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  257. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  258. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  259. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  260. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  261. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  262. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  263. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  264. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  265. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  266. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  267. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  268. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  269. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  270. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  271. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  272. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  273. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  274. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  275. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  276. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  277. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  278. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  279. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  280. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  281. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  282. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  283. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  284. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  285. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  286. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  287. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  288. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  289. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  290. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  291. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  292. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  293. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  294. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  295. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  296. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  297. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  298. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  299. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  300. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  301. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  302. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  303. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  304. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  305. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  306. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  307. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  308. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  309. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  310. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
  311. package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
  312. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  313. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  314. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
  315. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  316. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  317. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  318. package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
  319. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  320. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  321. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
  322. package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
  323. package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
  324. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  325. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  326. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,500 +1,411 @@
1
1
  ---
2
2
  name: cloudkit-sync
3
- description: "Implement, review, or improve CloudKit and iCloud sync in iOS/macOS apps. Use when working with CKContainer, CKRecord, CKQuery, CKSubscription, CKSyncEngine, CKShare, NSUbiquitousKeyValueStore, or iCloud Drive file coordination; when syncing SwiftData models via ModelConfiguration with cloudKitDatabase; when handling CKError codes for conflict resolution, network failures, or quota limits; or when checking iCloud account status before performing sync operations."
3
+ description: "CloudKit and iCloud sync on iOS and macOS: CKContainer public, private and shared databases, CKRecord, CKQuery, CKSubscription, CKSyncEngine, CKShare, NSUbiquitousKeyValueStore, iCloud Drive file coordination, SwiftData sync via ModelConfiguration cloudKitDatabase, CKError handling for conflicts, network failures and quota, iCloud account status checks. Use when implementing, reviewing or improving how an app stores or shares user data through iCloud."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
- # CloudKit
8
-
9
- Sync data across devices using CloudKit, iCloud key-value storage, and iCloud
10
- Drive. Covers container setup, record CRUD, queries, subscriptions, CKSyncEngine,
11
- SwiftData integration, conflict resolution, and error handling. Targets iOS 26+
12
- with Swift 6.3; older availability noted where relevant.
13
-
14
- ## Contents
15
-
16
- - [Container and Database Setup](#container-and-database-setup)
17
- - [CKRecord CRUD](#ckrecord-crud)
18
- - [CKQuery](#ckquery)
19
- - [CKSubscription](#cksubscription)
20
- - [CKSyncEngine (iOS 17+)](#cksyncengine-ios-17)
21
- - [SwiftData + CloudKit](#swiftdata--cloudkit)
22
- - [NSUbiquitousKeyValueStore](#nsubiquitouskeyvaluestore)
23
- - [iCloud Drive File Sync](#icloud-drive-file-sync)
24
- - [Account Status and Error Handling](#account-status-and-error-handling)
25
- - [Conflict Resolution](#conflict-resolution)
26
- - [Common Mistakes](#common-mistakes)
27
- - [Review Checklist](#review-checklist)
28
- - [References](#references)
29
-
30
- ## Container and Database Setup
31
-
32
- Enable iCloud + CloudKit in Signing & Capabilities. A container provides three databases:
33
-
34
- | Database | Scope | Requires iCloud | Storage Quota |
35
- |----------|-------|-----------------|---------------|
36
- | Public | All users | Read: No, Write: Yes | App quota |
37
- | Private | Current user | Yes | User quota |
38
- | Shared | Shared records | Yes | Owner quota |
7
+
8
+ # CloudKit Sync
9
+
10
+ Everything iCloud offers for app data: CloudKit databases, the key-value
11
+ store, and iCloud Drive documents. This file covers containers, record
12
+ operations, queries, subscriptions, `CKSyncEngine`, SwiftData integration,
13
+ error handling and conflict merges for Swift 6.3 on iOS 26, with older
14
+ availability noted where it matters.
15
+
16
+ Incremental fetch operations, change tokens, sharing, zones, assets, batching,
17
+ quality of service, encrypted fields and the Dashboard are in the
18
+ [CloudKit patterns reference](references/cloudkit-patterns.md). Core Data
19
+ stacks that sync through `NSPersistentCloudKitContainer` start in `core-data`;
20
+ background push handling in general is `push-notifications`.
21
+
22
+ ## Containers and databases
23
+
24
+ Enable **iCloud** with the **CloudKit** service under Signing & Capabilities.
25
+
26
+ | Database | Who sees it | Needs an iCloud account | Storage charged to |
27
+ |---|---|---|---|
28
+ | Public | Every user of the app | Only for writes; reads work signed out | The app |
29
+ | Private | The signed-in user | Yes | The user |
30
+ | Shared | Records others shared with the user | Yes | The owner |
39
31
 
40
32
  ```swift
41
33
  import CloudKit
42
34
 
43
- let container = CKContainer.default()
44
- // Or named: CKContainer(identifier: "iCloud.com.example.app")
45
-
46
- let publicDB = container.publicCloudDatabase
35
+ let container = CKContainer(identifier: "iCloud.app.gardenlog") // or CKContainer.default()
36
+ let publicDB = container.publicCloudDatabase
47
37
  let privateDB = container.privateCloudDatabase
48
- let sharedDB = container.sharedCloudDatabase
38
+ let sharedDB = container.sharedCloudDatabase
49
39
  ```
50
40
 
51
- ## CKRecord CRUD
41
+ ## Records
52
42
 
53
- Records are key-value pairs. Max 1 MB per record (excluding CKAsset data).
43
+ A `CKRecord` is a typed dictionary. One record can hold up to 1 MB, not
44
+ counting the files behind any `CKAsset` fields.
54
45
 
55
46
  ```swift
56
- // CREATE
57
- let record = CKRecord(recordType: "Note")
58
- record["title"] = "Meeting Notes" as CKRecordValue
59
- record["body"] = "Discussed Q3 roadmap" as CKRecordValue
60
- record["createdAt"] = Date() as CKRecordValue
61
- record["tags"] = ["work", "planning"] as CKRecordValue
62
- let saved = try await privateDB.save(record)
63
-
64
- // FETCH by ID
65
- let recordID = CKRecord.ID(recordName: "unique-id-123")
66
- let fetched = try await privateDB.record(for: recordID)
67
-
68
- // UPDATE -- fetch first, modify, then save
69
- fetched["title"] = "Updated Title" as CKRecordValue
70
- let updated = try await privateDB.save(fetched)
71
-
72
- // DELETE
73
- try await privateDB.deleteRecord(withID: recordID)
47
+ func plant(named name: String, in db: CKDatabase) async throws -> CKRecord {
48
+ let plant = CKRecord(recordType: "Plant")
49
+ plant["name"] = name as CKRecordValue
50
+ plant["plantedOn"] = Date.now as CKRecordValue
51
+ plant["tags"] = ["herb", "sunny"] as CKRecordValue
52
+ return try await db.save(plant)
53
+ }
54
+
55
+ func rename(_ recordName: String, to newName: String, in db: CKDatabase) async throws {
56
+ let id = CKRecord.ID(recordName: recordName)
57
+ let existing = try await db.record(for: id) // always fetch before editing
58
+ existing["name"] = newName as CKRecordValue
59
+ _ = try await db.save(existing)
60
+ }
61
+
62
+ func uproot(_ recordName: String, in db: CKDatabase) async throws {
63
+ _ = try await db.deleteRecord(withID: CKRecord.ID(recordName: recordName))
64
+ }
74
65
  ```
75
66
 
76
- ### Custom Record Zones
67
+ ### Custom zones
77
68
 
78
- Apps create custom zones in the private database. Shared databases expose zones
79
- that other users share with the current user. Custom zones support atomic
80
- commits, change tracking, and sharing; public databases do not support custom
69
+ The private database accepts zones the app creates; the shared database
70
+ exposes the zones other people shared with the user. Custom zones unlock
71
+ atomic commits, change tracking and sharing. The public database has no custom
81
72
  zones.
82
73
 
83
74
  ```swift
84
- let zoneID = CKRecordZone.ID(zoneName: "NotesZone")
85
- let zone = CKRecordZone(zoneID: zoneID)
86
- try await privateDB.save(zone)
75
+ let beds = CKRecordZone.ID(zoneName: "GardenBeds")
76
+ _ = try await privateDB.save(CKRecordZone(zoneID: beds))
87
77
 
88
- let recordID = CKRecord.ID(recordName: UUID().uuidString, zoneID: zoneID)
89
- let record = CKRecord(recordType: "Note", recordID: recordID)
78
+ let bedID = CKRecord.ID(recordName: UUID().uuidString, zoneID: beds)
79
+ let bed = CKRecord(recordType: "Bed", recordID: bedID)
90
80
  ```
91
81
 
92
- ## CKQuery
93
-
94
- Query records with NSPredicate. Supported: `==`, `!=`, `<`, `>`, `<=`, `>=`,
95
- `BEGINSWITH`, `CONTAINS`, `IN`, `AND`, `NOT`, `BETWEEN`,
96
- `distanceToLocation:fromLocation:`.
82
+ ## Queries
97
83
 
98
- `CONTAINS` tests list membership except for tokenized full-text search with
99
- `self CONTAINS`. `BEGINSWITH` is the string-prefix operator; unsupported
100
- operators, key paths, or field types fail when the query executes.
101
- For every encryption review, explicitly call out field eligibility: encrypted
102
- values cannot be queried or sorted; `CKAsset` is encrypted by default; and
103
- `CKRecord.Reference` cannot be encrypted because CloudKit needs it server-side.
84
+ CloudKit accepts a subset of `NSPredicate`:
104
85
 
105
- ```swift
106
- let predicate = NSPredicate(format: "title BEGINSWITH %@", "Meeting")
107
- let query = CKQuery(recordType: "Note", predicate: predicate)
108
- query.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: false)]
109
-
110
- let (results, _) = try await privateDB.records(matching: query)
111
- for (_, result) in results {
112
- let record = try result.get()
113
- print(record["title"] as? String ?? "")
114
- }
86
+ - comparisons `==`, `!=`, `<`, `>`, `<=`, `>=`
87
+ - `BEGINSWITH` for string prefixes
88
+ - `CONTAINS` for membership in a list field, except `self CONTAINS`, which runs a tokenized full-text search
89
+ - `IN`, `AND`, `NOT`, `BETWEEN`
90
+ - `distanceToLocation:fromLocation:` for location fields
115
91
 
116
- // Fetch all records of a type
117
- let allQuery = CKQuery(recordType: "Note", predicate: NSPredicate(value: true))
92
+ An unsupported operator, key path or field type is only rejected when the
93
+ query executes, not when you build it.
118
94
 
119
- // Full-text search across string fields
120
- let searchQuery = CKQuery(
121
- recordType: "Note",
122
- predicate: NSPredicate(format: "self CONTAINS %@", "roadmap")
123
- )
95
+ Encrypted values cannot be queried or sorted. `CKAsset` contents are already
96
+ encrypted, and a `CKRecord.Reference` can never be encrypted because the
97
+ server needs it. Say all three whenever you review encryption.
124
98
 
125
- // Compound predicate
126
- let compound = NSCompoundPredicate(andPredicateWithSubpredicates: [
127
- NSPredicate(format: "createdAt > %@", cutoffDate as NSDate),
128
- NSPredicate(format: "tags CONTAINS %@", "work")
129
- ])
99
+ ```swift
100
+ func recentHerbs(in db: CKDatabase) async throws -> [CKRecord] {
101
+ let weekAgo = Date.now.addingTimeInterval(-7 * 86_400)
102
+ let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
103
+ NSPredicate(format: "plantedOn > %@", weekAgo as NSDate),
104
+ NSPredicate(format: "tags CONTAINS %@", "herb"),
105
+ ])
106
+ let query = CKQuery(recordType: "Plant", predicate: predicate)
107
+ query.sortDescriptors = [NSSortDescriptor(key: "plantedOn", ascending: false)]
108
+
109
+ let (matches, _) = try await db.records(matching: query)
110
+ return matches.compactMap { try? $0.1.get() }
111
+ }
130
112
  ```
131
113
 
132
- ## CKSubscription
114
+ Use `NSPredicate(value: true)` to match every record of a type.
133
115
 
134
- Subscriptions trigger push notifications when records change server-side.
135
- CloudKit/Xcode handles the APNs entitlement when CloudKit is enabled; no
136
- separate explicit App ID push setup is needed. Silent/background processing
137
- still needs Background Modes > Remote notifications.
116
+ ## Subscriptions
117
+
118
+ A subscription makes CloudKit push a notification when server data changes.
119
+ With CloudKit enabled, Xcode manages the push entitlement; no separate App ID
120
+ push setup is needed. Silent pushes still require **Background Modes >
121
+ Remote notifications**.
138
122
 
139
123
  ```swift
140
- // Query subscription -- fires when matching records change
141
- let subscription = CKQuerySubscription(
142
- recordType: "Note",
143
- predicate: NSPredicate(format: "tags CONTAINS %@", "urgent"),
144
- subscriptionID: "urgent-notes",
124
+ let newPlants = CKQuerySubscription(
125
+ recordType: "Plant",
126
+ predicate: NSPredicate(value: true),
127
+ subscriptionID: "plant-changes",
145
128
  options: [.firesOnRecordCreation, .firesOnRecordUpdate]
146
129
  )
147
- let notifInfo = CKSubscription.NotificationInfo()
148
- notifInfo.shouldSendContentAvailable = true // silent push
149
- subscription.notificationInfo = notifInfo
150
- try await privateDB.save(subscription)
151
-
152
- // Database subscription -- fires on any database change
153
- let dbSub = CKDatabaseSubscription(subscriptionID: "private-db-changes")
154
- dbSub.notificationInfo = notifInfo
155
- try await privateDB.save(dbSub)
156
-
157
- // Record zone subscription -- fires on changes within a zone
158
- let zoneSub = CKRecordZoneSubscription(
159
- zoneID: CKRecordZone.ID(zoneName: "NotesZone"),
160
- subscriptionID: "notes-zone-changes"
161
- )
162
- zoneSub.notificationInfo = notifInfo
163
- try await privateDB.save(zoneSub)
164
- ```
130
+ let info = CKSubscription.NotificationInfo()
131
+ info.shouldSendContentAvailable = true // silent
132
+ newPlants.notificationInfo = info
133
+ _ = try await publicDB.save(newPlants)
165
134
 
166
- Handle in AppDelegate:
135
+ let everything = CKDatabaseSubscription(subscriptionID: "private-db") // any change in the DB
136
+ let oneZone = CKRecordZoneSubscription(zoneID: beds, subscriptionID: "beds") // changes in one zone
137
+ ```
167
138
 
168
139
  ```swift
169
- func application(
170
- _ application: UIApplication,
171
- didReceiveRemoteNotification userInfo: [AnyHashable: Any]
172
- ) async -> UIBackgroundFetchResult {
173
- let notification = CKNotification(fromRemoteNotificationDictionary: userInfo)
174
- guard notification?.subscriptionID == "private-db-changes" else { return .noData }
175
- // Fetch changes using CKSyncEngine or CKFetchRecordZoneChangesOperation
176
- return .newData
140
+ import UIKit
141
+
142
+ // UIApplicationDelegate
143
+ func application(_ application: UIApplication,
144
+ didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult {
145
+ guard let note = CKNotification(fromRemoteNotificationDictionary: userInfo),
146
+ note.subscriptionID == "private-db" else { return .noData }
147
+ do {
148
+ try await GardenSync.shared.pullNow() // CKSyncEngine or CKFetchRecordZoneChangesOperation
149
+ return .newData
150
+ } catch {
151
+ return .failed
152
+ }
177
153
  }
178
154
  ```
179
155
 
180
156
  ## CKSyncEngine (iOS 17+)
181
157
 
182
- `CKSyncEngine` is the recommended sync approach for custom model data. It
183
- handles scheduling, transient retries, change tokens, and database
184
- subscriptions, but not app-specific save failures: `CKError.serverRecordChanged`
185
- from `sentRecordZoneChanges.failedRecordSaves` still requires custom conflict
186
- resolution and rescheduling. Automatic sync timing is indeterminate. Requires
187
- CloudKit capability + Remote notifications; private/shared databases only.
158
+ For custom model data, `CKSyncEngine` is the recommended route. It schedules
159
+ work, retries transient failures, keeps change tokens and manages the
160
+ database subscription for you. It does **not** resolve app-level save
161
+ failures: a `CKError.serverRecordChanged` inside
162
+ `sentRecordZoneChanges.failedRecordSaves` needs your merge and a reschedule.
163
+ Its automatic timing is not predictable. It needs the CloudKit capability plus
164
+ Remote notifications, and works only with private and shared databases, never
165
+ public.
188
166
 
189
167
  ```swift
190
- import CloudKit
191
-
192
- final class SyncManager: CKSyncEngineDelegate {
193
- let syncEngine: CKSyncEngine
168
+ actor GardenSync: CKSyncEngineDelegate {
169
+ static let shared = GardenSync()
170
+ private var engine: CKSyncEngine?
194
171
 
195
- init(container: CKContainer = .default()) {
172
+ func start() {
173
+ guard engine == nil else { return }
196
174
  let config = CKSyncEngine.Configuration(
197
- database: container.privateCloudDatabase,
198
- stateSerialization: Self.loadState(),
175
+ database: CKContainer.default().privateCloudDatabase,
176
+ stateSerialization: StateFile.load(), // CKSyncEngine.State.Serialization?
199
177
  delegate: self
200
178
  )
201
- self.syncEngine = CKSyncEngine(config)
179
+ engine = CKSyncEngine(config)
202
180
  }
203
181
 
204
182
  func handleEvent(_ event: CKSyncEngine.Event, syncEngine: CKSyncEngine) async {
205
183
  switch event {
206
184
  case .stateUpdate(let update):
207
- Self.saveState(update.stateSerialization)
185
+ StateFile.save(update.stateSerialization) // resume from the right token next launch
208
186
  case .accountChange(let change):
209
- handleAccountChange(change)
210
- case .fetchedRecordZoneChanges(let changes):
211
- for mod in changes.modifications { processRemoteRecord(mod.record) }
212
- for del in changes.deletions { processRemoteDeletion(del.recordID) }
187
+ await LocalStore.shared.handle(change)
188
+ case .fetchedRecordZoneChanges(let fetched):
189
+ for mod in fetched.modifications { await LocalStore.shared.upsert(mod.record) }
190
+ for gone in fetched.deletions { await LocalStore.shared.remove(gone.recordID) }
213
191
  case .sentRecordZoneChanges(let sent):
214
- for saved in sent.savedRecords { markSynced(saved) }
215
- for fail in sent.failedRecordSaves { handleSaveFailure(fail) }
216
- default: break
192
+ for saved in sent.savedRecords { await LocalStore.shared.markSynced(saved) }
193
+ for failure in sent.failedRecordSaves { await resolve(failure, engine: syncEngine) }
194
+ default:
195
+ break
217
196
  }
218
197
  }
219
198
 
220
- func nextRecordZoneChangeBatch(
221
- _ context: CKSyncEngine.SendChangesContext,
222
- syncEngine: CKSyncEngine
223
- ) async -> CKSyncEngine.RecordZoneChangeBatch? {
224
- let pending = syncEngine.state.pendingRecordZoneChanges
225
- .filter { context.options.zoneIDs.contains($0) }
226
- return await CKSyncEngine.RecordZoneChangeBatch(
227
- pendingChanges: pending
228
- ) { recordID in self.recordToSend(for: recordID) }
199
+ func nextRecordZoneChangeBatch(_ context: CKSyncEngine.SendChangesContext,
200
+ syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch? {
201
+ let scope = context.options.scope
202
+ let pending = syncEngine.state.pendingRecordZoneChanges.filter { scope.contains($0) }
203
+ return await CKSyncEngine.RecordZoneChangeBatch(pendingChanges: pending) { id in
204
+ await LocalStore.shared.record(for: id)
205
+ }
229
206
  }
230
- }
231
207
 
232
- // Schedule changes
233
- let zoneID = CKRecordZone.ID(zoneName: "NotesZone")
234
- let recordID = CKRecord.ID(recordName: noteID, zoneID: zoneID)
235
- syncEngine.state.add(pendingRecordZoneChanges: [.saveRecord(recordID)])
208
+ func queueUpload(of id: CKRecord.ID) {
209
+ engine?.state.add(pendingRecordZoneChanges: [.saveRecord(id)])
210
+ }
236
211
 
237
- // Trigger immediate sync (pull-to-refresh)
238
- try await syncEngine.fetchChanges()
239
- try await syncEngine.sendChanges()
212
+ func pullNow() async throws {
213
+ guard let engine else { return }
214
+ try await engine.fetchChanges() // pull-to-refresh or push arrival
215
+ try await engine.sendChanges()
216
+ }
217
+ }
240
218
  ```
241
219
 
242
- **Key point**: persist `stateSerialization` across launches; the engine needs it
243
- to resume from the correct change token.
220
+ `context.options.scope` says which zones (for example `.zoneIDs([...])`) or
221
+ records this send covers; filter the pending changes to it rather than
222
+ sending everything. Both delegate methods are `async`. Persist
223
+ `stateSerialization` on every `.stateUpdate`; without it the engine starts
224
+ over on the next launch.
244
225
 
245
- ## SwiftData + CloudKit
226
+ ## SwiftData with CloudKit
246
227
 
247
- `ModelConfiguration` supports CloudKit sync. In every SwiftData CloudKit
248
- implementation or review, always report two verdicts:
228
+ `ModelConfiguration` can sync a SwiftData store to CloudKit. Any
229
+ implementation or review has to state **two verdicts**:
249
230
 
250
- - **Model compatibility**: no `#Unique` or unique constraints, optional
251
- relationships, no `.deny`, and external storage for large `Data`.
252
- - **Schema rollout**: initialize the development schema in nonproduction builds,
253
- verify it in CloudKit Dashboard, promote it before release, and after
254
- production promotion only add schema; don't delete model types or change
255
- existing attributes.
231
+ 1. **Model compatibility** - no `#Unique` macro or unique attributes, every
232
+ relationship optional, no `.deny` delete rule, and large `Data` marked for
233
+ external storage. Scalar properties do not all need to be optional; they
234
+ just need defaults or optionality where CloudKit requires it.
235
+ 2. **Schema rollout** - initialize the development schema from a non-release
236
+ build, inspect it in CloudKit Dashboard, and promote it to production before
237
+ shipping. After promotion, changes are additive only: never delete a model
238
+ type or change an existing attribute.
256
239
 
257
240
  ```swift
258
241
  import SwiftData
259
242
 
260
243
  @Model
261
- class Note {
262
- var title: String
263
- var body: String?
264
- var createdAt: Date?
265
- @Attribute(.externalStorage) var imageData: Data?
266
-
267
- init(title: String, body: String? = nil) {
268
- self.title = title
269
- self.body = body
270
- self.createdAt = Date()
271
- }
244
+ final class Harvest {
245
+ var crop: String = ""
246
+ var weightGrams: Double = 0
247
+ var pickedOn: Date = Date.now
248
+ @Attribute(.externalStorage) var photo: Data?
249
+ var bed: GardenBed? // optional relationship
250
+ init() {}
272
251
  }
252
+ ```
273
253
 
274
- let config = ModelConfiguration(
275
- "Notes",
276
- cloudKitDatabase: .private("iCloud.com.example.app")
277
- )
278
- let container = try ModelContainer(for: Note.self, configurations: config)
254
+ ```swift
255
+ let config = ModelConfiguration("Garden", cloudKitDatabase: .private("iCloud.app.gardenlog"))
256
+ let container = try ModelContainer(for: Harvest.self, GardenBed.self, configurations: config)
279
257
  ```
280
258
 
281
- ## NSUbiquitousKeyValueStore
259
+ ## Key-value store
282
260
 
283
- Simple key-value sync. Max 1024 keys, 1 MB total, 1 MB per value. Stores
284
- locally when iCloud is unavailable.
261
+ `NSUbiquitousKeyValueStore` holds small preferences: at most 1024 keys, 1 MB
262
+ in total and 1 MB per value. It writes locally when iCloud is unavailable and
263
+ syncs later.
285
264
 
286
265
  ```swift
287
- let kvStore = NSUbiquitousKeyValueStore.default
288
-
289
- // Write
290
- kvStore.set("dark", forKey: "theme")
291
- kvStore.set(14.0, forKey: "fontSize")
292
- kvStore.set(true, forKey: "notificationsEnabled")
293
- kvStore.synchronize()
266
+ let kv = NSUbiquitousKeyValueStore.default
267
+ kv.set("celsius", forKey: "temperatureUnit")
268
+ kv.synchronize()
269
+ let unit = kv.string(forKey: "temperatureUnit")
294
270
 
295
- // Read
296
- let theme = kvStore.string(forKey: "theme") ?? "system"
297
-
298
- // Observe external changes
299
271
  NotificationCenter.default.addObserver(
300
272
  forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
301
- object: kvStore, queue: .main
302
- ) { notification in
303
- guard let userInfo = notification.userInfo,
304
- let reason = userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] as? Int,
305
- let keys = userInfo[NSUbiquitousKeyValueStoreChangedKeysKey] as? [String]
306
- else { return }
307
-
273
+ object: kv, queue: .main
274
+ ) { note in
275
+ let reason = note.userInfo?[NSUbiquitousKeyValueStoreChangeReasonKey] as? Int
276
+ let keys = note.userInfo?[NSUbiquitousKeyValueStoreChangedKeysKey] as? [String] ?? []
308
277
  switch reason {
309
- case NSUbiquitousKeyValueStoreServerChange:
310
- for key in keys { applyRemoteChange(key: key) }
311
- case NSUbiquitousKeyValueStoreInitialSyncChange:
312
- reloadAllSettings()
313
- case NSUbiquitousKeyValueStoreQuotaViolationChange:
314
- handleQuotaExceeded()
278
+ case NSUbiquitousKeyValueStoreServerChange: Preferences.apply(keys)
279
+ case NSUbiquitousKeyValueStoreInitialSyncChange: Preferences.reloadAll()
280
+ case NSUbiquitousKeyValueStoreQuotaViolationChange: Preferences.trimStorage()
315
281
  default: break
316
282
  }
317
283
  }
318
284
  ```
319
285
 
320
- ## iCloud Drive File Sync
286
+ ## iCloud Drive documents
321
287
 
322
- Use `FileManager` ubiquity APIs for document-level sync. Call
323
- `url(forUbiquityContainerIdentifier:)` and `setUbiquitous` off the main thread;
324
- `setUbiquitous` performs coordinated file work and can block. If the app is
325
- presenting the file, configure an active file presenter before moving it.
288
+ Documents sync through the `FileManager` ubiquity APIs. Both
289
+ `url(forUbiquityContainerIdentifier:)` and
290
+ `setUbiquitous(_:itemAt:destinationURL:)` can block (the second performs
291
+ coordinated file operations), so call them off the main thread. A `nil`
292
+ container URL means iCloud is not available. If the app is currently
293
+ presenting the file, register a file presenter before moving it.
326
294
 
327
295
  ```swift
328
- Task.detached {
329
- guard let ubiquityURL = FileManager.default.url(
330
- forUbiquityContainerIdentifier: "iCloud.com.example.app"
331
- ) else { return } // iCloud not available
332
-
333
- let docsURL = ubiquityURL.appendingPathComponent("Documents")
334
- try FileManager.default.createDirectory(at: docsURL, withIntermediateDirectories: true)
335
- let cloudURL = docsURL.appendingPathComponent("report.pdf")
336
- try FileManager.default.setUbiquitous(true, itemAt: localURL, destinationURL: cloudURL)
296
+ func moveToCloud(_ local: URL) {
297
+ Task.detached {
298
+ let fm = FileManager.default
299
+ guard let root = fm.url(forUbiquityContainerIdentifier: nil) else { return }
300
+ let docs = root.appending(path: "Documents", directoryHint: .isDirectory)
301
+ try? fm.createDirectory(at: docs, withIntermediateDirectories: true)
302
+ try? fm.setUbiquitous(true, itemAt: local, destinationURL: docs.appending(path: local.lastPathComponent))
303
+ }
337
304
  }
338
305
  ```
339
306
 
340
- Monitor files with `NSMetadataQuery` scoped to
341
- `NSMetadataQueryUbiquitousDocumentsScope` or
342
- `NSMetadataQueryUbiquitousDataScope`.
307
+ Watch for changes with an `NSMetadataQuery` whose search scope is
308
+ `NSMetadataQueryUbiquitousDocumentsScope` or `NSMetadataQueryUbiquitousDataScope`.
343
309
 
344
- ## Account Status and Error Handling
310
+ ## Account status
345
311
 
346
- Always check account status before sync. Listen for `.CKAccountChanged`.
312
+ Check the account before any sync, and listen for `.CKAccountChanged`.
347
313
 
348
314
  ```swift
349
- func checkiCloudStatus() async throws -> CKAccountStatus {
350
- let status = try await CKContainer.default().accountStatus()
351
- switch status {
352
- case .available: return status
353
- case .noAccount: throw SyncError.noiCloudAccount
354
- case .restricted: throw SyncError.restricted
355
- case .temporarilyUnavailable: throw SyncError.temporarilyUnavailable
356
- case .couldNotDetermine: throw SyncError.unknown
357
- @unknown default: throw SyncError.unknown
358
- }
315
+ switch try await CKContainer.default().accountStatus() {
316
+ case .available: await GardenSync.shared.start()
317
+ case .noAccount: showSignInToICloudHint()
318
+ case .restricted: showRestrictedNotice()
319
+ case .temporarilyUnavailable: scheduleAccountRecheck()
320
+ case .couldNotDetermine: scheduleAccountRecheck()
321
+ @unknown default: scheduleAccountRecheck()
359
322
  }
360
323
  ```
361
324
 
362
- ### CKError Handling
325
+ ## CKError handling
363
326
 
364
- | Error Code | Strategy |
365
- |-----------|----------|
366
- | `.networkFailure`, `.networkUnavailable` | Queue for retry when network returns |
367
- | `.serverRecordChanged` | Three-way merge (see Conflict Resolution) |
368
- | `.requestRateLimited`, `.zoneBusy`, `.serviceUnavailable` | Retry after `retryAfterSeconds` |
369
- | `.quotaExceeded` | Notify user; reduce data usage |
370
- | `.notAuthenticated` | Prompt iCloud sign-in |
371
- | `.partialFailure` | Inspect `partialErrorsByItemID` per item |
372
- | `.changeTokenExpired` | Reset token, refetch all changes |
373
- | `.userDeletedZone` | Recreate zone and re-upload data |
327
+ | Code | Response |
328
+ |---|---|
329
+ | `.networkFailure`, `.networkUnavailable` | Queue the work and retry once connectivity returns |
330
+ | `.serverRecordChanged` | Three-way merge (below) |
331
+ | `.requestRateLimited`, `.zoneBusy`, `.serviceUnavailable` | Wait `retryAfterSeconds` before retrying |
332
+ | `.quotaExceeded` | Tell the user; reduce stored data |
333
+ | `.notAuthenticated` | Ask the user to sign in to iCloud |
334
+ | `.partialFailure` | Walk `partialErrorsByItemID` and handle each item's error |
335
+ | `.changeTokenExpired` | Drop the token and refetch everything |
336
+ | `.userDeletedZone` | Recreate the zone and upload again |
374
337
 
375
338
  ```swift
376
- func handleCloudKitError(_ error: Error) {
377
- guard let ckError = error as? CKError else { return }
378
- switch ckError.code {
379
- case .networkFailure, .networkUnavailable:
380
- scheduleRetryWhenOnline()
381
- case .serverRecordChanged:
382
- resolveConflict(ckError)
383
- case .requestRateLimited, .zoneBusy, .serviceUnavailable:
384
- let delay = ckError.retryAfterSeconds ?? 3.0
385
- scheduleRetry(after: delay)
386
- case .quotaExceeded:
387
- notifyUserStorageFull()
388
- case .partialFailure:
389
- if let partial = ckError.partialErrorsByItemID {
390
- for (_, itemError) in partial { handleCloudKitError(itemError) }
391
- }
392
- case .changeTokenExpired:
393
- resetChangeToken()
394
- case .userDeletedZone:
395
- recreateZoneAndResync()
396
- default: logError(ckError)
397
- }
339
+ func retryDelay(for error: CKError) -> TimeInterval {
340
+ error.retryAfterSeconds ?? 3
398
341
  }
399
342
  ```
400
343
 
401
- ## Conflict Resolution
344
+ ## Conflict resolution
402
345
 
403
- When saving a record that changed server-side, CloudKit returns
404
- `.serverRecordChanged` with three record versions. Always merge into
405
- `serverRecord` -- it has the correct change tag.
346
+ A `.serverRecordChanged` error carries `ancestorRecord`, `clientRecord` and
347
+ `serverRecord`. Always merge **into `serverRecord`**, because it holds the
348
+ current change tag; saving the client copy just fails again.
406
349
 
407
350
  ```swift
408
- func resolveConflict(_ error: CKError) {
409
- guard error.code == .serverRecordChanged,
410
- let ancestor = error.ancestorRecord,
411
- let client = error.clientRecord,
412
- let server = error.serverRecord
413
- else { return }
414
-
415
- // Merge client changes into server record
416
- for key in client.changedKeys() {
417
- if server[key] == ancestor[key] {
418
- server[key] = client[key] // Server unchanged, use client
419
- } else if client[key] == ancestor[key] {
420
- // Client unchanged, keep server (already there)
351
+ func merged(_ error: CKError) -> CKRecord? {
352
+ guard let server = error.serverRecord, let mine = error.clientRecord else { return nil }
353
+ let base = error.ancestorRecord
354
+ for key in mine.changedKeys() {
355
+ let serverValue = server[key] as? NSObject
356
+ let baseValue = base?[key] as? NSObject
357
+ let myValue = mine[key] as? NSObject
358
+ if serverValue == baseValue {
359
+ server[key] = mine[key] // only I changed it
360
+ } else if myValue == baseValue {
361
+ continue // only the server changed it
421
362
  } else {
422
- server[key] = mergeValues( // Both changed, custom merge
423
- ancestor: ancestor[key], client: client[key], server: server[key])
363
+ server[key] = resolveBothChanged(key, mine: mine[key], theirs: server[key])
424
364
  }
425
365
  }
426
-
427
- Task { try await CKContainer.default().privateCloudDatabase.save(server) }
366
+ return server // save this one
428
367
  }
429
368
  ```
430
369
 
431
- ## Common Mistakes
432
-
433
- **DON'T:** Perform sync operations without checking account status.
434
- **DO:** Check `CKContainer.accountStatus()` first; handle `.noAccount`.
435
- ```swift
436
- // WRONG
437
- try await privateDB.save(record)
438
- // CORRECT
439
- guard try await CKContainer.default().accountStatus() == .available
440
- else { throw SyncError.noiCloudAccount }
441
- try await privateDB.save(record)
442
- ```
443
-
444
- **DON'T:** Ignore `.serverRecordChanged` errors.
445
- **DO:** Implement three-way merge using ancestor, client, and server records.
446
-
447
- **DON'T:** Store user-specific data in the public database.
448
- **DO:** Use private database for personal data; public only for app-wide content.
449
-
450
- **DON'T:** Poll for changes on a timer.
451
- **DO:** Use `CKDatabaseSubscription` or `CKSyncEngine` for push-based sync.
452
- ```swift
453
- // WRONG
454
- Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { _ in fetchAll() }
455
- // CORRECT
456
- let sub = CKDatabaseSubscription(subscriptionID: "db-changes")
457
- sub.notificationInfo = CKSubscription.NotificationInfo()
458
- sub.notificationInfo?.shouldSendContentAvailable = true
459
- try await privateDB.save(sub)
460
- ```
461
-
462
- **DON'T:** Retry immediately on rate limiting.
463
- **DO:** Use `CKError.retryAfterSeconds` to wait the required duration.
464
-
465
- **DON'T:** Assume `CKSyncEngine` handles `.serverRecordChanged` conflicts for you.
466
- **DO:** Resolve `failedRecordSaves` with a three-way merge, then reschedule the save.
467
-
468
- **DON'T:** Pass nil change token on every fetch.
469
- **DO:** Persist change tokens to disk and supply them on subsequent fetches.
470
-
471
- ## Review Checklist
472
-
473
- - [ ] iCloud + CloudKit capability enabled in Signing & Capabilities
474
- - [ ] Account status checked before sync; `.noAccount` handled gracefully
475
- - [ ] Private database used for user data; public only for shared content
476
- - [ ] Custom record zones created in private DB; shared DB zones discovered from shares
477
- - [ ] `CKError.serverRecordChanged` handled with three-way merge into `serverRecord`
478
- - [ ] Network failures queued for retry; `retryAfterSeconds` respected
479
- - [ ] `CKDatabaseSubscription` or `CKSyncEngine` used for push-based sync; Remote notifications enabled for background delivery
480
- - [ ] Change tokens persisted to disk; `changeTokenExpired` resets and refetches
481
- - [ ] `.partialFailure` errors inspected per-item via `partialErrorsByItemID`
482
- - [ ] `.userDeletedZone` handled by recreating zone and resyncing
483
- - [ ] SwiftData CloudKit review reports model compatibility and schema rollout: initialized/verified development schema, promoted before release, and additive-only production changes
484
- - [ ] `NSUbiquitousKeyValueStore.didChangeExternallyNotification` observed
485
- - [ ] Encryption review says `CKRecord.Reference` cannot use `encryptedValues` because CloudKit needs it server-side; no query/sort on encrypted fields; `CKAsset` is encrypted by default
486
- - [ ] `CKSyncEngine` state serialization persisted across launches (iOS 17+)
370
+ ## Common mistakes
371
+
372
+ - Starting sync without confirming `accountStatus() == .available`.
373
+ - Ignoring `.serverRecordChanged`, which silently loses edits.
374
+ - Putting personal data in the public database. Private is for user data;
375
+ public is only for content every user should see.
376
+ - Polling on a timer. Use a `CKDatabaseSubscription` with silent pushes or `CKSyncEngine`.
377
+ - Retrying a rate-limited request immediately instead of waiting `retryAfterSeconds`.
378
+ - Expecting `CKSyncEngine` to settle `.serverRecordChanged`. Merge the
379
+ `failedRecordSaves` yourself and reschedule them.
380
+ - Fetching with a `nil` change token every time. Store tokens on disk.
381
+
382
+ ## Review checklist
383
+
384
+ - [ ] iCloud capability with CloudKit enabled
385
+ - [ ] Account status checked; `.noAccount` handled
386
+ - [ ] User data kept in the private database
387
+ - [ ] Custom zones in the private database; shared zones discovered from accepted shares
388
+ - [ ] `.serverRecordChanged` merged into `serverRecord`
389
+ - [ ] Network failures queued; `retryAfterSeconds` honored
390
+ - [ ] Sync driven by pushes; Remote notifications background mode on
391
+ - [ ] Change tokens persisted; `.changeTokenExpired` resets them
392
+ - [ ] `.partialFailure` examined item by item
393
+ - [ ] `.userDeletedZone` handled
394
+ - [ ] SwiftData review gives both the model-compatibility and schema-rollout verdicts
395
+ - [ ] Key-value store external-change notification observed
396
+ - [ ] Encryption review mentions: references cannot be encrypted, encrypted fields cannot be queried or sorted, assets are encrypted already
397
+ - [ ] `CKSyncEngine` state serialization saved (iOS 17+)
487
398
 
488
399
  ## References
489
400
 
490
- - See [references/cloudkit-patterns.md](references/cloudkit-patterns.md) for incremental sync, CKShare, zones, CKAsset storage, batch operations, and Dashboard usage.
491
- - [CloudKit Framework](https://sosumi.ai/documentation/cloudkit)
492
- - [CKContainer](https://sosumi.ai/documentation/cloudkit/ckcontainer)
493
- - [CKRecord](https://sosumi.ai/documentation/cloudkit/ckrecord)
494
- - [CKQuery](https://sosumi.ai/documentation/cloudkit/ckquery)
495
- - [CKSubscription](https://sosumi.ai/documentation/cloudkit/cksubscription)
496
- - [CKSyncEngine](https://sosumi.ai/documentation/cloudkit/cksyncengine)
497
- - [CKShare](https://sosumi.ai/documentation/cloudkit/ckshare)
498
- - [CKError](https://sosumi.ai/documentation/cloudkit/ckerror)
499
- - [NSUbiquitousKeyValueStore](https://sosumi.ai/documentation/foundation/nsubiquitouskeyvaluestore)
500
- - [SwiftData CloudKit sync](https://sosumi.ai/documentation/swiftdata/syncing-model-data-across-a-persons-devices)
401
+ - [CloudKit patterns](references/cloudkit-patterns.md) - incremental fetches, tokens, sharing, zones, assets, batches, QoS, encryption, Dashboard
402
+ - [CloudKit](https://developer.apple.com/documentation/cloudkit)
403
+ - [CKContainer](https://developer.apple.com/documentation/cloudkit/ckcontainer)
404
+ - [CKRecord](https://developer.apple.com/documentation/cloudkit/ckrecord)
405
+ - [CKQuery](https://developer.apple.com/documentation/cloudkit/ckquery)
406
+ - [CKSubscription](https://developer.apple.com/documentation/cloudkit/cksubscription)
407
+ - [CKSyncEngine](https://developer.apple.com/documentation/cloudkit/cksyncengine-5sie5)
408
+ - [CKShare](https://developer.apple.com/documentation/cloudkit/ckshare)
409
+ - [CKError](https://developer.apple.com/documentation/cloudkit/ckerror)
410
+ - [NSUbiquitousKeyValueStore](https://developer.apple.com/documentation/foundation/nsubiquitouskeyvaluestore)
411
+ - SwiftData: the article on syncing model data across a person's devices