@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,76 +1,62 @@
1
1
  ---
2
2
  name: callkit-voip
3
- description: "Implement VoIP calling with CallKit and PushKit. Use when building incoming/outgoing call flows, registering for VoIP push notifications, configuring CXProvider and CXCallController, handling call actions, coordinating audio sessions, or creating Call Directory extensions for caller ID and call blocking."
3
+ description: "CallKit and PushKit VoIP calling: CXProvider and CXCallController, incoming and outgoing call flows, call actions, VoIP push registration and reporting, call audio sessions, Call Directory caller ID and blocking. Use when implementing VoIP calls or VoIP push handling."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
7
 
8
- # CallKit
8
+ # CallKit and PushKit VoIP
9
9
 
10
- Build VoIP calling features that integrate with the native iOS call UI using
11
- CallKit and PushKit. Covers incoming/outgoing call flows, VoIP push
12
- registration, audio session coordination, and call directory extensions.
13
- Targets Swift 6.3 / iOS 26+.
10
+ CallKit puts an app's VoIP calls into the same system call UI that cellular
11
+ calls use: the lock-screen answer slider, the in-call screen, Recents and
12
+ Bluetooth controls. PushKit wakes the app when a call arrives. Everything here
13
+ targets Swift 6.3 and iOS 26 or later unless a line names another release.
14
14
 
15
- ## Contents
16
-
17
- - [Setup](#setup)
18
- - [Provider Configuration](#provider-configuration)
19
- - [Incoming Call Flow](#incoming-call-flow)
20
- - [Outgoing Call Flow](#outgoing-call-flow)
21
- - [PushKit VoIP Registration](#pushkit-voip-registration)
22
- - [Audio Session Coordination](#audio-session-coordination)
23
- - [Call Directory Extension and Manager](#call-directory-extension-and-manager)
24
- - [Common Mistakes](#common-mistakes)
25
- - [Review Checklist](#review-checklist)
26
- - [References](#references)
15
+ Rotating APNs auth keys and everyday remote-notification registration live
16
+ in `push-notifications`. This skill starts at the VoIP push type.
27
17
 
28
18
  ## Setup
29
19
 
30
- ### Project Configuration
20
+ ### Project configuration
31
21
 
32
- 1. Enable the **Voice over IP** background mode in Signing & Capabilities
33
- 2. Add the **Push Notifications** capability
34
- 3. For call directory extensions, add a **Call Directory Extension** target
22
+ 1. Signing & Capabilities: turn on Background Modes and tick **Voice over IP**.
23
+ 2. Push Notifications must also appear in the capability list.
24
+ 3. Caller ID or blocking lists need a separate target of type Call Directory Extension.
35
25
 
36
- ### Key Types
26
+ ### The types you touch
37
27
 
38
- | Type | Role |
39
- |---|---|
40
- | `CXProvider` | Reports calls to the system, receives call actions |
41
- | `CXCallController` | Requests call actions (start, end, hold, mute) |
42
- | `CXCallUpdate` | Describes call metadata (caller name, video, handle) |
43
- | `CXProviderDelegate` | Handles system call actions and audio session events |
44
- | `PKPushRegistry` | Registers for and receives VoIP push notifications |
45
- | `PKVoIPPushMetadata` | iOS 26.4+ metadata that says whether a VoIP push must be reported |
28
+ | Type | Job |
29
+ | --- | --- |
30
+ | `CXProvider` | Tells the system about calls and receives the actions the system wants performed |
31
+ | `CXCallController` | Asks the system to perform an action: start, end, hold, mute |
32
+ | `CXCallUpdate` | Describes a call: caller name, handle, video |
33
+ | `CXProviderDelegate` | Performs call actions and receives audio session activation |
34
+ | `PKPushRegistry` | Registers for VoIP pushes and delivers them |
35
+ | `PKVoIPPushMetadata` | iOS 26.4+: says whether a given VoIP push has to be reported |
46
36
 
47
- ## Provider Configuration
37
+ ## Provider configuration
48
38
 
49
- Create a single `CXProvider` at app launch and keep it alive for the app
50
- lifetime. Configure it with a `CXProviderConfiguration` that describes your
51
- calling capabilities.
39
+ Build one `CXProvider` when the app launches and keep that instance until the
40
+ process dies. Creating a second provider or recreating it per call confuses the
41
+ system about which calls exist.
52
42
 
53
43
  ```swift
54
44
  import CallKit
55
45
 
56
- /// CXProvider dispatches all delegate calls to the queue passed to `setDelegate(_:queue:)`.
57
- /// The `let` properties are initialized once and never mutated, making this type
58
- /// safe to share across concurrency domains despite @unchecked Sendable.
59
- final class CallManager: NSObject, @unchecked Sendable {
60
- static let shared = CallManager()
46
+ @MainActor
47
+ final class PhoneLine: NSObject {
48
+ static let shared = PhoneLine()
61
49
 
62
50
  let provider: CXProvider
63
- let callController = CXCallController()
51
+ let controller = CXCallController()
64
52
 
65
- private override init() {
53
+ override init() {
66
54
  let config = CXProviderConfiguration()
67
- config.localizedName = "My VoIP App"
68
55
  config.supportsVideo = true
69
- config.maximumCallsPerCallGroup = 1
70
56
  config.maximumCallGroups = 2
57
+ config.maximumCallsPerCallGroup = 1
71
58
  config.supportedHandleTypes = [.phoneNumber, .emailAddress]
72
59
  config.includesCallsInRecents = true
73
-
74
60
  provider = CXProvider(configuration: config)
75
61
  super.init()
76
62
  provider.setDelegate(self, queue: nil)
@@ -78,204 +64,188 @@ final class CallManager: NSObject, @unchecked Sendable {
78
64
  }
79
65
  ```
80
66
 
81
- ## Incoming Call Flow
67
+ `PhoneLine` is `@MainActor` because every delegate callback arrives on the
68
+ queue passed to `setDelegate(_:queue:)`, and `nil` means the main queue. The
69
+ delegate conformance below is `@preconcurrency`, so those callbacks run as
70
+ main-actor code and the `Task` each one starts inherits that isolation. That is
71
+ what lets a `Task` hold on to the non-`Sendable` `CXAction` and `CXProvider`
72
+ it was handed; from a nonisolated method Swift 6 rejects the same capture with
73
+ "passing closure as a 'sending' parameter risks causing data races". The name the
74
+ system shows comes from `localizedName`; on current SDKs it is read from the
75
+ bundle display name, and the old `init(localizedName:)` is deprecated.
82
76
 
83
- When a required VoIP call push arrives, report the incoming call to CallKit
84
- immediately. The system displays the native call UI. You must report required
85
- calls before the PushKit completion handler returns -- failure to do so causes
86
- the system to terminate your app.
77
+ ## Incoming call flow
78
+
79
+ When a VoIP push says a call is ringing, report it to CallKit right away; the
80
+ system then draws the native incoming-call UI. For a push that must be
81
+ reported, the report has to happen before the PushKit completion handler runs,
82
+ or the system kills the app.
87
83
 
88
84
  ```swift
89
- func reportIncomingCall(
90
- uuid: UUID,
91
- handle: String,
92
- hasVideo: Bool
93
- ) async throws {
94
- let update = CXCallUpdate()
95
- update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
96
- update.hasVideo = hasVideo
97
- update.localizedCallerName = "Jane Doe"
98
-
99
- try await withCheckedThrowingContinuation {
100
- (continuation: CheckedContinuation<Void, Error>) in
101
- provider.reportNewIncomingCall(
102
- with: uuid,
103
- update: update
104
- ) { error in
105
- if let error {
106
- continuation.resume(throwing: error)
107
- } else {
108
- continuation.resume()
85
+ extension PhoneLine {
86
+ func announceIncoming(id: UUID, number: String, name: String, video: Bool) async throws {
87
+ let update = CXCallUpdate()
88
+ update.remoteHandle = CXHandle(type: .phoneNumber, value: number.trimmingCharacters(in: .whitespaces))
89
+ update.localizedCallerName = name
90
+ update.hasVideo = video
91
+
92
+ try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
93
+ provider.reportNewIncomingCall(with: id, update: update) { error in
94
+ if let error { cont.resume(throwing: error) } else { cont.resume() }
109
95
  }
110
96
  }
111
97
  }
112
98
  }
113
99
  ```
114
100
 
115
- ### Handling the Answer Action
101
+ The report throws when Do Not Disturb or the user's block list filters the call.
102
+ That is a normal outcome, not a crash path.
116
103
 
117
- Implement `CXProviderDelegate` to respond when the user answers:
104
+ ### Answering and ending
118
105
 
119
106
  ```swift
120
- extension CallManager: CXProviderDelegate {
121
- func providerDidReset(_ provider: CXProvider) {
122
- // End all calls, reset audio
107
+ extension PhoneLine: @preconcurrency CXProviderDelegate {
108
+ func providerDidReset(_ cx: CXProvider) {
109
+ MediaSession.shared.hangUpEverything()
110
+ AudioRoute.tearDown()
123
111
  }
124
112
 
125
- func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
126
- // Prepare audio, then fulfill only after the call is actually ready
127
- configureAudioSession()
128
- connectToCallServer(callUUID: action.callUUID) { success in
129
- if success {
130
- action.fulfill()
131
- } else {
132
- provider.reportCall(
133
- with: action.callUUID,
134
- endedAt: Date(),
135
- reason: .failed
136
- )
137
- action.fail()
113
+ func provider(_ cx: CXProvider, perform answer: CXAnswerCallAction) {
114
+ AudioRoute.prepareForCall()
115
+ Task {
116
+ do {
117
+ try await SignalingClient.shared.join(call: answer.callUUID)
118
+ answer.fulfill()
119
+ } catch {
120
+ cx.reportCall(with: answer.callUUID, endedAt: .now, reason: .failed)
121
+ answer.fail()
138
122
  }
139
123
  }
140
124
  }
141
125
 
142
- func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
143
- disconnectFromCallServer(callUUID: action.callUUID)
144
- action.fulfill()
126
+ func provider(_ cx: CXProvider, perform hangup: CXEndCallAction) {
127
+ Task {
128
+ await SignalingClient.shared.leave(call: hangup.callUUID)
129
+ hangup.fulfill()
130
+ }
145
131
  }
146
132
  }
147
133
  ```
148
134
 
149
- ## Outgoing Call Flow
135
+ `providerDidReset(_:)` means the system dropped every call: end all of them and
136
+ reset audio. The answer action stays pending until the media server connection
137
+ is up; only then is it fulfilled. If the connection fails, report the call as
138
+ ended with `.failed` and fail the action.
139
+
140
+ ## Outgoing call flow
150
141
 
151
- Use `CXCallController` to request an outgoing call. The system routes the
152
- request through your `CXProviderDelegate`.
142
+ Outgoing calls start with a request to `CXCallController`. The system checks it
143
+ and hands it back through `provider(_:perform:)` with a `CXStartCallAction`.
153
144
 
154
145
  ```swift
155
- func startOutgoingCall(handle: String, hasVideo: Bool) {
156
- let uuid = UUID()
157
- let handle = CXHandle(type: .phoneNumber, value: handle)
158
- let startAction = CXStartCallAction(call: uuid, handle: handle)
159
- startAction.isVideo = hasVideo
160
-
161
- let transaction = CXTransaction(action: startAction)
162
- callController.request(transaction) { error in
163
- if let error {
164
- print("Failed to start call: \(error)")
146
+ extension PhoneLine {
147
+ func dial(_ address: String, video: Bool) -> UUID {
148
+ let id = UUID()
149
+ let start = CXStartCallAction(call: id, handle: CXHandle(type: .phoneNumber, value: address))
150
+ start.isVideo = video
151
+ controller.request(CXTransaction(action: start)) { error in
152
+ if let error { Log.calls.error("dial refused: \(error)") }
165
153
  }
154
+ return id
166
155
  }
167
- }
168
- ```
169
156
 
170
- ### Delegate Methods for Outgoing Calls
171
-
172
- ```swift
173
- extension CallManager {
174
- func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
175
- configureAudioSession()
176
- // Begin connecting to server
177
- provider.reportOutgoingCall(
178
- with: action.callUUID,
179
- startedConnectingAt: Date()
180
- )
181
-
182
- connectToServer(callUUID: action.callUUID) {
183
- provider.reportOutgoingCall(
184
- with: action.callUUID,
185
- connectedAt: Date()
186
- )
157
+ func provider(_ cx: CXProvider, perform dial: CXStartCallAction) {
158
+ AudioRoute.prepareForCall()
159
+ let id = dial.callUUID
160
+ cx.reportOutgoingCall(with: id, startedConnectingAt: .now)
161
+ Task {
162
+ do {
163
+ try await SignalingClient.shared.place(call: id, to: dial.handle.value)
164
+ cx.reportOutgoingCall(with: id, connectedAt: .now)
165
+ dial.fulfill()
166
+ } catch {
167
+ dial.fail()
168
+ }
187
169
  }
188
- action.fulfill()
189
170
  }
190
171
  }
191
172
  ```
192
173
 
193
- ## PushKit VoIP Registration
174
+ Report both `startedConnectingAt` and `connectedAt`; Recents and the call timer
175
+ use them.
176
+
177
+ ## PushKit VoIP registration
178
+
179
+ Create the registry on every launch, not lazily, and forward each token change
180
+ to your server.
181
+
182
+ Report rules:
194
183
 
195
- Register for VoIP pushes at every app launch and send token changes to your
196
- server. For iOS 13 SDK+ apps, every report-required VoIP call push must be
197
- reported before PushKit completion using CallKit, or LiveCommunicationKit for
198
- apps built on that framework. On iOS 26.4+, `PKVoIPPushMetadata.mustReport` is
199
- the gate: `true` means report before completion; `false` means no CallKit or
200
- LiveCommunicationKit report is required. Missing a required report before
201
- completion can terminate the app, and repeated failures may stop VoIP delivery.
184
+ - Linked against SDK 13 or newer: every VoIP push that is a call must be
185
+ reported to CallKit (LiveCommunicationKit apps report there instead)
186
+ before the PushKit completion handler is called.
187
+ - iOS 26.4 and later: `PKVoIPPushMetadata.mustReport` decides. When it is `true`,
188
+ the report precedes completion; when `false`, that push can be handled
189
+ without any report at all.
190
+ - A skipped required report may end the process; keep skipping and the
191
+ system may stop delivering VoIP pushes altogether.
202
192
 
203
- | Path | Report decision | Completion timing |
204
- |---|---|---|
205
- | iOS 26.4+ `mustReport == true` | Report with CallKit or LiveCommunicationKit | After report callback |
206
- | iOS 26.4+ `mustReport == false` | No CallKit/LiveCommunicationKit report required | After local handling |
207
- | Older delegate | iOS 13 SDK+ treats VoIP call pushes as report-required | After report callback |
193
+ | Delivery path | Report? | When to call completion |
194
+ | --- | --- | --- |
195
+ | 26.4+ delegate, `mustReport == true` | Yes | After the report callback |
196
+ | 26.4+ delegate, `mustReport == false` | No | After local handling |
197
+ | Older delegate | Treat every VoIP call push as required | After the report callback |
208
198
 
209
199
  ```swift
210
200
  import PushKit
211
201
 
212
- final class PushManager: NSObject, PKPushRegistryDelegate {
213
- let registry: PKPushRegistry
202
+ @MainActor
203
+ final class VoIPInbox: NSObject, @preconcurrency PKPushRegistryDelegate {
204
+ private let registry = PKPushRegistry(queue: .main)
214
205
 
215
- override init() {
216
- registry = PKPushRegistry(queue: .main)
217
- super.init()
206
+ func start() {
218
207
  registry.delegate = self
219
208
  registry.desiredPushTypes = [.voIP]
220
209
  }
221
210
 
222
- func pushRegistry(
223
- _ registry: PKPushRegistry,
224
- didUpdate pushCredentials: PKPushCredentials,
225
- for type: PKPushType
226
- ) {
227
- let token = pushCredentials.token
228
- .map { String(format: "%02x", $0) }
229
- .joined()
230
- // Send token to your server
231
- sendTokenToServer(token)
211
+ func pushRegistry(_ reg: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
212
+ let hex = credentials.token.reduce(into: "") { out, byte in out += String(format: "%02x", byte) }
213
+ Backend.shared.upload(voipToken: hex)
232
214
  }
233
215
 
234
216
  @available(iOS 26.4, *)
235
- func pushRegistry(
236
- _ registry: PKPushRegistry,
237
- didReceiveIncomingVoIPPushWith payload: PKPushPayload,
238
- metadata: PKVoIPPushMetadata,
239
- withCompletionHandler completion: @escaping @Sendable () -> Void
240
- ) {
217
+ func pushRegistry(_ reg: PKPushRegistry,
218
+ didReceiveIncomingVoIPPushWith payload: PKPushPayload,
219
+ metadata: PKVoIPPushMetadata,
220
+ withCompletionHandler completion: @escaping @Sendable () -> Void) {
241
221
  guard metadata.mustReport else {
242
222
  completion()
243
223
  return
244
224
  }
245
- handleIncomingVoIPPush(payload, completion: completion)
225
+ ring(from: payload, then: completion)
246
226
  }
247
227
 
248
- // Keep the older callback for iOS 26.0-26.3 and older deployment targets.
249
- func pushRegistry(
250
- _ registry: PKPushRegistry,
251
- didReceiveIncomingPushWith payload: PKPushPayload,
252
- for type: PKPushType,
253
- completion: @escaping () -> Void
254
- ) {
228
+ func pushRegistry(_ reg: PKPushRegistry,
229
+ didReceiveIncomingPushWith payload: PKPushPayload,
230
+ for type: PKPushType,
231
+ completion: @escaping () -> Void) {
255
232
  guard type == .voIP else {
256
233
  completion()
257
234
  return
258
235
  }
259
-
260
- handleIncomingVoIPPush(payload, completion: completion)
236
+ ring(from: payload, then: completion)
261
237
  }
262
238
 
263
- private func handleIncomingVoIPPush(
264
- _ payload: PKPushPayload,
265
- completion: @escaping () -> Void
266
- ) {
267
- let callUUID = UUID()
268
- let handle = payload.dictionaryPayload["handle"] as? String ?? "Unknown"
269
-
239
+ private func ring(from payload: PKPushPayload, then completion: @escaping () -> Void) {
240
+ let body = payload.dictionaryPayload
241
+ let number = body["from"] as? String ?? "Unknown"
242
+ let name = body["display"] as? String ?? number
243
+ let video = body["video"] as? Bool ?? false
270
244
  Task {
271
245
  do {
272
- try await CallManager.shared.reportIncomingCall(
273
- uuid: callUUID,
274
- handle: handle,
275
- hasVideo: false
276
- )
246
+ try await PhoneLine.shared.announceIncoming(id: UUID(), number: number, name: name, video: video)
277
247
  } catch {
278
- // Call was filtered by DND or block list
248
+ Log.calls.notice("call filtered or rejected: \(error)")
279
249
  }
280
250
  completion()
281
251
  }
@@ -283,218 +253,167 @@ final class PushManager: NSObject, PKPushRegistryDelegate {
283
253
  }
284
254
  ```
285
255
 
286
- Server-side VoIP pushes should use a short lifetime: set `apns-expiration` to
287
- `0` or only a few seconds. After the initial push wakes the app, send hangups
288
- and call-detail changes over the app-server connection instead of sending more
289
- VoIP pushes.
256
+ Keep the older delegate method for iOS 26.0 through 26.3 and for lower
257
+ deployment targets. The registry delivers on `.main`, which is why the inbox is
258
+ `@MainActor`: the `Task` in `ring` can then carry the non-`Sendable` completion
259
+ handler without leaving the actor. Completion runs after the report attempt on both the
260
+ success and the error path.
290
261
 
291
- ## Audio Session Coordination
262
+ Server side: send VoIP pushes with `apns-expiration: 0` or a few seconds; a
263
+ ring that arrives a minute late is worse than none. After the first push has
264
+ woken the app, hangups and changed call details travel over your own socket or
265
+ API channel instead of further VoIP pushes.
292
266
 
293
- CallKit manages audio session activation/deactivation. Configure your audio
294
- session when CallKit tells you to, not before.
295
- Review answers should name both sides: start media only in
296
- `provider(_:didActivate:)`, and stop/tear down media in
297
- `provider(_:didDeactivate:)` or reset paths.
267
+ ## Audio session coordination
298
268
 
299
- ```swift
300
- extension CallManager {
301
- func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
302
- // Audio session is now active -- start audio engine / WebRTC
303
- startAudioEngine()
304
- }
269
+ CallKit owns activation and deactivation of the audio session. Set the category
270
+ while preparing the call, and wait for CallKit before touching media.
305
271
 
306
- func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
307
- // Audio session deactivated -- stop audio engine
308
- stopAudioEngine()
309
- }
272
+ ```swift
273
+ import AVFAudio
310
274
 
311
- func configureAudioSession() {
312
- let session = AVAudioSession.sharedInstance()
275
+ enum AudioRoute {
276
+ static func prepareForCall() {
313
277
  do {
314
- try session.setCategory(
315
- .playAndRecord,
316
- mode: .voiceChat,
317
- options: [.allowBluetooth, .allowBluetoothA2DP]
318
- )
278
+ try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .voiceChat,
279
+ options: [.allowBluetoothHFP, .allowBluetoothA2DP])
319
280
  } catch {
320
- print("Audio session configuration failed: \(error)")
281
+ Log.calls.error("audio category failed: \(error)")
321
282
  }
322
283
  }
323
- }
324
- ```
325
-
326
- ## Call Directory Extension and Manager
327
284
 
328
- Use Call Directory for preloaded caller ID/blocking, not per-call API lookup.
329
- The extension loads sorted bulk data in `beginRequest(with:)`; the main app uses
330
- `CXCallDirectoryManager` to check enabled status, open Call Blocking &
331
- Identification settings when disabled, and reload after data changes. Store
332
- `CXCallDirectoryPhoneNumber` as country code plus digits in ascending order
333
- (for example `18005551234`), not a formatted string.
334
-
335
- ```swift
336
- import CallKit
285
+ static func tearDown() { MediaSession.shared.stopEngine() }
286
+ }
337
287
 
338
- final class CallDirectoryHandler: CXCallDirectoryProvider {
339
- override func beginRequest(
340
- with context: CXCallDirectoryExtensionContext
341
- ) {
342
- if context.isIncremental {
343
- addOrRemoveIncrementalEntries(to: context)
344
- } else {
345
- addAllEntries(to: context)
346
- }
347
- context.completeRequest()
288
+ extension PhoneLine {
289
+ func provider(_ cx: CXProvider, didActivate session: AVAudioSession) {
290
+ MediaSession.shared.startEngine()
348
291
  }
349
292
 
350
- private func addAllEntries(
351
- to context: CXCallDirectoryExtensionContext
352
- ) {
353
- // Country code + digits, sorted in ascending order
354
- let blockedNumbers: [CXCallDirectoryPhoneNumber] = [
355
- 18005551234, 18005555678
356
- ]
357
- for number in blockedNumbers {
358
- context.addBlockingEntry(
359
- withNextSequentialPhoneNumber: number
360
- )
361
- }
362
-
363
- let identifiedNumbers: [(CXCallDirectoryPhoneNumber, String)] = [
364
- (18005551111, "Local Pizza"),
365
- (18005552222, "Dentist Office")
366
- ]
367
- for (number, label) in identifiedNumbers {
368
- context.addIdentificationEntry(
369
- withNextSequentialPhoneNumber: number,
370
- label: label
371
- )
372
- }
293
+ func provider(_ cx: CXProvider, didDeactivate session: AVAudioSession) {
294
+ MediaSession.shared.stopEngine()
373
295
  }
374
296
  }
375
297
  ```
376
298
 
377
- ### Main-App Manager: Status, Settings, Reload
299
+ Media starts in `provider(_:didActivate:)` and stops in
300
+ `provider(_:didDeactivate:)` or a reset path. A review should point at both
301
+ callbacks by name.
302
+
303
+ ## Call Directory extension and manager
304
+
305
+ A Call Directory supplies caller ID labels and blocked numbers that are loaded
306
+ in advance. It cannot look anything up while a call is ringing.
378
307
 
379
308
  ```swift
380
- let manager = CXCallDirectoryManager.sharedInstance
381
- manager.getEnabledStatusForExtension(withIdentifier: extensionID) { status, _ in
382
- guard status == .enabled else {
383
- manager.openSettings { _ in } // Call Blocking & Identification
384
- return
309
+ import CallKit
310
+
311
+ final class SpamListProvider: CXCallDirectoryProvider {
312
+ override func beginRequest(with ctx: CXCallDirectoryExtensionContext) {
313
+ if ctx.isIncremental {
314
+ applyChanges(to: ctx)
315
+ } else {
316
+ for number in SharedStore.blocked().sorted() {
317
+ ctx.addBlockingEntry(withNextSequentialPhoneNumber: number)
318
+ }
319
+ for entry in SharedStore.labels().sorted(by: { $0.key < $1.key }) {
320
+ ctx.addIdentificationEntry(withNextSequentialPhoneNumber: entry.key, label: entry.value)
321
+ }
322
+ }
323
+ ctx.completeRequest()
385
324
  }
386
- manager.reloadExtension(withIdentifier: extensionID) { _ in }
387
325
  }
388
326
  ```
389
327
 
390
- Check `getEnabledStatusForExtension(...)` before assuming the extension is
391
- active, use `openSettings(...)` for Call Blocking & Identification when
392
- disabled, and call `reloadExtension(...)` after data changes. Route APNs
393
- auth-key rotation and normal remote-notification setup to push-notifications.
394
-
395
- ## Common Mistakes
396
-
397
- ### DON'T: Fail to report a required call on VoIP push receipt
398
-
399
- Follow the PushKit report rules above: iOS 13 SDK+ apps must report
400
- report-required VoIP call pushes before completion, and on iOS 26.4+
401
- `PKVoIPPushMetadata.mustReport` identifies which pushes are required. Missing a
402
- required report can terminate the app; repeated failures may stop VoIP delivery.
403
-
404
- Do not treat a required VoIP push as a data-only notification. Report the call
405
- to CallKit and call the PushKit completion handler from the report completion.
406
-
407
- ### DON'T: Fulfill answer before the call is connected
408
-
409
- When the user answers before your app has established the server/media
410
- connection, leave the `CXAnswerCallAction` pending while connecting. Fulfill it
411
- after the call is ready; if connection fails, fail the action and report the
412
- call ended with `.failed`.
413
-
414
- ### DON'T: Start audio before CallKit activates the session
415
-
416
- Starting your audio engine before `provider(_:didActivate:)` causes silence
417
- or immediate deactivation. CallKit manages session priority with the system.
418
-
419
- Prepare audio in the answer/start action if needed, then start media only from
420
- `provider(_:didActivate:)`.
421
-
422
- For iOS 26 call translation, set `CXProviderConfiguration.supportsAudioTranslation`
423
- when your service supports it and handle `CXSetTranslatingCallAction`. If a
424
- person mutes during a translated call, mute app input with
425
- `CXSetMutedCallAction`; do not deactivate upstream audio that translated audio
426
- depends on.
427
-
428
- For encrypted VoIP metadata, use `CXProvider.reportNewIncomingVoIPPushPayload`
429
- only from a notification service extension when the server cannot determine
430
- whether encrypted content is a VoIP call or other data. That path requires the
431
- `com.apple.developer.usernotifications.filtering` entitlement; otherwise send a
432
- normal PushKit VoIP push.
433
-
434
- ### DON'T: Forget to call action.fulfill() or action.fail()
435
-
436
- Failing to fulfill or fail an action leaves the call in a limbo state and
437
- triggers the timeout handler.
438
-
439
- Every provider action path must eventually call `fulfill()` or `fail()`,
440
- including network-error and cancellation paths.
441
-
442
- ### DON'T: Ignore push token refresh
443
-
444
- The VoIP push token can change at any time. If your server has a stale token,
445
- pushes silently fail and incoming calls never arrive.
446
-
447
- Send the token to your server every time `didUpdate pushCredentials` fires,
448
- not just during first-run onboarding.
449
-
450
- ### DON'T: Use Call Directory for per-call lookup
451
-
452
- Call Directory extensions provide preloaded caller ID and blocking data. They
453
- cannot ask a web service for the incoming caller during call presentation.
454
- Fetch or generate the dataset ahead of time, reload the extension, and add
455
- entries in sorted sequential order.
456
-
457
- ## Review Checklist
458
-
459
- - [ ] VoIP background mode enabled in capabilities
460
- - [ ] Single `CXProvider` instance created at app launch and retained
461
- - [ ] `CXProviderDelegate` set before reporting any calls
462
- - [ ] iOS 26.4+ PushKit path reports when `mustReport` is true and may skip when false
463
- - [ ] iOS 13 SDK+ PushKit VoIP call pushes report to CallKit before completion
464
- - [ ] VoIP APNs requests use `apns-expiration` of `0` or only a few seconds
465
- - [ ] Hangups and detail updates use the app-server connection after the initial push
466
- - [ ] `action.fulfill()` or `action.fail()` called for every provider delegate action
467
- - [ ] `CXAnswerCallAction` fulfilled only after the call server/media connection is ready
468
- - [ ] Audio engine started only after `provider(_:didActivate:)` callback
469
- - [ ] Audio engine stopped in `provider(_:didDeactivate:)` callback
470
- - [ ] Audio session category set to `.playAndRecord` with `.voiceChat` mode
471
- - [ ] VoIP push token sent to server on every `didUpdate pushCredentials` callback
472
- - [ ] `PKPushRegistry` created at every app launch (not lazily)
473
- - [ ] Call Directory data is preloaded, not fetched per incoming call
474
- - [ ] `CXCallDirectoryPhoneNumber` documented as country calling code + digits
475
- - [ ] `CXCallDirectoryManager` names status check, reload, and settings-opening APIs
476
- - [ ] `CXCallUpdate` populated with `localizedCallerName` and `remoteHandle`
477
- - [ ] Outgoing calls report `startedConnectingAt` and `connectedAt` timestamps
478
- - [ ] iOS 26 call translation keeps upstream audio active during mute
479
- - [ ] Encrypted metadata filtering mentions the notification service extension entitlement
328
+ A `CXCallDirectoryPhoneNumber` is an `Int64`: country calling code followed by
329
+ the national digits, for example `14155550123`, never a formatted string.
330
+ Entries go in ascending order.
331
+
332
+ In the containing app, use `CXCallDirectoryManager.sharedInstance`:
333
+
334
+ - `getEnabledStatusForExtension(withIdentifier:)` before assuming the extension
335
+ is on.
336
+ - When the status is not `.enabled`, `openSettings { _ in }` takes the user to
337
+ Call Blocking & Identification.
338
+ - After the dataset changes, `reloadExtension(withIdentifier:)`.
339
+
340
+ Rotating APNs keys and the usual notification registration are covered by
341
+ `push-notifications`.
342
+
343
+ ## Common mistakes
344
+
345
+ - **Treating a call push as data.** A required VoIP call push that is not
346
+ reported ends the process. Hand it to CallKit first and let the report's
347
+ callback be where PushKit's completion runs.
348
+ - **Fulfilling the answer too early.** Keep `CXAnswerCallAction` pending while
349
+ connecting; fulfill once media is ready; on failure, fail it and report the
350
+ call ended with `.failed`.
351
+ - **Starting audio before activation.** Media started before
352
+ `provider(_:didActivate:)` plays silence or gets deactivated at once, because
353
+ CallKit arbitrates session priority. Prepare in the answer or start action,
354
+ play only after activation.
355
+ - **Missing iOS 26 call translation.** Set
356
+ `CXProviderConfiguration.supportsAudioTranslation` when the app supports it
357
+ and handle `CXSetTranslatingCallAction`.
358
+ - **Muting by killing audio.** During a translated call, mute the app's own
359
+ input in `CXSetMutedCallAction`; do not deactivate the upstream audio that
360
+ translation reads.
361
+ - **Overusing encrypted payload reporting.** `CXProvider.reportNewIncomingVoIPPushPayload`
362
+ belongs in a notification service extension, and only when the server cannot
363
+ tell whether encrypted content is a call. If it can tell, send a normal
364
+ PushKit VoIP push. The extension needs the
365
+ entitlement `com.apple.developer.usernotifications.filtering`.
366
+ - **Leaving an action unresolved.** A call action that is neither fulfilled nor
367
+ failed hangs until its timeout fires. Every branch, including network errors
368
+ and cancellation, ends with `fulfill()` or `fail()`.
369
+ - **Caching the push token.** It can change at any moment; a stale server copy
370
+ makes pushes vanish silently. Upload on every `didUpdate` credentials call.
371
+ - **Per-call lookup in Call Directory.** The extension cannot call a web service
372
+ when a call is presented. Fetch ahead, reload, and add sorted sequential
373
+ entries.
374
+
375
+ ## Review checklist
376
+
377
+ - [ ] Voice over IP background mode is on
378
+ - [ ] Exactly one `CXProvider`, created at launch and retained
379
+ - [ ] Provider delegate is set before any call is reported
380
+ - [ ] On 26.4+, a `mustReport` of true triggers a report; false allows skipping it
381
+ - [ ] With SDK 13 or newer, each VoIP call push is reported ahead of completion
382
+ - [ ] `apns-expiration` is 0 or a few seconds
383
+ - [ ] After the waking push, hangups and call details use the app's own server channel
384
+ - [ ] Every delegate action ends in `fulfill()` or `fail()`
385
+ - [ ] Answer is fulfilled only when the server and media connection are ready
386
+ - [ ] Audio engine starts after `didActivate` and stops in `didDeactivate`
387
+ - [ ] Audio category is `.playAndRecord` with mode `.voiceChat`
388
+ - [ ] Token uploaded on every `didUpdate` credentials callback
389
+ - [ ] `PKPushRegistry` created on every launch, not lazily
390
+ - [ ] Call Directory data is preloaded, never fetched per call
391
+ - [ ] `CXCallDirectoryPhoneNumber` described as country calling code plus digits
392
+ - [ ] `CXCallDirectoryManager` status check, reload and settings APIs are named
393
+ - [ ] `CXCallUpdate` sets `localizedCallerName` and `remoteHandle`
394
+ - [ ] Dialed calls send both `startedConnectingAt` and `connectedAt` timestamps
395
+ - [ ] Translated calls keep upstream audio live while muted
396
+ - [ ] Encrypted payload filtering lists the filtering entitlement the extension needs
480
397
 
481
398
  ## References
482
399
 
483
- - Extended patterns (hold, mute, group calls, delegate lifecycle): [references/callkit-patterns.md](references/callkit-patterns.md)
484
- - [CallKit framework](https://sosumi.ai/documentation/callkit)
485
- - [CXProvider](https://sosumi.ai/documentation/callkit/cxprovider)
486
- - [CXCallController](https://sosumi.ai/documentation/callkit/cxcallcontroller)
487
- - [CXCallAction](https://sosumi.ai/documentation/callkit/cxcallaction)
488
- - [CXCallUpdate](https://sosumi.ai/documentation/callkit/cxcallupdate)
489
- - [CXProviderConfiguration](https://sosumi.ai/documentation/callkit/cxproviderconfiguration)
490
- - [CXProviderDelegate](https://sosumi.ai/documentation/callkit/cxproviderdelegate)
491
- - [PKPushRegistry](https://sosumi.ai/documentation/pushkit/pkpushregistry)
492
- - [PKPushRegistryDelegate](https://sosumi.ai/documentation/pushkit/pkpushregistrydelegate)
493
- - [PKVoIPPushMetadata](https://sosumi.ai/documentation/pushkit/pkvoippushmetadata)
494
- - [CXCallDirectoryProvider](https://sosumi.ai/documentation/callkit/cxcalldirectoryprovider)
495
- - [CXCallDirectoryPhoneNumber](https://sosumi.ai/documentation/callkit/cxcalldirectoryphonenumber)
496
- - [CXCallDirectoryManager](https://sosumi.ai/documentation/callkit/cxcalldirectorymanager)
497
- - [CXSetTranslatingCallAction](https://sosumi.ai/documentation/callkit/cxsettranslatingcallaction)
498
- - [reportNewIncomingVoIPPushPayload(_:completion:)](https://sosumi.ai/documentation/callkit/cxprovider/reportnewincomingvoippushpayload(_:completion:))
499
- - [Making and receiving VoIP calls](https://sosumi.ai/documentation/callkit/making-and-receiving-voip-calls)
500
- - [Responding to VoIP Notifications from PushKit](https://sosumi.ai/documentation/pushkit/responding-to-voip-notifications-from-pushkit)
400
+ - [Longer patterns](references/callkit-patterns.md): observable manager, hold
401
+ and mute, concurrent calls, call observer, encrypted filtering, incremental
402
+ Call Directory, local testing
403
+ - [CallKit](https://developer.apple.com/documentation/callkit)
404
+ - [CXProvider](https://developer.apple.com/documentation/callkit/cxprovider)
405
+ - [CXCallController](https://developer.apple.com/documentation/callkit/cxcallcontroller)
406
+ - [CXCallAction](https://developer.apple.com/documentation/callkit/cxcallaction)
407
+ - [CXCallUpdate](https://developer.apple.com/documentation/callkit/cxcallupdate)
408
+ - [CXProviderConfiguration](https://developer.apple.com/documentation/callkit/cxproviderconfiguration)
409
+ - [CXProviderDelegate](https://developer.apple.com/documentation/callkit/cxproviderdelegate)
410
+ - [CXSetTranslatingCallAction](https://developer.apple.com/documentation/callkit/cxsettranslatingcallaction)
411
+ - [reportNewIncomingVoIPPushPayload(_:completion:)](https://developer.apple.com/documentation/callkit/cxprovider/reportnewincomingvoippushpayload(_:completion:))
412
+ - [CXCallDirectoryProvider](https://developer.apple.com/documentation/callkit/cxcalldirectoryprovider)
413
+ - [CXCallDirectoryPhoneNumber](https://developer.apple.com/documentation/callkit/cxcalldirectoryphonenumber)
414
+ - [CXCallDirectoryManager](https://developer.apple.com/documentation/callkit/cxcalldirectorymanager)
415
+ - [PKPushRegistry](https://developer.apple.com/documentation/pushkit/pkpushregistry)
416
+ - [PKPushRegistryDelegate](https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate)
417
+ - [PKVoIPPushMetadata](https://developer.apple.com/documentation/pushkit/pkvoippushmetadata)
418
+ - Article: [VoIP call handling with CallKit](https://developer.apple.com/documentation/callkit/making-and-receiving-voip-calls)
419
+ - Article: [PushKit VoIP notification handling](https://developer.apple.com/documentation/pushkit/responding-to-voip-notifications-from-pushkit)