@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,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)