@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,297 +1,232 @@
1
1
  #!/usr/bin/env python3
2
- """Render a PER-KEY "screenpiece" for every mapping row: a cropped band of the screen
3
- with a RED BOX around exactly that element, so the table's "Ekran Görüntüsü" cell shows
4
- the content team where each key lives on screen (ported from the web skill's
5
- render-key-shots.mjs). No AI image generation - pure geometry + HTML + headless Chrome.
6
-
7
- Input modes (same split as render-overlay.py):
8
- * REST (default) - Figma token (--token / env / keychain FIGMA_ACCESS_TOKEN):
9
- geometry GET /v1/files/<key>/nodes?ids=<frame> → per-text absoluteBoundingBox,
10
- with ANCESTOR VISIBILITY (`visible!=false`) + cumulative opacity tracked,
11
- so hidden component variants with stale bboxes are never boxed;
12
- image GET /v1/images/<key>?ids=<frame>&format=png&scale=<n> → one render,
13
- cropped per key.
14
- * --spec <file> (repeatable; MCP / offline) - the SAME spec shape render-overlay.py
15
- takes: { fileKey?, pages:[{nodeId, image, frame:{w,h}, nodes:[{id, characters,
16
- x,y,w,h}]}] }. `image` may be a data URL **or a file path** (resolved relative to
17
- the spec file) - a committed example spec can reference screenshot.png instead of
18
- inlining base64. Several --spec files = several screen states; each row is boxed
19
- on the FIRST spec/page where its node matches.
20
-
21
- Rows are matched to nodes exactly like the overlay: by `cmsNodeId` first, else by
22
- normalized value (new/cms/legacy tr+en, element label). Every matched row gets a shot;
23
- unmatched rows are listed in the manifest's `missing` (blank cell in the table - never
24
- a guessed crop).
25
-
26
- Output, into --out:
27
- keyshots/keyshot__<NN>__<key>.png one per matched row; NN = the row's 1-based
28
- position in mapping.rows - the SAME number the
29
- table and the overlay cards use.
30
- <slug>.keyshots.manifest.json {shots:[{row,key,element,file,...}], missing:[...]}
31
- build-artifact.py reads this (mapping top-level
32
- `keyshots`) to fill the table cells;
33
- publish-confluence.py --keyshots attaches the PNGs.
34
- No headless Chrome → per-shot `.html` files are written instead (open/print manually);
35
- the manifest marks them `"fallback": "html"`. Read-only against every source.
2
+ """Cut one crop per mapped row, with the element framed in red, for the Screenshot column.
3
+
4
+ A crop keeps the whole frame width and a strip of --pad pixels over and under
5
+ the element, which gets a red outline. Output lands in
6
+ keyshots/keyshot__NN__<key>.png, NN being the row's index in mapping.rows
7
+ counted from 1. A row whose element cannot be found on screen is reported as
8
+ missing and gets no picture; crops are never guessed. Node matching and the
9
+ Figma calls are shared with render-overlay.py, loaded from this folder.
36
10
  """
37
11
  import argparse
38
- import base64
39
12
  import importlib.util
40
13
  import json
41
14
  import os
42
15
  import re
16
+ import subprocess
43
17
  import sys
44
- import tempfile
45
-
46
- sys.dont_write_bytecode = True
47
- _HERE = os.path.dirname(os.path.abspath(__file__))
48
- sys.path.insert(0, _HERE)
49
18
 
50
- # Shared with the overlay renderer on purpose: SAME matching = the shot, the
51
- # overlay card and the table row can never disagree about which node a key is.
52
- # The filename is not a valid identifier, so it loads by path rather than by
53
- # `import`: spec_from_file_location yields a real module object, and no source
54
- # text is evaluated at runtime.
55
- _RO_PATH = os.path.join(_HERE, "render-overlay.py")
56
- _spec = importlib.util.spec_from_file_location("render_overlay", _RO_PATH)
57
- if _spec is None or _spec.loader is None:
58
- sys.exit(f"render-key-shots: cannot load {_RO_PATH}")
59
- _ro = importlib.util.module_from_spec(_spec)
60
- sys.modules["render_overlay"] = _ro
61
- _spec.loader.exec_module(_ro)
19
+ SCRIPT_DIR = os.path.split(os.path.abspath(__file__))[0]
20
+ BOX_COLOUR = "#E81932"
21
+ ID_FIELD = "cmsNodeId"
22
+ INSET = 6
23
+ UNSAFE = re.compile(r"[\\/:*?\"<>|\s]+")
24
+ TRAILING_MARKER = re.compile(r"\s*\([^()]*\)\s*$")
62
25
 
63
- canon_id = _ro.canon_id
64
- slug = _ro.slug
65
- build_index = _ro.build_index
66
- match_row = _ro.match_row
67
- figma_token = _ro.figma_token
68
- http_get_json = _ro.http_get_json
69
- http_get_bytes = _ro.http_get_bytes
70
- find_chrome = _ro.find_chrome
71
- FIGMA_API = _ro.FIGMA_API
72
26
 
73
- RED = "#E81932"
27
+ def load_overlay():
28
+ path = os.path.join(SCRIPT_DIR, "render-overlay.py")
29
+ try:
30
+ spec = importlib.util.spec_from_file_location("render_overlay", path)
31
+ module = importlib.util.module_from_spec(spec)
32
+ spec.loader.exec_module(module)
33
+ except Exception as exc:
34
+ sys.exit(f"cannot load {path}: {exc}")
35
+ return module
74
36
 
75
37
 
76
- def key_file(num, key):
77
- # Drop a trailing "(owned by ...)" marker first: it is provenance, not identity, so
78
- # letting it into the filename churns every PNG (and orphans the old ones) whenever
79
- # a component is renamed or the tag is added. Matches build-spreadsheet's clean_key.
80
- bare = re.sub(r"\s*\(.*\)\s*$", "", key or "").strip()
81
- safe = re.sub(r'[/\\:*?"<>|\s]+', "-", bare or "key").strip("-")
82
- return f"keyshot__{num:02d}__{safe}.png"
38
+ ov = load_overlay()
83
39
 
84
40
 
85
- def build_shot_html(img_src, fw, fh, box, pad):
86
- """Full-frame-width band: `pad` px of context above/below the element, red box on it."""
87
- cy0 = max(0, box["y"] - pad)
88
- cy1 = min(fh, box["y"] + box["h"] + pad)
89
- crop_w, crop_h = fw, cy1 - cy0
90
- bx = max(0, box["x"] - 6)
91
- by = box["y"] - cy0 - 6
92
- bw, bh = box["w"] + 12, box["h"] + 12
93
- html = f"""<!doctype html><html><head><meta charset="utf-8"><style>
94
- *{{margin:0;padding:0;box-sizing:border-box}}
95
- body{{width:{crop_w}px;height:{crop_h}px;overflow:hidden;position:relative;background:#fff}}
96
- img{{position:absolute;left:0;top:{-cy0}px;width:{fw}px;height:{fh}px}}
97
- .box{{position:absolute;left:{bx}px;top:{by}px;width:{bw}px;height:{bh}px;
98
- border:3px solid {RED};border-radius:6px;box-shadow:0 0 0 2px rgba(232,25,50,.15)}}
99
- </style></head><body><img src="{img_src.replace('"', '&quot;')}"/><div class="box"></div></body></html>"""
100
- return html, crop_w, crop_h
41
+ def shot_name(row_number, key):
42
+ clean = UNSAFE.sub("-", TRAILING_MARKER.sub("", key).strip()).strip("-")
43
+ return f"keyshot__{row_number:02d}__{clean}"
101
44
 
102
45
 
103
- def render_shot_png(chrome, html, w, h, out_path, scale):
104
- """One headless-Chrome screenshot. Returns None on success, else an error string."""
105
- import subprocess
106
- with tempfile.NamedTemporaryFile("w", suffix=".html", delete=False, encoding="utf-8") as tf:
107
- tf.write(html)
108
- tmp = tf.name
109
- try:
110
- r = subprocess.run(
111
- [chrome, "--headless", "--disable-gpu", "--hide-scrollbars",
112
- f"--force-device-scale-factor={scale}",
113
- f"--window-size={w},{int(h)}",
114
- f"--screenshot={out_path}", tmp],
115
- capture_output=True, text=True, timeout=120)
116
- if not os.path.isfile(out_path):
117
- return (r.stderr or "no output")[:200]
118
- return None
119
- except Exception as exc: # timeout / spawn failure - fall back per shot, not per run
120
- return str(exc)[:200]
121
- finally:
122
- try:
123
- os.unlink(tmp)
124
- except OSError:
125
- pass
126
-
127
-
128
- def spec_image_src(page, spec_path):
129
- """`image` as a browser src: pass a data URL through; inline a file path (relative
130
- to the spec file) as base64 so the crop HTML stays self-contained for Chrome."""
131
- img = page.get("image") or ""
132
- if img.startswith("data:"):
133
- return img
134
- cand = img if os.path.isabs(img) else os.path.join(os.path.dirname(os.path.abspath(spec_path)), img)
135
- if not os.path.isfile(cand):
136
- sys.exit(f"spec image not found: {cand}")
137
- with open(cand, "rb") as fh:
138
- return "data:image/png;base64," + base64.b64encode(fh.read()).decode("ascii")
46
+ def spec_pages(paths):
47
+ pages = []
48
+ for path in paths:
49
+ spec, loaded = ov.load_spec_pages(path)
50
+ if not loaded:
51
+ sys.exit(f"--spec lists no pages: {path}")
52
+ pages.extend(loaded)
53
+ return pages
139
54
 
140
55
 
141
- def collect_rest_pages(file_key, node_ids, token, ca_file, scale):
142
- """Fetch each frame's VISIBLE text nodes (+ image render) - visibility is ancestor
143
- `visible` AND cumulative opacity, exactly the web port's rule, because a node tree
144
- carries text that is not rendered in this state and its stale bbox would land the
145
- red box on the wrong content."""
146
- headers = {"X-Figma-Token": token}
147
- import urllib.parse
56
+ def visible_text_nodes(document):
57
+ frame = document.get("absoluteBoundingBox") or {}
58
+ fx, fy = frame.get("x", 0), frame.get("y", 0)
59
+ found = []
60
+ pending = [(document, True, 1.0)]
61
+ while pending:
62
+ node, shown, alpha = pending.pop()
63
+ shown = shown and node.get("visible", True) is not False
64
+ alpha = alpha * float(node.get("opacity", 1.0))
65
+ if not shown or alpha <= 0:
66
+ continue
67
+ box = node.get("absoluteBoundingBox")
68
+ text = node.get("characters")
69
+ is_text = node.get("type") == "TEXT"
70
+ if is_text and box and text and text.strip():
71
+ found.append({
72
+ "id": node.get("id"),
73
+ "characters": text,
74
+ "x": round(box["x"] - fx),
75
+ "y": round(box["y"] - fy),
76
+ "w": round(box["width"]),
77
+ "h": round(box["height"]),
78
+ })
79
+ pending.extend((child, shown, alpha) for child in reversed(node.get("children") or []))
80
+ return found
81
+
82
+
83
+ def rest_pages(file_key, node_ids, scale, token, ca_file):
148
84
  pages = []
149
- for nid in node_ids:
150
- data = http_get_json(f"{FIGMA_API}/files/{file_key}/nodes?ids={urllib.parse.quote(nid, safe='')}",
151
- headers, ca_file)
152
- doc = ((data.get("nodes") or {}).get(nid) or {}).get("document")
153
- if not doc:
154
- sys.stderr.write(f"# warning: node {nid} not found - skipped\n")
85
+ for raw in node_ids:
86
+ node_id = ov.canon_id(raw)
87
+ document = ov.fetch_node(file_key, node_id, token, ca_file)
88
+ if not document:
89
+ print(f"# warning: {file_key} has no frame {node_id}; skipping it", file=sys.stderr)
155
90
  continue
156
- fb = doc.get("absoluteBoundingBox") or {"x": 0, "y": 0, "width": 0, "height": 0}
157
- nodes = []
158
-
159
- def walk(n, anc_visible, opacity):
160
- vis = anc_visible and n.get("visible") is not False
161
- op = opacity * (n.get("opacity") if isinstance(n.get("opacity"), (int, float)) else 1)
162
- b = n.get("absoluteBoundingBox")
163
- is_text = n.get("type") == "TEXT" and isinstance(n.get("characters"), str) and n["characters"].strip()
164
- if is_text and b and vis and op > 0:
165
- nodes.append({"id": n.get("id"), "characters": n["characters"],
166
- "x": round(b["x"] - fb["x"]), "y": round(b["y"] - fb["y"]),
167
- "w": round(b["width"]), "h": round(b["height"])})
168
- for c in n.get("children") or []:
169
- walk(c, vis, op)
170
-
171
- walk(doc, True, 1)
172
- img = http_get_json(
173
- f"{FIGMA_API}/images/{file_key}?ids={urllib.parse.quote(nid, safe='')}&format=png&scale={scale}",
174
- headers, ca_file)
175
- url = (img.get("images") or {}).get(nid)
176
- if not url:
177
- sys.stderr.write(f"# warning: no image render for {nid} - skipped\n")
91
+ image = ov.render_image(file_key, node_id, scale, token, ca_file)
92
+ if not image:
93
+ print(f"# warning: Figma returned no render of {node_id}; skipping it", file=sys.stderr)
178
94
  continue
179
- raw = http_get_bytes(url, ca_file)
180
- pages.append({"nodeId": nid,
181
- "image": "data:image/png;base64," + base64.b64encode(raw).decode("ascii"),
182
- "frame": {"w": round(fb.get("width", 0)), "h": round(fb.get("height", 0))},
183
- "nodes": nodes, "_src": "rest"})
95
+ box = document.get("absoluteBoundingBox") or {}
96
+ pages.append({
97
+ "nodeId": node_id,
98
+ "name": document.get("name"),
99
+ "image": image,
100
+ "frame": {"w": round(box.get("width", 0)), "h": round(box.get("height", 0))},
101
+ "nodes": visible_text_nodes(document),
102
+ })
103
+ if not pages:
104
+ sys.exit("none of the frames could be rendered")
184
105
  return pages
185
106
 
186
107
 
187
- def main():
188
- ap = argparse.ArgumentParser(description=__doc__,
189
- formatter_class=argparse.RawDescriptionHelpFormatter)
190
- ap.add_argument("--mapping", required=True, help="mapping JSON (rows + figmaFileKey/figmaNodes + screen)")
191
- ap.add_argument("--spec", action="append", default=[],
192
- help="spec JSON (repeatable - one per screen state; overlay-compatible shape; "
193
- "image may be a data URL or a file path relative to the spec)")
194
- ap.add_argument("--file", help="Figma fileKey (REST; else mapping figmaFileKey)")
195
- ap.add_argument("--nodes", help="comma-separated frame node-ids (REST; else mapping figmaNodes)")
196
- ap.add_argument("--pad", type=int, default=150, help="context band above/below the element (px)")
197
- ap.add_argument("--scale", type=int, default=2)
198
- ap.add_argument("--token")
199
- ap.add_argument("--ca-file")
200
- ap.add_argument("--out", default=".", help="output dir (shots go under <out>/keyshots/)")
201
- ap.add_argument("--slug", help="override slug (default from screen name)")
202
- args = ap.parse_args()
108
+ def assign(rows, pages):
109
+ index = ov.build_index(rows)
110
+ hits = {}
111
+ for page in pages:
112
+ ordered = sorted(page.get("nodes") or [], key=lambda item: float(item.get("y", 0)))
113
+ for node in ordered:
114
+ position = ov.match_row(node, index)
115
+ if position is not None:
116
+ hits.setdefault(position, (page, node))
117
+ return hits
118
+
119
+
120
+ def crop_html(page, node, pad):
121
+ fw, fh = (float(page["frame"][side]) for side in ("w", "h"))
122
+ x, y, w, h = (float(node.get(axis, 0)) for axis in ("x", "y", "w", "h"))
123
+ top = max(0.0, y - pad)
124
+ height = max(1.0, min(fh, y + h + pad) - top)
125
+ pin_left = "left:0;"
126
+ box_class = "box"
127
+ style = (
128
+ f"html,body{{margin:0;padding:0;}}body{{width:{fw:.0f}px;height:{height:.0f}px;position:relative;overflow:hidden;}}"
129
+ f"img{{position:absolute;{pin_left}top:{-top:.0f}px;width:{fw:.0f}px;height:{fh:.0f}px;}}"
130
+ f".{box_class}{{position:absolute;left:{x - INSET:.0f}px;top:{y - top - INSET:.0f}px;width:{w + 2 * INSET:.0f}px;"
131
+ f"height:{h + 2 * INSET:.0f}px;border:3px solid {BOX_COLOUR};border-radius:6px;box-sizing:border-box;}}"
132
+ )
133
+ document = (
134
+ f"{ov.PAGE_OPEN}{ov.META_UTF8}<style>{style}</style></head>"
135
+ f'<body><img src="{page["image"]}"><div class="{box_class}"></div></body></html>'
136
+ )
137
+ return document, fw, height
203
138
 
204
- with open(args.mapping, encoding="utf-8") as fh:
205
- m = json.load(fh)
206
- rows = m.get("rows") or []
207
- screen = m.get("screen") or "screen"
208
- sl = args.slug or slug(screen)
209
- shots_dir = os.path.join(args.out, "keyshots")
210
- os.makedirs(shots_dir, exist_ok=True)
211
139
 
212
- # ---- geometry pages, in precedence order (first match wins per row) ----
213
- if args.spec:
214
- pages = []
215
- for sp in args.spec:
216
- with open(sp, encoding="utf-8") as fh:
217
- spec = json.load(fh)
218
- for page in spec.get("pages") or []:
219
- page["_img_src"] = spec_image_src(page, sp)
220
- pages.append(page)
221
- if not pages:
222
- sys.exit("--spec files carry no pages")
140
+ def main():
141
+ cli = argparse.ArgumentParser(description=__doc__.splitlines()[0])
142
+ cli.add_argument("--mapping", required=True, help="the reuse-map JSON")
143
+ cli.add_argument("--spec", action="append", help="geometry spec for one screen state; may be repeated")
144
+ cli.add_argument("--file", help="Figma file key; falls back to figmaFileKey in the mapping")
145
+ cli.add_argument("--nodes", help="frame node-ids separated by commas; falls back to figmaNodes")
146
+ cli.add_argument("--pad", default=150, type=int, help="pixels of surrounding context kept over and under the element")
147
+ cli.add_argument("--scale", default=2, type=int)
148
+ cli.add_argument("--token", help="Figma token; FIGMA_ACCESS_TOKEN or the keychain is safer since other processes can read argv")
149
+ cli.add_argument("--ca-file", help="PEM roots to trust when a proxy intercepts TLS")
150
+ cli.add_argument("--out", dest="target_dir", default=".", help="folder for the manifest; the crops go into <out>/keyshots/")
151
+ cli.add_argument("--slug", help="stem of the manifest name; derived from the screen name when omitted")
152
+ opts = cli.parse_args()
153
+
154
+ with open(opts.mapping, encoding="utf-8") as stream:
155
+ mapping = json.load(stream)
156
+ rows = mapping.get("rows") or []
157
+ stem = opts.slug or ov.slug(mapping.get("screen"))
158
+
159
+ if opts.spec:
160
+ pages = spec_pages(opts.spec)
223
161
  else:
224
- file_key = args.file or m.get("figmaFileKey")
225
- node_ids = [canon_id(n) for n in (args.nodes or "").split(",") if n.strip()] or \
226
- [canon_id(str(n)) for n in (m.get("figmaNodes") or [])]
227
- if not (file_key and node_ids):
228
- ap.error("REST needs a fileKey + node-ids (via --file/--nodes or the mapping). "
229
- "Or pass --spec for the offline path.")
230
- token = figma_token(args)
162
+ file_key = opts.file or mapping.get("figmaFileKey")
163
+ nodes = ov.split_nodes(opts.nodes, mapping)
164
+ if not (file_key and nodes):
165
+ cli.error("need a Figma file key and frame node-ids (flags or mapping), or --spec")
166
+ token = ov.figma_token(opts.token)
231
167
  if not token:
232
- sys.exit("No Figma token - pass --token, set FIGMA_ACCESS_TOKEN, or add the "
233
- "keychain item FIGMA_ACCESS_TOKEN (or use --spec).")
234
- pages = collect_rest_pages(file_key, node_ids, token, args.ca_file, args.scale)
235
- for page in pages:
236
- page["_img_src"] = page["image"]
237
- if not pages:
238
- sys.exit("no renderable frames")
239
-
240
- # ---- choose one node per row: first page (in order) whose nodes match it ----
241
- by = [build_index([r]) for r in rows] # per-row index → exact same matcher as overlay
242
- chosen = {} # row idx (0-based) → {page, node}
243
- for page in pages:
244
- for n in sorted(page["nodes"], key=lambda x: x.get("y", 0)):
245
- for i, r in enumerate(rows):
246
- if i in chosen or not r.get("newKey"):
247
- continue
248
- if match_row(n, *by[i]) is r:
249
- chosen[i] = {"page": page, "node": n}
250
- missing = [{"row": i + 1, "key": r.get("newKey"),
251
- "reason": "no visible node matched (cmsNodeId/value)"}
252
- for i, r in enumerate(rows) if r.get("newKey") and i not in chosen]
168
+ sys.exit("No Figma token found (FIGMA_ACCESS_TOKEN, FIGMA_TOKEN, keychain FIGMA_ACCESS_TOKEN or --token). "
169
+ "Without one, build --spec files from the screenshots instead.")
170
+ pages = rest_pages(file_key, nodes, opts.scale, token, opts.ca_file)
171
+
172
+ crops_dir = os.path.join(opts.target_dir, "keyshots")
173
+ os.makedirs(crops_dir, exist_ok=True)
174
+ chrome = ov.find_chrome()
175
+ hits = assign(rows, pages)
176
+ shots, missing = [], []
177
+ failed = 0
178
+
179
+ for number, row in enumerate(rows, 1):
180
+ key = str(row.get("newKey") or "").strip()
181
+ if not key:
182
+ continue
183
+ if number - 1 not in hits:
184
+ missing.append({"row": number, "key": key, "reason": f"no visible node matched ({ID_FIELD}/value)"})
185
+ continue
186
+ page, node = hits[number - 1]
187
+ document, width, height = crop_html(page, node, opts.pad)
188
+ base = shot_name(number, key)
189
+ entry = {
190
+ "row": number,
191
+ "key": TRAILING_MARKER.sub("", key),
192
+ "element": row.get("element"),
193
+ "pageNode": page.get("nodeId"),
194
+ "cropW": int(round(width)),
195
+ "cropH": int(round(height)),
196
+ }
197
+ if not chrome:
198
+ with open(os.path.join(crops_dir, base + ".html"), "w", encoding="utf-8") as sink:
199
+ sink.write(document)
200
+ entry.update(file=f"keyshots/{base}.html", fallback="html")
201
+ shots.append(entry)
202
+ continue
203
+ try:
204
+ ov.chrome_screenshot(chrome, document, os.path.join(crops_dir, base + ".png"), width, height, opts.scale)
205
+ except (RuntimeError, subprocess.SubprocessError) as exc:
206
+ failed += 1
207
+ print(f"# row {number} {key}: {exc}", file=sys.stderr)
208
+ continue
209
+ entry["file"] = f"keyshots/{base}.png"
210
+ shots.append(entry)
211
+
212
+ manifest_name = f"{stem}.keyshots.manifest.json"
213
+ manifest = {
214
+ "screen": mapping.get("screen"),
215
+ "slug": stem,
216
+ "pad": opts.pad,
217
+ "scale": opts.scale,
218
+ "dir": "keyshots",
219
+ "shots": shots,
220
+ "missing": missing,
221
+ }
222
+ with open(os.path.join(opts.target_dir, manifest_name), "w", encoding="utf-8") as sink:
223
+ json.dump(manifest, sink, indent=2, ensure_ascii=False)
224
+ sink.write("\n")
253
225
 
254
- chrome = find_chrome()
255
226
  if not chrome:
256
- sys.stderr.write("# no headless Chrome - writing per-shot .html fallbacks\n")
257
-
258
- manifest = {"screen": screen, "slug": sl, "pad": args.pad, "scale": args.scale,
259
- "dir": "keyshots", "shots": [], "missing": missing}
260
- ok = fail = 0
261
- for i in sorted(chosen):
262
- r, c = rows[i], chosen[i]
263
- page, n = c["page"], c["node"]
264
- num = i + 1
265
- html, w, h = build_shot_html(page["_img_src"], page["frame"]["w"], page["frame"]["h"],
266
- {"x": n["x"], "y": n["y"], "w": n["w"], "h": n["h"]}, args.pad)
267
- fname = key_file(num, r["newKey"])
268
- out_png = os.path.join(shots_dir, fname)
269
- entry = {"row": num, "key": r["newKey"], "element": r.get("element"),
270
- "file": f"keyshots/{fname}", "pageNode": page.get("nodeId"),
271
- "cropW": w, "cropH": h}
272
- if chrome:
273
- err = render_shot_png(chrome, html, w, h, out_png, args.scale)
274
- if err:
275
- fail += 1
276
- sys.stderr.write(f"# ✗ row {num} {r['newKey']}: {err}\n")
277
- continue
278
- else:
279
- alt = out_png[:-4] + ".html"
280
- with open(alt, "w", encoding="utf-8") as fh:
281
- fh.write(html)
282
- entry["file"] = f"keyshots/{os.path.basename(alt)}"
283
- entry["fallback"] = "html"
284
- ok += 1
285
- manifest["shots"].append(entry)
286
-
287
- mpath = os.path.join(args.out, sl + ".keyshots.manifest.json")
288
- with open(mpath, "w", encoding="utf-8") as fh:
289
- json.dump(manifest, fh, ensure_ascii=False, indent=2)
290
- fh.write("\n")
291
- sys.stderr.write(f"# keyshots: {ok} written, {len(missing)} unmatched, {fail} failed → "
292
- f"{shots_dir}/ · manifest: {mpath}\n")
293
- sys.stderr.write(f'# set the mapping\'s top-level "keyshots": "{os.path.basename(mpath)}" '
294
- "so build-artifact.py fills the table cells\n")
227
+ print("# Chrome, Chromium and Edge are all missing; the crops were saved as .html", file=sys.stderr)
228
+ print(f"# keyshots: {len(shots)} written, {len(missing)} unmatched, {failed} failed", file=sys.stderr)
229
+ print(f'# point the mapping\'s top-level "keyshots" field at "{manifest_name}"', file=sys.stderr)
295
230
 
296
231
 
297
232
  if __name__ == "__main__":