@mmerterden/multi-agent-pipeline 20.7.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 (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,498 +1,422 @@
1
1
  ---
2
2
  name: core-data
3
- description: "Build, review, or improve Core Data persistence in apps that have not adopted SwiftData. Use when working with NSManagedObject subclasses, NSFetchedResultsController for list-driven UI, NSBatchInsertRequest / NSBatchDeleteRequest / NSBatchUpdateRequest for bulk operations, NSPersistentHistoryChangeRequest for persistent history tracking and multi-target sync, NSStagedMigrationManager for staged schema migrations (iOS 17+), NSCompositeAttributeDescription for composite attributes (iOS 17+), or when integrating Core Data threading with Swift Concurrency. For Core Data + SwiftData coexistence or migration, see the swiftdata skill instead."
3
+ description: "Create, audit or refine Core Data persistence for apps still on Core Data rather than SwiftData: stack setup, NSManagedObject subclasses, NSFetchedResultsController for list UIs, NSBatchInsertRequest, NSBatchDeleteRequest and NSBatchUpdateRequest bulk work, NSPersistentHistoryChangeRequest history tracking across app and extension targets, NSStagedMigrationManager staged migrations and NSCompositeAttributeDescription composite attributes (iOS 17+), and making Core Data contexts work with Swift Concurrency. Use when working on an existing Core Data store. Not for Core Data and SwiftData coexistence or migration; use the swiftdata skill."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
7
 
8
8
  # Core Data
9
9
 
10
- Build and maintain data persistence using Core Data for apps that have not
11
- adopted SwiftData. Covers stack setup, concurrency, batch operations,
12
- NSFetchedResultsController, persistent history tracking, staged migration,
13
- and testing.
10
+ For apps that still persist with Core Data rather than SwiftData. Covers the
11
+ stack, queue rules and Swift Concurrency, fetched results controllers, batch
12
+ requests, persistent history, staged migration, composite attributes and
13
+ testing. Hand-offs: SwiftData coexistence and migration go to `swiftdata`;
14
+ iCloud sync details (zones, sharing, `CKError`) go to `cloudkit-sync`; test
15
+ style goes to `swift-testing`.
14
16
 
15
- ## Contents
17
+ ## The stack
16
18
 
17
- - [Stack Setup](#stack-setup)
18
- - [Concurrency and Threading](#concurrency-and-threading)
19
- - [NSFetchedResultsController](#nsfetchedresultscontroller)
20
- - [Batch Operations](#batch-operations)
21
- - [Persistent History Tracking](#persistent-history-tracking)
22
- - [Staged Migration](#staged-migration)
23
- - [Composite Attributes](#composite-attributes)
24
- - [SwiftData Boundary](#swiftdata-boundary)
25
- - [Testing](#testing)
26
- - [Common Mistakes](#common-mistakes)
27
- - [Review Checklist](#review-checklist)
28
- - [References](#references)
29
-
30
- ## Stack Setup
31
-
32
- `NSPersistentContainer` encapsulates the Core Data stack.
33
-
34
- Docs: [NSPersistentContainer](https://sosumi.ai/documentation/coredata/nspersistentcontainer)
19
+ `NSPersistentContainer` wraps the model, coordinator and contexts. Create it
20
+ once and share it.
35
21
 
36
22
  ```swift
37
23
  import CoreData
38
24
 
39
- final class CoreDataStack: @unchecked Sendable {
40
- static let shared = CoreDataStack()
41
-
25
+ final class LibraryStore: @unchecked Sendable {
26
+ static let shared = LibraryStore()
42
27
  let container: NSPersistentContainer
43
28
 
44
29
  private init() {
45
- container = NSPersistentContainer(name: "MyAppModel")
30
+ container = NSPersistentContainer(name: "Library")
46
31
  container.loadPersistentStores { _, error in
47
- if let error { fatalError("Core Data store failed: \(error)") }
32
+ if let error { fatalError("Store failed to load: \(error)") }
48
33
  }
49
- container.viewContext.automaticallyMergesChangesFromParent = true
50
- container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
34
+ let ui = container.viewContext
35
+ ui.automaticallyMergesChangesFromParent = true
36
+ ui.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump // NSMergeByPropertyObjectTrumpMergePolicy
51
37
  }
52
38
 
53
- var viewContext: NSManagedObjectContext { container.viewContext }
54
-
55
- func newBackgroundContext() -> NSManagedObjectContext {
39
+ func makeWorker() -> NSManagedObjectContext {
56
40
  container.newBackgroundContext()
57
41
  }
58
42
  }
59
43
  ```
60
44
 
61
- For CloudKit sync, use `NSPersistentCloudKitContainer` instead.
45
+ Replace `fatalError` with real recovery in shipping code. When the store syncs
46
+ through iCloud, create an `NSPersistentCloudKitContainer` instead.
62
47
 
63
- ## Concurrency and Threading
48
+ ## Queues and threads
64
49
 
65
- Core Data contexts are bound to queues. The `viewContext` is on the main queue;
66
- background contexts operate on private queues.
50
+ Every context belongs to a queue: `viewContext` to the main queue, background
51
+ contexts to private queues.
67
52
 
68
- Docs: [NSManagedObjectContext](https://sosumi.ai/documentation/coredata/nsmanagedobjectcontext)
69
-
70
- **Rules:**
71
- - Always use `perform(_:)` or `performAndWait(_:)` when accessing a context
72
- off its own queue.
73
- - Never pass `NSManagedObject` instances across context or thread boundaries.
74
- Pass `NSManagedObjectID` instead and re-fetch.
75
- - Set `automaticallyMergesChangesFromParent = true` on the `viewContext`.
53
+ - Touch a context from anywhere other than its own queue only inside
54
+ `perform(_:)` or `performAndWait(_:)`.
55
+ - Never hand an `NSManagedObject` to another context or thread. Pass its
56
+ `NSManagedObjectID` and fetch it again on the other side.
57
+ - Turn on `automaticallyMergesChangesFromParent` for `viewContext`.
76
58
 
77
59
  ```swift
78
- // Writing on a background context
79
- func updateTrip(id: NSManagedObjectID, newName: String) async throws {
80
- let context = CoreDataStack.shared.newBackgroundContext()
81
- try await context.perform {
82
- guard let trip = try context.existingObject(with: id) as? CDTrip else {
83
- throw PersistenceError.notFound
84
- }
85
- trip.name = newName
86
- try context.save()
60
+ func markReturned(_ loanID: NSManagedObjectID) async throws {
61
+ let worker = LibraryStore.shared.makeWorker()
62
+ try await worker.perform {
63
+ guard let loan = try worker.existingObject(with: loanID) as? Loan else { return }
64
+ loan.returnedAt = .now
65
+ try worker.save()
87
66
  }
88
67
  }
89
68
  ```
90
69
 
91
- ### Swift Concurrency Integration
70
+ ### Swift Concurrency
92
71
 
93
- `NSManagedObjectContext.perform(_:)` has an `async throws` overload
94
- (iOS 15+). Avoid marking `NSManagedObject` subclasses as `Sendable`.
72
+ Since iOS 15 you can `try await context.perform { }`. Do not mark
73
+ `NSManagedObject` subclasses `Sendable` or `@unchecked Sendable`; they are not
74
+ thread-safe.
95
75
 
96
76
  ```swift
97
- func importItems(_ records: [ItemRecord]) async throws {
98
- let context = CoreDataStack.shared.newBackgroundContext()
99
- try await context.perform {
100
- for record in records {
101
- let item = CDItem(context: context)
102
- item.id = record.id
103
- item.title = record.title
77
+ func importBooks(_ rows: [BookRow]) async throws {
78
+ let worker = LibraryStore.shared.makeWorker()
79
+ try await worker.perform {
80
+ for row in rows {
81
+ let book = Book(context: worker)
82
+ book.isbn = row.isbn
83
+ book.title = row.title
104
84
  }
105
- try context.save()
85
+ if worker.hasChanges { try worker.save() }
106
86
  }
107
- // After save completes, viewContext auto-merges if configured
87
+ // viewContext picks this up through automatic merging
108
88
  }
109
- ```
110
-
111
- **Do not use `@unchecked Sendable` on managed objects.** If you need
112
- cross-boundary communication, pass the `objectID` (which is `Sendable`)
113
- and re-fetch:
114
89
 
115
- ```swift
116
- let objectID = trip.objectID // Sendable
117
- Task.detached {
118
- let bgContext = CoreDataStack.shared.newBackgroundContext()
119
- try await bgContext.perform {
120
- let trip = try bgContext.existingObject(with: objectID) as! CDTrip
121
- trip.isFavorite = true
122
- try bgContext.save()
90
+ func archive(_ bookID: NSManagedObjectID) {
91
+ Task.detached { // NSManagedObjectID is Sendable
92
+ let worker = LibraryStore.shared.makeWorker()
93
+ try await worker.perform {
94
+ let book = try worker.existingObject(with: bookID) as? Book
95
+ book?.isArchived = true
96
+ try worker.save()
97
+ }
123
98
  }
124
99
  }
125
100
  ```
126
101
 
127
- ## NSFetchedResultsController
102
+ ## `NSFetchedResultsController`
128
103
 
129
- Efficiently drives `UITableView` / `UICollectionView` from a Core Data fetch
130
- request, with built-in change tracking and optional caching.
131
-
132
- Docs: [NSFetchedResultsController](https://sosumi.ai/documentation/coredata/nsfetchedresultscontroller)
104
+ An FRC feeds a table or collection view from a fetch request, tracks changes,
105
+ and can cache section data.
133
106
 
134
107
  ```swift
135
- import CoreData
136
108
  import UIKit
137
109
 
138
- class TripsViewController: UITableViewController, NSFetchedResultsControllerDelegate {
139
-
140
- private lazy var fetchedResultsController: NSFetchedResultsController<CDTrip> = {
141
- let request: NSFetchRequest<CDTrip> = CDTrip.fetchRequest()
142
- request.sortDescriptors = [
143
- NSSortDescriptor(keyPath: \CDTrip.startDate, ascending: false)
144
- ]
145
- request.fetchBatchSize = 20
146
-
147
- let controller = NSFetchedResultsController(
148
- fetchRequest: request,
149
- managedObjectContext: CoreDataStack.shared.viewContext,
150
- sectionNameKeyPath: nil,
151
- cacheName: "TripsCache"
152
- )
153
- controller.delegate = self
154
- return controller
155
- }()
110
+ final class ShelfViewController: UICollectionViewController, @preconcurrency NSFetchedResultsControllerDelegate {
111
+ private var results: NSFetchedResultsController<Book>!
112
+ private var dataSource: UICollectionViewDiffableDataSource<String, NSManagedObjectID>!
156
113
 
157
114
  override func viewDidLoad() {
158
115
  super.viewDidLoad()
159
- try? fetchedResultsController.performFetch()
160
- }
161
-
162
- // MARK: - UITableViewDataSource
163
-
164
- override func numberOfSections(in tableView: UITableView) -> Int {
165
- fetchedResultsController.sections?.count ?? 0
116
+ let request = Book.fetchRequest()
117
+ request.sortDescriptors = [NSSortDescriptor(keyPath: \Book.title, ascending: true)]
118
+ request.fetchBatchSize = 25
119
+ results = NSFetchedResultsController(fetchRequest: request,
120
+ managedObjectContext: LibraryStore.shared.container.viewContext,
121
+ sectionNameKeyPath: nil,
122
+ cacheName: nil)
123
+ results.delegate = self
124
+ try? results.performFetch()
166
125
  }
167
126
 
168
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
169
- fetchedResultsController.sections?[section].numberOfObjects ?? 0
170
- }
171
-
172
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
173
- let cell = tableView.dequeueReusableCell(withIdentifier: "TripCell", for: indexPath)
174
- let trip = fetchedResultsController.object(at: indexPath)
175
- cell.textLabel?.text = trip.name
176
- return cell
177
- }
178
-
179
- // MARK: - NSFetchedResultsControllerDelegate (diffable)
180
-
181
- func controller(
182
- _ controller: NSFetchedResultsController<any NSFetchRequestResult>,
183
- didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference
184
- ) {
185
- let snapshot = snapshot as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>
186
- dataSource.apply(snapshot, animatingDifferences: true)
127
+ func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
128
+ didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
129
+ let typed = snapshot as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>
130
+ dataSource.apply(typed, animatingDifferences: true)
187
131
  }
188
132
  }
189
133
  ```
190
134
 
191
- **Key points:**
192
- - The fetch request **must** have at least one sort descriptor.
193
- - Call `deleteCache(withName:)` before changing the fetch request predicate or
194
- sort descriptors, or set `cacheName` to `nil`.
195
- - The diffable snapshot delegate method (`didChangeContentWith:`) is available
196
- iOS 13+ and is preferred over the older per-change callbacks.
197
- - After a context `reset()`, call `performFetch()` again.
135
+ Without a diffable data source, read `results.sections?.count`,
136
+ `results.sections?[section].numberOfObjects` and `results.object(at:)`.
198
137
 
199
- ## Batch Operations
138
+ Rules:
200
139
 
201
- Batch operations execute at the SQL level, bypassing the managed object
202
- context. They are fast but don't trigger context notifications automatically.
140
+ - The fetch request needs at least one sort descriptor.
141
+ - Before changing the predicate or sort descriptors, call
142
+ `NSFetchedResultsController.deleteCache(withName:)` or use `cacheName: nil`.
143
+ - The snapshot callback (iOS 13+) is preferred over the per-object change callbacks.
144
+ - After `reset()` on the context, call `performFetch()` again.
203
145
 
204
- ### NSBatchInsertRequest (iOS 13+)
146
+ ## Batch requests
205
147
 
206
- Docs: [NSBatchInsertRequest](https://sosumi.ai/documentation/coredata/nsbatchinsertrequest)
148
+ Batch requests run directly against SQLite. They skip the context, which makes
149
+ them fast, but they post no context notifications, so **merge their results
150
+ yourself**.
207
151
 
208
152
  ```swift
209
- func batchImport(_ records: [[String: Any]]) async throws {
210
- let context = CoreDataStack.shared.newBackgroundContext()
211
- try await context.perform {
212
- let request = NSBatchInsertRequest(
213
- entity: CDTrip.entity(),
214
- objects: records
215
- )
216
- request.resultType = .objectIDs
217
- let result = try context.execute(request) as? NSBatchInsertResult
218
- if let ids = result?.result as? [NSManagedObjectID] {
219
- NSManagedObjectContext.mergeChanges(
220
- fromRemoteContextSave: [NSInsertedObjectsKey: ids],
221
- into: [CoreDataStack.shared.viewContext]
222
- )
223
- }
224
- }
153
+ @Sendable func publish(_ result: Any?, as key: String) {
154
+ let ids = result as? [NSManagedObjectID] ?? []
155
+ let ui = LibraryStore.shared.container.viewContext
156
+ NSManagedObjectContext.mergeChanges(fromRemoteContextSave: [key: ids], into: [ui])
225
157
  }
226
- ```
227
-
228
- ### NSBatchDeleteRequest (iOS 9+)
229
158
 
230
- Docs: [NSBatchDeleteRequest](https://sosumi.ai/documentation/coredata/nsbatchdeleterequest)
159
+ let worker = LibraryStore.shared.makeWorker()
231
160
 
232
- ```swift
233
- func deleteOldTrips(before cutoff: Date) async throws {
234
- let context = CoreDataStack.shared.newBackgroundContext()
235
- try await context.perform {
236
- let fetchRequest: NSFetchRequest<NSFetchRequestResult> = CDTrip.fetchRequest()
237
- fetchRequest.predicate = NSPredicate(format: "endDate < %@", cutoff as NSDate)
238
- let request = NSBatchDeleteRequest(fetchRequest: fetchRequest)
239
- request.resultType = .resultTypeObjectIDs
240
- let result = try context.execute(request) as? NSBatchDeleteResult
241
- if let ids = result?.result as? [NSManagedObjectID] {
242
- NSManagedObjectContext.mergeChanges(
243
- fromRemoteContextSave: [NSDeletedObjectsKey: ids],
244
- into: [CoreDataStack.shared.viewContext]
245
- )
246
- }
247
- }
161
+ // Insert (iOS 13+)
162
+ try await worker.perform {
163
+ let insert = NSBatchInsertRequest(entity: Book.entity(), objects: rows.map { ["isbn": $0.isbn, "title": $0.title] })
164
+ insert.resultType = .objectIDs
165
+ let inserted = try worker.execute(insert) as? NSBatchInsertResult
166
+ publish(inserted?.result, as: NSInsertedObjectsKey)
248
167
  }
249
- ```
250
168
 
251
- ### NSBatchUpdateRequest (iOS 8+)
169
+ // Delete (iOS 9+)
170
+ try await worker.perform {
171
+ let stale: NSFetchRequest<NSFetchRequestResult> = Loan.fetchRequest()
172
+ stale.predicate = NSPredicate(format: "returnedAt < %@", cutoff as NSDate)
173
+ let delete = NSBatchDeleteRequest(fetchRequest: stale)
174
+ delete.resultType = .resultTypeObjectIDs
175
+ let removed = try worker.execute(delete) as? NSBatchDeleteResult
176
+ publish(removed?.result, as: NSDeletedObjectsKey)
177
+ }
252
178
 
253
- ```swift
254
- func markAllTripsAsNotFavorite() async throws {
255
- let context = CoreDataStack.shared.newBackgroundContext()
256
- try await context.perform {
257
- let request = NSBatchUpdateRequest(entity: CDTrip.entity())
258
- request.propertiesToUpdate = ["isFavorite": false]
259
- request.resultType = .updatedObjectIDsResultType
260
- let result = try context.execute(request) as? NSBatchUpdateResult
261
- if let ids = result?.result as? [NSManagedObjectID] {
262
- NSManagedObjectContext.mergeChanges(
263
- fromRemoteContextSave: [NSUpdatedObjectsKey: ids],
264
- into: [CoreDataStack.shared.viewContext]
265
- )
266
- }
267
- }
179
+ // Update (iOS 8+)
180
+ try await worker.perform {
181
+ let update = NSBatchUpdateRequest(entity: Book.entity())
182
+ update.propertiesToUpdate = ["isArchived": true]
183
+ update.resultType = .updatedObjectIDsResultType
184
+ let changed = try worker.execute(update) as? NSBatchUpdateResult
185
+ publish(changed?.result, as: NSUpdatedObjectsKey)
268
186
  }
269
187
  ```
270
188
 
271
- **Always merge changes** back into relevant contexts after batch operations.
272
- Batch delete does not enforce the Deny delete rule.
273
-
274
- ## Persistent History Tracking
189
+ A batch delete does not honor the **Deny** delete rule and skips validation
190
+ beyond the basic delete rules. Check those constraints yourself first.
275
191
 
276
- Track store-level changes across targets (app, extensions, widgets) and
277
- processes.
192
+ ## Persistent history
278
193
 
279
- Docs: [NSPersistentHistoryChangeRequest](https://sosumi.ai/documentation/coredata/nspersistenthistorychangerequest)
194
+ History tracking records store-level changes so the app, its extensions and
195
+ widgets (separate processes) can see each other's writes, including batch
196
+ requests.
280
197
 
281
- ### Enable History Tracking
198
+ ### Turn it on
282
199
 
283
200
  ```swift
284
- let description = NSPersistentStoreDescription()
285
- description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
286
- description.setOption(true as NSNumber,
287
- forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
201
+ guard let description = container.persistentStoreDescriptions.first else { return }
202
+ for key in [NSPersistentHistoryTrackingKey, NSPersistentStoreRemoteChangeNotificationPostOptionKey] {
203
+ description.setOption(NSNumber(value: true), forKey: key)
204
+ }
288
205
  container.persistentStoreDescriptions = [description]
289
206
  ```
290
207
 
291
- ### Observe, Fetch, Merge, and Purge
208
+ ### Consume it
209
+
210
+ 1. Observe `.NSPersistentStoreRemoteChange`, with the container's persistent store coordinator as the object.
211
+ 2. On a background context, fetch transactions after your last token.
212
+ 3. Merge each transaction into `viewContext` and move the token forward.
213
+ 4. Purge history older than the token.
292
214
 
293
215
  ```swift
294
- // 1. Observe remote change notifications
295
- NotificationCenter.default.addObserver(
296
- self, selector: #selector(storeRemoteChange(_:)),
297
- name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator
298
- )
299
-
300
- // 2. Fetch history since last token
301
- @objc func storeRemoteChange(_ notification: Notification) {
302
- let context = container.newBackgroundContext()
303
- context.perform {
304
- let request = NSPersistentHistoryChangeRequest.fetchHistory(after: self.lastToken)
305
- if let result = try? context.execute(request) as? NSPersistentHistoryResult,
306
- let transactions = result.result as? [NSPersistentHistoryTransaction] {
307
- // 3. Merge into viewContext
216
+ final class HistoryReader: NSObject, @unchecked Sendable {
217
+ private let container: NSPersistentContainer
218
+ private let tokenKey: String // one per target: "history.app", "history.widget"
219
+ private var lastToken: NSPersistentHistoryToken?
220
+
221
+ init(container: NSPersistentContainer, target: String) {
222
+ self.container = container
223
+ self.tokenKey = "history.\(target)"
224
+ super.init()
225
+ lastToken = Self.loadToken(forKey: tokenKey)
226
+ let center = NotificationCenter.default
227
+ center.addObserver(self, selector: #selector(storeChanged),
228
+ name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)
229
+ }
230
+
231
+ @objc private func storeChanged(_ note: Notification) {
232
+ let worker = container.newBackgroundContext()
233
+ worker.perform { [self] in
234
+ let fetch = NSPersistentHistoryChangeRequest.fetchHistory(after: lastToken)
235
+ let history = try? worker.execute(fetch) as? NSPersistentHistoryResult
236
+ guard let transactions = history?.result as? [NSPersistentHistoryTransaction] else { return }
237
+ let ui = container.viewContext
308
238
  for transaction in transactions {
309
- self.container.viewContext.mergeChanges(fromContextDidSave: transaction.objectIDNotification())
310
- self.lastToken = transaction.token
239
+ let changes = transaction.objectIDNotification().userInfo ?? [:]
240
+ NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [ui])
241
+ lastToken = transaction.token
242
+ }
243
+ Self.saveToken(lastToken, forKey: tokenKey)
244
+ if let lastToken {
245
+ _ = try? worker.execute(NSPersistentHistoryChangeRequest.deleteHistory(before: lastToken))
311
246
  }
312
247
  }
313
- // 4. Purge old history
314
- let purgeRequest = NSPersistentHistoryChangeRequest.deleteHistory(before: self.lastToken)
315
- try? context.execute(purgeRequest)
248
+ }
249
+
250
+ private static var defaults: UserDefaults { UserDefaults(suiteName: "group.app.library") ?? .standard }
251
+
252
+ private static func loadToken(forKey key: String) -> NSPersistentHistoryToken? {
253
+ defaults.data(forKey: key).flatMap {
254
+ try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSPersistentHistoryToken.self, from: $0)
255
+ }
256
+ }
257
+
258
+ private static func saveToken(_ token: NSPersistentHistoryToken?, forKey key: String) {
259
+ guard let token else { return }
260
+ let encoded = try? NSKeyedArchiver.archivedData(withRootObject: token, requiringSecureCoding: true)
261
+ defaults.set(encoded, forKey: key)
316
262
  }
317
263
  }
318
264
  ```
319
265
 
320
- Store `lastToken` in `UserDefaults` (per target) so history is processed
321
- correctly across launches.
266
+ The static merge call is safe from the worker queue; calling
267
+ `viewContext.mergeChanges(fromContextDidSave: transaction.objectIDNotification())`
268
+ inside `viewContext.perform` is the equivalent instance form.
322
269
 
323
- ## Staged Migration
270
+ Keep one token per target (for example in the shared `UserDefaults` suite) so
271
+ each process resumes where it stopped after relaunch. Purge only up to the
272
+ point every reader has consumed.
324
273
 
325
- `NSStagedMigrationManager` (iOS 17+) sequences schema migrations through
326
- ordered stages, each lightweight or custom.
274
+ ## Staged migration (iOS 17+)
327
275
 
328
- Docs: [NSStagedMigrationManager](https://sosumi.ai/documentation/coredata/nsstagedmigrationmanager)
276
+ `NSStagedMigrationManager` applies an ordered list of stages, each either
277
+ lightweight or custom.
329
278
 
330
- ```swift
331
- import CoreData
279
+ - `NSLightweightMigrationStage` takes the **version checksums** (`[String]`) of
280
+ compiled model versions, not their names, and has a `label`.
281
+ - `NSManagedObjectModelReference(name:in:versionChecksum:)` points to one model version.
282
+ - `NSCustomMigrationStage(migratingFrom:to:)` takes a source and a destination
283
+ reference and offers `label` and `willMigrateHandler` (called with the manager and stage).
332
284
 
333
- // Define migration stages
334
- // Use version checksums from the compiled model versions, not model names.
335
- let checksumV1 = "<ModelV1 version checksum>"
336
- let checksumV2 = "<ModelV2 version checksum>"
337
- let checksumV3 = "<ModelV3 version checksum>"
338
- let stage1to2 = NSLightweightMigrationStage([checksumV1, checksumV2])
339
- stage1to2.label = "Add isFavorite property"
340
-
341
- let modelV2 = NSManagedObjectModelReference(
342
- name: "ModelV2",
343
- in: Bundle.main,
344
- versionChecksum: checksumV2
345
- )
346
- let modelV3 = NSManagedObjectModelReference(
347
- name: "ModelV3",
348
- in: Bundle.main,
349
- versionChecksum: checksumV3
350
- )
351
- let stage2to3 = NSCustomMigrationStage(
352
- migratingFrom: modelV2,
353
- to: modelV3
354
- )
355
- stage2to3.label = "Split name into firstName/lastName"
356
- stage2to3.willMigrateHandler = { migrationManager, currentStage in
357
- guard let container = migrationManager.container else { return }
358
- let context = container.newBackgroundContext()
285
+ ```swift
286
+ let v1 = NSManagedObjectModelReference(name: "Library_v1", in: .main, versionChecksum: "k3Qp...v1")
287
+ let v2 = NSManagedObjectModelReference(name: "Library_v2", in: .main, versionChecksum: "R8bz...v2")
288
+ let v3Checksum = "Yt5m...v3"
289
+
290
+ let splitAuthor = NSCustomMigrationStage(migratingFrom: v1, to: v2)
291
+ splitAuthor.label = "Split author into first and last name"
292
+ splitAuthor.willMigrateHandler = { manager, _ in
293
+ guard let store = manager.container else { return }
294
+ let context = store.newBackgroundContext()
359
295
  try context.performAndWait {
360
- // Transform data between schema versions
361
- let request = NSFetchRequest<NSManagedObject>(entityName: "Person")
362
- let people = try context.fetch(request)
363
- for person in people {
364
- let fullName = person.value(forKey: "name") as? String ?? ""
365
- let parts = fullName.split(separator: " ", maxSplits: 1)
366
- person.setValue(String(parts.first ?? ""), forKey: "firstName")
367
- person.setValue(parts.count > 1 ? String(parts.last!) : "", forKey: "lastName")
296
+ let request = NSFetchRequest<NSManagedObject>(entityName: "Book")
297
+ for book in try context.fetch(request) {
298
+ let full = book.value(forKey: "author") as? String ?? ""
299
+ let parts = full.split(separator: " ", maxSplits: 1).map(String.init)
300
+ book.setValue(parts.first, forKey: "authorFirst")
301
+ book.setValue(parts.count > 1 ? parts[1] : nil, forKey: "authorLast")
368
302
  }
369
303
  try context.save()
370
304
  }
371
305
  }
372
306
 
373
- // Apply to the persistent store
374
- let manager = NSStagedMigrationManager([stage1to2, stage2to3])
375
- let description = NSPersistentStoreDescription()
376
- description.setOption(manager,
377
- forKey: NSPersistentStoreStagedMigrationManagerOptionKey)
307
+ let addIndexes = NSLightweightMigrationStage([v3Checksum])
308
+ addIndexes.label = "v2 to v3: add indexes"
309
+
310
+ let manager = NSStagedMigrationManager([splitAuthor, addIndexes])
311
+ let description = container.persistentStoreDescriptions.first ?? NSPersistentStoreDescription()
312
+ description.setOption(manager, forKey: NSPersistentStoreStagedMigrationManagerOptionKey)
378
313
  container.persistentStoreDescriptions = [description]
379
314
  container.loadPersistentStores { _, error in
380
- if let error { fatalError("Migration failed: \(error)") }
315
+ if let error { MigrationLog.failed(error) }
381
316
  }
382
317
  ```
383
318
 
384
- For apps targeting below iOS 17, use lightweight migration
385
- (`NSInferMappingModelAutomaticallyOption`) or mapping models.
386
-
387
- `NSLightweightMigrationStage` takes **version checksums** (`[String]`), not
388
- human-readable model names.
389
-
390
- ## Composite Attributes
391
-
392
- iOS 17+ supports composite attributes: groups of sub-attributes on an entity
393
- that act as a single logical unit. Define them in the model editor by adding a
394
- Composite type attribute and nesting sub-attributes beneath it.
319
+ The option must be on the store description **before** `loadPersistentStores`.
320
+ Read the checksums from the compiled `.momd` (for example
321
+ `NSManagedObjectModel.versionChecksum`), never type a model name in their place.
395
322
 
396
- Docs: [NSCompositeAttributeDescription](https://sosumi.ai/documentation/coredata/nscompositeattributedescription)
323
+ Older deployment targets fall back to inferred mapping
324
+ (`NSInferMappingModelAutomaticallyOption`) or hand-written mapping models.
397
325
 
398
- Composite attributes map to `Codable` structs in SwiftData coexistence
399
- scenarios.
326
+ ## Composite attributes (iOS 17+)
400
327
 
401
- ## SwiftData Boundary
328
+ `NSCompositeAttributeDescription` groups several sub-attributes into one
329
+ logical attribute, for example an address made of street, city and postcode.
330
+ Create it in the model editor by choosing the **Composite** type and adding
331
+ nested attributes. When the same store is read by SwiftData, a composite maps
332
+ to a `Codable` struct.
402
333
 
403
- Use the `swiftdata` skill for Core Data + SwiftData coexistence or migration
404
- implementation. Before handing off, preserve these Core Data boundaries:
334
+ ## Where SwiftData starts
405
335
 
406
- - SwiftData must point at the existing persistent store URL when it is meant to
407
- share or migrate Core Data data.
408
- - Shared persisted data must keep entity names, property names, types, and
409
- schema compatible across the Core Data model and SwiftData `@Model` classes.
410
- - Map renamed persisted properties with SwiftData `@Attribute(originalName:)`.
336
+ Coexistence and migration work belongs to `swiftdata`. The rules that matter
337
+ from this side:
411
338
 
412
- ## Testing
339
+ - Point SwiftData at the **existing** store URL when it shares or takes over Core Data data.
340
+ - The `@Model` classes must line up with the Core Data model: same entity and
341
+ property names, matching types, a compatible schema.
342
+ - Map renamed persisted properties with `@Attribute(originalName:)`.
413
343
 
414
- ### In-Memory Store for Tests
344
+ ## Testing with an in-memory store
415
345
 
416
346
  ```swift
417
- import CoreData
418
347
  import Testing
348
+ import CoreData
419
349
 
420
- struct CoreDataTests {
421
- func makeTestContainer() throws -> NSPersistentContainer {
422
- let container = NSPersistentContainer(name: "MyAppModel")
423
- let description = NSPersistentStoreDescription()
424
- description.type = NSInMemoryStoreType
425
- container.persistentStoreDescriptions = [description]
426
-
427
- var loadError: Error?
428
- container.loadPersistentStores { _, error in loadError = error }
429
- if let loadError { throw loadError }
430
- return container
431
- }
432
-
433
- @Test func createAndFetchTrip() throws {
434
- let container = try makeTestContainer()
435
- let context = container.viewContext
436
-
437
- let trip = CDTrip(context: context)
438
- trip.name = "Test Trip"
439
- trip.startDate = .now
440
- try context.save()
350
+ enum TestModel {
351
+ nonisolated(unsafe) static let shared: NSManagedObjectModel = { // loaded once, never mutated
352
+ let compiled = Bundle(for: LibraryStore.self).url(forResource: "Library", withExtension: "momd")
353
+ return compiled.flatMap(NSManagedObjectModel.init(contentsOf:)) ?? NSManagedObjectModel()
354
+ }()
355
+ }
441
356
 
442
- let request: NSFetchRequest<CDTrip> = CDTrip.fetchRequest()
443
- let trips = try context.fetch(request)
444
- #expect(trips.count == 1)
445
- #expect(trips.first?.name == "Test Trip")
446
- }
357
+ func makeMemoryContainer() throws -> NSPersistentContainer {
358
+ let container = NSPersistentContainer(name: "Library", managedObjectModel: TestModel.shared)
359
+ let memory = NSPersistentStoreDescription()
360
+ memory.type = NSInMemoryStoreType
361
+ container.persistentStoreDescriptions = [memory]
362
+ var failure: Error?
363
+ container.loadPersistentStores { _, problem in failure = problem }
364
+ if let failure { throw failure }
365
+ return container
447
366
  }
448
- ```
449
367
 
450
- **Tips:**
451
- - Share the `NSManagedObjectModel` instance across tests to avoid "duplicate
452
- entity" warnings.
453
- - Use a single shared model loaded once:
368
+ @Test func savingABookPersistsIt() throws {
369
+ let context = try makeMemoryContainer().viewContext
370
+ let book = Book(context: context)
371
+ book.title = "Dune"
372
+ try context.save()
454
373
 
455
- ```swift
456
- private let sharedModel: NSManagedObjectModel = {
457
- let url = Bundle.main.url(forResource: "MyAppModel", withExtension: "momd")!
458
- return NSManagedObjectModel(contentsOf: url)!
459
- }()
460
-
461
- func makeTestContainer() throws -> NSPersistentContainer {
462
- let container = NSPersistentContainer(name: "MyAppModel",
463
- managedObjectModel: sharedModel)
464
- // ... configure in-memory store
374
+ let fetched = try context.fetch(Book.fetchRequest())
375
+ #expect(fetched.count == 1)
376
+ #expect(fetched.first?.title == "Dune")
465
377
  }
466
378
  ```
467
379
 
468
- ## Common Mistakes
380
+ Load the model once and pass the same `NSManagedObjectModel` to every test
381
+ container; loading it per test produces "multiple entity descriptions claim"
382
+ duplicate-entity warnings.
383
+
384
+ ## Common mistakes
469
385
 
470
386
  | Mistake | Fix |
471
- |---------|-----|
472
- | Passing `NSManagedObject` across threads | Pass `objectID` and re-fetch in the target context |
473
- | Forgetting to merge batch operation results | Call `mergeChanges(fromRemoteContextSave:into:)` |
474
- | Calling `save()` without checking `hasChanges` | Guard with `context.hasChanges` first |
475
- | Using deprecated `init(concurrencyType:)` confinement type | Use `.privateQueueConcurrencyType` or `.mainQueueConcurrencyType` |
476
- | Not setting `mergePolicy` on `viewContext` | Set `NSMergeByPropertyObjectTrumpMergePolicy` to avoid conflict crashes |
477
- | Modifying fetch request on live `NSFetchedResultsController` without deleting cache | Call `deleteCache(withName:)` first or use `cacheName: nil` |
478
- | Batch delete ignoring Deny delete rule | Batch delete bypasses delete rules; validate manually |
479
- | Marking `NSManagedObject` as `@unchecked Sendable` | Do not. Pass `objectID` instead |
480
- | Pointing SwiftData at a fresh store during coexistence | Use the existing store URL and compatible schema when SwiftData should share or migrate Core Data data |
481
-
482
- ## Review Checklist
483
-
484
- - [ ] `NSPersistentContainer` is initialized once and shared
485
- - [ ] `viewContext` used only on main queue; background contexts for writes
486
- - [ ] `perform(_:)` or `performAndWait(_:)` wraps all off-queue context access
487
- - [ ] `automaticallyMergesChangesFromParent` set on `viewContext`
488
- - [ ] `mergePolicy` set on `viewContext` to prevent conflict crashes
489
- - [ ] Batch operation results merged into relevant contexts
490
- - [ ] `NSFetchedResultsController` fetch requests have sort descriptors
491
- - [ ] Persistent history tracking enabled for multi-target apps
492
- - [ ] Core Data + SwiftData handoff preserves store URL, schema compatibility, entity/property names, and rename mappings
493
- - [ ] Tests use in-memory stores with shared `NSManagedObjectModel`
494
- - [ ] No `NSManagedObject` instances cross thread boundaries
387
+ |---|---|
388
+ | Handing an `NSManagedObject` to another thread | Send `objectID` and fetch again |
389
+ | Batch results never merged | `NSManagedObjectContext.mergeChanges(fromRemoteContextSave:into:)` |
390
+ | `save()` with nothing to save | Check `context.hasChanges` first |
391
+ | The deprecated confinement type passed to `init(concurrencyType:)` | Pick `.privateQueueConcurrencyType` or `.mainQueueConcurrencyType` |
392
+ | No `mergePolicy` on `viewContext` | `NSMergeByPropertyObjectTrumpMergePolicy`, avoiding merge-conflict failures |
393
+ | Editing a live FRC request without clearing its cache | `deleteCache(withName:)` or `cacheName: nil` |
394
+ | Relying on the Deny rule with batch delete | Batch delete ignores it; validate manually |
395
+ | `@unchecked Sendable` on managed objects | Remove it; pass `objectID` |
396
+ | SwiftData opening a fresh, empty file while sharing data with Core Data | Reuse the current store URL and keep the schema compatible |
397
+
398
+ ## Review checklist
399
+
400
+ - [ ] One `NSPersistentContainer`, created once and shared
401
+ - [ ] `viewContext` used only on the main queue; writes on background contexts
402
+ - [ ] Every off-queue access wrapped in `perform` or `performAndWait`
403
+ - [ ] `automaticallyMergesChangesFromParent` on `viewContext`
404
+ - [ ] `mergePolicy` on `viewContext`
405
+ - [ ] Batch request results merged
406
+ - [ ] FRC fetch requests sorted
407
+ - [ ] Persistent history enabled when several targets share the store
408
+ - [ ] SwiftData hand-off keeps the store URL, schema, entity and property names, and rename mappings
409
+ - [ ] Test containers are in-memory and share a single loaded model
410
+ - [ ] No `NSManagedObject` crosses a thread boundary
495
411
 
496
412
  ## References
497
413
 
498
- - Apple docs: [Core Data](https://sosumi.ai/documentation/coredata) | [NSPersistentContainer](https://sosumi.ai/documentation/coredata/nspersistentcontainer) | [NSFetchedResultsController](https://sosumi.ai/documentation/coredata/nsfetchedresultscontroller) | [NSStagedMigrationManager](https://sosumi.ai/documentation/coredata/nsstagedmigrationmanager)
414
+ - [Core Data](https://developer.apple.com/documentation/coredata)
415
+ - [NSPersistentContainer](https://developer.apple.com/documentation/coredata/nspersistentcontainer)
416
+ - [NSManagedObjectContext](https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext)
417
+ - [NSFetchedResultsController](https://developer.apple.com/documentation/coredata/nsfetchedresultscontroller)
418
+ - [NSBatchInsertRequest](https://developer.apple.com/documentation/coredata/nsbatchinsertrequest)
419
+ - [NSBatchDeleteRequest](https://developer.apple.com/documentation/coredata/nsbatchdeleterequest)
420
+ - [NSPersistentHistoryChangeRequest](https://developer.apple.com/documentation/coredata/nspersistenthistorychangerequest)
421
+ - [NSStagedMigrationManager](https://developer.apple.com/documentation/coredata/nsstagedmigrationmanager)
422
+ - [NSCompositeAttributeDescription](https://developer.apple.com/documentation/coredata/nscompositeattributedescription)