@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1

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 (284) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +285 -285
  5. package/package.json +3 -3
  6. package/pipeline/lib/redact.mjs +3 -2
  7. package/pipeline/scripts/_notices.mjs +1 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +13 -1
  9. package/pipeline/scripts/pre-commit-check.sh +4 -0
  10. package/pipeline/skills/.skill-manifest.json +69 -69
  11. package/pipeline/skills/shared/README.md +70 -70
  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/app-clips/SKILL.md +260 -160
  16. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  17. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  18. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  19. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  20. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  21. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  22. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  23. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +345 -277
  24. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  25. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +107 -121
  26. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
  27. package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -326
  28. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  29. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  30. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  31. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +335 -360
  32. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  34. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  35. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  37. package/pipeline/skills/shared/external/authentication/SKILL.md +277 -381
  38. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  39. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -178
  40. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  41. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  42. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  43. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  44. package/pipeline/skills/shared/external/background-processing/SKILL.md +274 -384
  45. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  46. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
  47. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  48. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  49. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  50. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  51. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  52. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  53. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  54. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  55. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  56. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +228 -376
  57. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  58. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  59. package/pipeline/skills/shared/external/core-data/SKILL.md +302 -368
  60. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  61. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  62. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  63. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  64. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  65. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  66. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  67. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  68. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  69. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  70. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  71. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  72. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  73. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  74. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  75. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  76. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  77. package/pipeline/skills/shared/external/device-integrity/SKILL.md +236 -353
  78. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  79. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  80. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  81. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  82. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  83. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  84. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  85. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  86. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  87. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  88. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  89. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  90. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  91. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  92. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  93. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  94. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  95. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  96. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  97. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  98. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  99. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  100. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  101. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  102. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  103. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  104. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  105. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  106. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  107. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  108. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  109. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  111. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  112. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  113. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  114. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
  115. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
  116. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
  117. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
  118. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
  119. package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
  120. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
  121. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
  122. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
  123. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
  124. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
  125. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
  126. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
  127. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
  128. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
  129. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
  130. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -267
  131. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  132. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  133. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  134. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  135. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  136. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  137. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  138. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  139. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  140. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  141. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  142. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  143. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  144. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  145. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  146. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  147. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  148. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  149. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  150. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  151. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  152. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  153. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  154. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  155. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  156. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  157. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  158. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  159. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  160. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  161. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  162. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  163. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  164. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  165. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  166. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  167. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  168. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  169. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  170. package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
  171. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  172. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  173. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  174. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  175. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  176. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  177. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +302 -241
  178. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  179. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  180. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  181. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  182. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  183. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  184. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  185. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  186. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  187. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  188. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  189. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  191. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +304 -351
  193. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  194. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  195. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  196. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  197. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  198. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  199. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  200. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  201. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  202. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  203. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  204. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  205. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  206. package/pipeline/skills/shared/external/swift-security/SKILL.md +183 -162
  207. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  208. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  209. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +411 -476
  210. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  211. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  212. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  213. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  214. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  215. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  216. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +375 -491
  217. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  218. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  219. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  220. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +397 -457
  221. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  222. package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
  223. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  224. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
  225. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  226. package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -256
  227. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  228. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  229. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  230. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  231. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  232. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  233. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  234. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  235. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  236. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  237. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  238. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  239. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  240. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  241. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  242. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  243. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  244. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  245. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  246. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  247. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  248. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  249. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  250. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  251. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +201 -168
  252. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  253. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
  254. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  255. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
  256. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  257. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  258. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  259. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  260. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  261. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  262. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  263. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  264. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  265. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  266. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  267. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  268. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  269. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  270. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  271. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  272. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  273. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  274. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  275. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  276. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  277. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  278. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  279. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  280. package/pipeline/skills/shared/external/weatherkit/SKILL.md +160 -315
  281. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  282. package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
  283. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
  284. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -18,7 +18,7 @@ Scope rules that hold for every run:
18
18
  - **CMS copy is additive.** The annotation holds the copy the content team signed off. It sits in its own columns and never overwrites `new`. When it differs from `new`, the difference is shown with a warning mark, not hidden.
19
19
  - **Figma live first.** CMS annotations and overlay geometry are read from Figma, over REST or through Figma MCP. The design export kept in the repo may be out of date, so it is used only if neither live route works.
20
20
  - **Read-only everywhere** except two writes: the Confluence page and, optionally, a pull request that adds `localizations/<slug>/` to the specs repo.
21
- - **Components are mapped once.** When a reusable component owns a key, the row is flagged `(owned by <Component>)` instead of being mapped again on each screen where the component appears.
21
+ - **Components are mapped once.** When a reusable component owns a key, the row's `newKey` carries `(owned by <Component>)` instead of the key being mapped again on each screen where the component appears.
22
22
  - **Chrome language.** Author-written `element` labels and `note` text follow `--ui-lang`; keys and translation values stay verbatim.
23
23
 
24
24
  It is not a key-minting tool: new keys and their values belong to the resource-authoring flow.
@@ -53,7 +53,7 @@ The helpers in `scripts/` use nothing beyond the Python 3 standard library, apar
53
53
  1. **Enumerate new keys.** Take the union of three sources: the design export's `components-used.md` across every state frame, each component's registry `localizationKeys`, and the keys the screen code wires. Tag each key screen-specific, component-owned or shared. Every key is confirmed by comparing sources, never lifted from one of them.
54
54
  2. **Catch what the cross-reference misses.** Run `scan-screen-keys.py --screen-path <dir>`. Each `error` and `dynamic` hit becomes a mapping row; compare the `static` hits with the union from step 1.
55
55
  3. **New values.** Run `resolve-new-values.py --keys "<all>" --resources-root <res> --langs all --catalog <shipped catalog>`; `--catalog` is never optional.
56
- 4. **CMS copy.** Run `fetch-annotations.py --mapping <map>.json`, falling back from REST to `--from-mcp` and then `--local`. Fill `cms` and `cmsNodeId` on each row, and send any TR without EN back to the content team.
56
+ 4. **CMS copy.** Run `fetch-annotations.py --mapping <map>.json [--local <export-screen-dir>]`: it reads REST and falls back to `--local` when REST fails. When REST is rate-limited or has no token, dump the annotations through Figma MCP and run it again with `--from-mcp <dump>.json`. Fill `cms` and `cmsNodeId` on each row, and send any TR without EN back to the content team.
57
57
  5. **Trace legacy keys** per platform: spec, then view controller, view model and cell presentation models on iOS; layout `@string/` on Android; call sites on web.
58
58
  6. **Legacy values.** Run `resolve-legacy-values.py --keys "<legacy>" --snapshot-root <res>/Localization/Legacy --langs all`; if the snapshot is out of date, `fetch-legacy-labels.py` refreshes it. A value that cannot be found stays blank and gets a note.
59
59
  7. **Assemble the mapping JSON**: one row per element, plus `screenshot`, `figmaFileKey` and `legacyKeyPrefix` where they apply. Schema: [format-and-output](reference/format-and-output.md).
@@ -76,7 +76,7 @@ When the Figma frame carries no annotations, say so, explain that keys will be m
76
76
 
77
77
  Edge cases:
78
78
 
79
- - **Component-owned key**: note `(owned by <Component>)`; do not map it twice.
79
+ - **Component-owned key**: suffix the `newKey` with `(owned by <Component>)`, where the gate looks for it (a note alone does not exempt the row); do not map it twice.
80
80
  - **Legacy keys differ across platforms**: keep every platform column that has a key, default to `review`.
81
81
  - **`tr` equals `en`**: note it as untranslated whatever the verdict.
82
82
  - **CMS present and different from `new`**: the renderer adds the warning mark; mention it in the note. CMS copy is context, not an input to the verdict.
@@ -74,7 +74,7 @@
74
74
  },
75
75
  {
76
76
  "element": "Password strength hint (owned by PasswordField)",
77
- "newKey": "PasswordField.StrengthHint",
77
+ "newKey": "PasswordField.StrengthHint (owned by PasswordField)",
78
78
  "new": {
79
79
  "en": "Use at least 8 characters",
80
80
  "tr": "En az 8 karakter kullan"
@@ -45,7 +45,7 @@ Keep `new` and `legacy` fully eight-keyed. The summary table shows old and new `
45
45
  | 1 | Nav title with no iOS legacy key (`(nav title)` marker) | review |
46
46
  | 2 | One shared key, identical on all three platforms | reuse |
47
47
  | 3 | Placeholder with no legacy counterpart | new |
48
- | 4 | Component-owned `PasswordField.StrengthHint` with cross-platform key drift | review |
48
+ | 4 | Component-owned `PasswordField.StrengthHint (owned by PasswordField)` with cross-platform key drift | review |
49
49
  | 5 | Duplicate-email error found only by the scanner; the Android key has a typo | review |
50
50
  | 6 | CTA whose web key is namespaced (`signup.continue_button`) | review |
51
51
  | 7 | `tr` equal to `en` in the redesign source | reuse, flagged untranslated |
@@ -72,7 +72,7 @@ The `--ui-lang` switch (`en` default, `tr`) changes headings, legend, labels and
72
72
 
73
73
  ## Overlay and keyshots
74
74
 
75
- - `render-overlay.py` writes `<slug>.overlay.png` (or `<slug>.overlay.html` when no Chrome, Chromium or Edge is installed) and `<slug>.overlay.manifest.json` with `screen`, `file`, `fileKey`, `nodeId`, `cardCount`, `annotatedCount`, `width`, `height`, `scale`. Pink cards carry CMS copy; gray cards show the on-screen text and await copy.
75
+ - `render-overlay.py` writes `<slug>.overlay.png` (or `<slug>.overlay.html` when no Chrome, Chromium or Edge is installed) and `<slug>.overlay.manifest.json` with `screen`, `file`, `fileKey`, `nodeId`, `cardCount`, `annotatedCount`, `width`, `height`, `scale`. Pink cards carry CMS copy; gray cards show the on-screen text and await copy. `--ui-lang en/tr` (default `en`) sets the card labels.
76
76
  - `render-key-shots.py` writes `keyshots/keyshot__<NN>__<key>.png` (or `.html` crops with `"fallback": "html"`) and `<slug>.keyshots.manifest.json` with `screen`, `slug`, `pad`, `scale`, `dir`, `shots[]` (`row`, `key`, `element`, `file`, `pageNode`, `cropW`, `cropH`) and `missing[]` (`row`, `key`, `reason`).
77
77
 
78
78
  ## CMS import spreadsheet
@@ -84,7 +84,8 @@ Nine fixed columns, spelled exactly like this:
84
84
  `Channel`, `Property Group`, `Property Module`, `Key`, `EN Value`, `TR Value`, `AR Value`, `Anotation EN`, `Anotation TR`
85
85
 
86
86
  - `Channel` comes from `--channel` (default `Mobile`).
87
- - `Key` is `newKey` with any `(owned by ...)` suffix removed.
87
+ - `Key` is `newKey` with its trailing marker removed: any parenthesised suffix such as `(owned by ...)` or `(component: ...)`.
88
+ - A row whose key is empty after that, starts with `(dynamic`, or contains a `<placeholder>` has no literal key to import: it is left out of the sheet and listed on stderr.
88
89
  - Values come from `new.en/tr/ar`, annotations from `cms.en/tr`; missing means blank.
89
90
  - Property Group and Module: row overrides win (each fills its own half). Otherwise the key decides, in order: contains `error` gives Errors; `lookup` gives Lookups; `validation`, `invalid` or a `.valid` ending gives Core / Validation; `field`, `placeholder`, `hint` or `label` gives Core / Fields; a first namespace segment or any word matching a domain list gives the domain group (`Domains`) and the domain name; anything else is Core / Common.
90
91
  - Default domains: auth (auth, login, signin, sign-in, register, signup, otp, password), account (account, profile, settings, preferences), checkout (checkout, cart, payment, billing, order), search (search, filter, results). A `--taxonomy` JSON object is merged one level deep over these defaults.
@@ -95,14 +96,14 @@ Nine fixed columns, spelled exactly like this:
95
96
 
96
97
  | Check | Hard? |
97
98
  |---|---|
98
- | Each `newKey` is wired in the screen code (exact match), or exempt as component-owned or dynamic. A key whose last segment only appears as a word is `weak`; neither is `unverified` | unverified: hard |
99
+ | Each `newKey` is wired in the screen code (exact match), or exempt as component-owned or dynamic. A `LocalizationStringKey` chain counts as a key only with two or more segments, so `extension LocalizationStringKey.Checkin` is not one. A key whose last segment only appears as a word is `weak`; neither is `unverified` | unverified: hard |
99
100
  | Keys the code wires that no row maps (`codeKeysNotInMap`) | hard |
100
101
  | `reuse` / `review` row without any legacy `en` or `tr` value | hard |
101
- | Non-`new` row with an empty `new.tr` | soft |
102
+ | Non-`new` row with an empty or whitespace-only `new.tr` | soft |
102
103
  | `new.tr` equal to `new.en` (untranslated) | soft |
103
104
  | With `--resources-root`: no `Suggested/<Key>.json` in the snapshot or the source layout | soft |
104
105
 
105
- A row is dynamic-exempt when its key starts with `(dynamic`, contains a `<placeholder>`, its element mentions dynamic, or its note says "enumerate at runtime". Exit 2 when any hard issue exists; publish only after fixing them (drop or fix UNVERIFIED keys, add rows for wired-but-unmapped keys) or after a conscious, stated waiver.
106
+ A row is component-exempt when its `newKey` carries a marker naming the owner: `(owned by <Component>)`, `(component ...)` or the Turkish synonym `(bilesen ...)` written with a Turkish s-cedilla (U+015F); the element and note text do not count. A row is dynamic-exempt when its key starts with `(dynamic`, contains a `<placeholder>`, its element mentions dynamic (or the Turkish synonym "dinamik"), or its note says "enumerate at runtime". Folders named `.git` or `Generated` are skipped when scanning; `.github` and similar names are read. Exit 2 when any hard issue exists; publish only after fixing them (drop or fix UNVERIFIED keys, add rows for wired-but-unmapped keys) or after a conscious, stated waiver.
106
107
 
107
108
  ## Script reference
108
109
 
@@ -113,7 +114,7 @@ A row is dynamic-exempt when its key starts with `(dynamic`, contains a `<placeh
113
114
  | `resolve-legacy-values.py` | `--keys` (required), `--langs`, `--snapshot-root`, `--live`, `--endpoint`, `--header K=V`, `--headers-file`, `--env`, `--status-field`, `--fail-status CODE=message`, `--timeout`, `--plist-root`, `--prefix` |
114
115
  | `fetch-legacy-labels.py` | `--out`, `--endpoint` (both required), `--header`, `--headers-file`, `--langs` (default `all`), `--env`, `--status-field`, `--fail-status`, `--timeout` |
115
116
  | `fetch-annotations.py` | `--file`, `--nodes`, `--mapping`, `--from-mcp`, `--local`, `--source auto/rest/mcp/local`, `--token`, `--ca-file`, `--out` |
116
- | `render-overlay.py` | `--mapping` (required), `--spec`, `--file`, `--nodes`, `--scale`, `--token`, `--ca-file`, `--out`, `--slug` |
117
+ | `render-overlay.py` | `--mapping` (required), `--spec`, `--file`, `--nodes`, `--scale`, `--token`, `--ca-file`, `--out`, `--slug`, `--ui-lang en/tr` |
117
118
  | `render-key-shots.py` | `--mapping` (required), `--spec` (repeatable), `--file`, `--nodes`, `--pad` (default 150), `--scale`, `--token`, `--ca-file`, `--out`, `--slug` |
118
119
  | `build-artifact.py` | `mapping` (or `-`), `--out`, `--slug`, `--ui-lang en/tr`, `--docx`, `--pdf`, `--all`, `--print-upload` |
119
120
  | `build-spreadsheet.py` | `mapping` (or `-`), `--out`, `--slug`, `--channel`, `--taxonomy`, `--csv`, `--csv-only` |
@@ -55,11 +55,11 @@ python3 scripts/publish-confluence.py \
55
55
  - **Transport**: `curl` carries every request. The Authorization header is written to a temporary config file with mode 600, handed over with `-K` and removed at the end; JSON bodies also travel through a temporary file.
56
56
  - **Idempotent**: the lookup is `GET /rest/api/content?spaceKey=&title=&type=page&expand=version&limit=5`, which reads the database rather than the CQL index. A hit is updated with `PUT` and the next version number, reported as `updated (vN->vN+1)`; a miss is created with `POST` below the parent and reported as `created`. A second run lands on the same page id.
57
57
  - **Attachments**: the screenshot first, then the overlay, then each `.png` listed in the keyshots manifest, then every `--attach` file. When a file of that name is already attached, a new version is uploaded to `.../child/attachment/<id>/data` instead of adding a second copy.
58
- - **Inline spreadsheet**: an `--attach` file with the extension `.xlsx`, `.xls`, `.csv`, `.docx` or `.pdf` is additionally shown on the page: a heading "CMS Aktarım Dosyası" is appended, then one `view-file` macro per such file.
58
+ - **Inline spreadsheet**: an `--attach` file with the extension `.xlsx`, `.xls`, `.csv`, `.docx` or `.pdf` is additionally shown on the page: a heading is appended (`--embed-heading`, default "CMS Import File"; pass the team's own wording, for example a Turkish title, to change it), then one `view-file` macro per such file.
59
59
  - **Dry run**: with `--dry-run` nothing is sent; the script prints base, space, parent, title, body size, the attachment list and the action it would take.
60
60
  - **Output**: a JSON object with `action`, `id`, `title`, `attachments` and `url`.
61
61
 
62
- Running `build-artifact.py --print-upload` shows the publish command that fits the mapping, plus a hand-written Bearer `curl` version of it that pulls the token out of the keychain. When neither route works, paste the storage XML into the Confluence editor and attach the images under their file names; the XML already points at them by name.
62
+ Running `build-artifact.py --print-upload` shows the publish command that fits the mapping, plus a hand-written Bearer `curl` version of it that pulls the token out of the keychain. File paths in the command are joined with `--out` (overlay, screenshot and keyshots manifest are looked up next to the mapping first), every argument is shell-quoted, and the curl config is created under `umask 077`. When neither route works, paste the storage XML into the Confluence editor and attach the images under their file names; the XML already points at them by name.
63
63
 
64
64
  ## Option B: specs-repo pull request
65
65
 
@@ -46,7 +46,7 @@ python3 scripts/resolve-new-values.py --keys "<k1>,<k2>" --resources-root <res>
46
46
  --catalog <path/to/shipped/Localizable.xcstrings>
47
47
  ```
48
48
 
49
- Always pass `--catalog`. A missing Suggested file means "not in this snapshot", not "unauthored": the snapshot can lag the shipped catalog. With the catalog the value still resolves and the script names the stale snapshot so it can be refreshed with `snapshot-resources.sh`. Only a key absent from both the snapshot and the catalog is truly unauthored. Never open an authoring request for a key the catalog resolved.
49
+ Always pass `--catalog`. A missing Suggested file means "not in this snapshot", not "unauthored": the snapshot can lag the shipped catalog. With the catalog the value still resolves and the script names the stale snapshot so it can be refreshed with `snapshot-resources.sh`. Only a key absent from both the snapshot and the catalog is truly unauthored. A catalog entry that is empty, or has no value in any requested language, counts as absent: the lookup then tries the normalised key form and otherwise lists the key as unresolved. Never open an authoring request for a key the catalog resolved.
50
50
 
51
51
  ## CMS copy (Figma annotations)
52
52
 
@@ -58,6 +58,8 @@ python3 scripts/fetch-annotations.py --out _annotations.json --from-mcp <raw-mcp
58
58
  python3 scripts/fetch-annotations.py --out _annotations.json --local <design-export>/screens/<frame>
59
59
  ```
60
60
 
61
+ The default source is REST; when it fails and `--local` is given, the export's `tree.json` is read instead. `--from-mcp` is never tried automatically: after a rate limit (HTTP 429), dump the annotations through Figma MCP and rerun with that file. Node-ids in the URL form `1024-4150` are read as `1024:4150`; an annotation without a node-id is kept as its own entry, never merged with another.
62
+
61
63
  Pair every annotation with its row through `nodeId`: the id is saved in the row as `cmsNodeId`, and the copy goes into `cms: {tr, en}`. An element without an annotation keeps a blank CMS cell. The script lists nodes that have TR but no EN; send those back to the content team.
62
64
 
63
65
  ## Legacy keys: trace, do not skim
@@ -91,6 +93,7 @@ python3 scripts/resolve-legacy-values.py --keys "Continue,EmailAddress" \
91
93
 
92
94
  - Reading the snapshot needs no network. `fetch-legacy-labels.py` refreshes it and is the only step that goes online.
93
95
  - `--live` checks against the service directly; if the live call fails, the script falls back to the snapshot, then to the plists.
96
+ - With none of `--snapshot-root`, `--plist-root` or `--endpoint`, the script stops with a message naming them.
94
97
  - `--plist-root` reads the bundled `<lang>.lproj/language.plist` as a last resort. It is usually a stale subset, so a hit from it deserves a note.
95
98
  - **Backend prefix.** When the backend stores `Mobile-Continue` but the app asks for `Continue`, pass `--prefix Mobile-`, keep the mapping keys bare, and set the mapping's `legacyKeyPrefix` so the table shows the full stored key.
96
99
 
@@ -108,7 +111,7 @@ The overlay does not depend on annotations. With zero annotations every keyed el
108
111
  "nodes": [{"id": "n1", "x": 16, "y": 64, "w": 220, "h": 28, "side": "left"}]}]}
109
112
  ```
110
113
 
111
- `image` is either a data URL or a PNG path, resolved from the folder of the spec file. `side` is `left` (default) or `right`: it puts the card on the side where its target sits, which two-column layouts need. Each side is numbered and stacked independently, and right-side connectors are mirrored.
114
+ Every page needs `frame` with numeric `w` and `h`; a page without it stops the script with an error naming the page. `render-overlay.py` draws only the first page and warns when there are more; `render-key-shots.py` reads every page. `image` is either a data URL or a PNG path, resolved from the folder of the spec file. `side` is `left` (default) or `right`: it puts the card on the side where its target sits, which two-column layouts need. Each side is numbered and stacked independently, and right-side connectors are mirrored.
112
115
 
113
116
  The export's `tree.json` stops at component instances and carries no geometry per label, so take only the frame node-id from it.
114
117
 
@@ -0,0 +1,100 @@
1
+ """Key patterns, mapping-row marker rules and text helpers shared by the scripts.
2
+
3
+ Every script that scans keys, names output files or orders languages imports
4
+ this module, so the scanner, the gate, the CMS sheet and the overlay agree on
5
+ what a wired key is, which rows are component-owned or dynamic, which files are
6
+ source, how a screen name becomes a file slug and which order languages take.
7
+ """
8
+ import os
9
+ import re
10
+
11
+ EXTENSIONS = {".swift", ".kt", ".kts", ".xml", ".ts", ".tsx", ".js", ".jsx", ".vue"}
12
+ SKIP_DIRS = {".git", "Generated"}
13
+ LANGS = ["en", "tr", "ar"] + sorted(["ru", "it", "fr", "es", "de"])
14
+
15
+ WORD = "A-Za-z0-9_"
16
+ IDENT = rf"[A-Za-z_][{WORD}]*"
17
+ LSK = re.compile(rf"LocalizationStringKey((?:\.{IDENT})+)")
18
+ LITERAL_KEY = re.compile(rf"\"([{WORD}.]+)\"\.localized\b")
19
+ WEB_T = re.compile(r"(?<![\w$])t\(\s*(['\"])([A-Za-z0-9_.\-]+)\1")
20
+ WEB_DOLLAR_T = re.compile(r"\$t\(\s*(['\"])([A-Za-z0-9_.\-]+)\1")
21
+ INTERPOLATION = re.compile(r"\\\(|\$\{|\$[A-Za-z_]")
22
+
23
+ TRAILING_MARKER = re.compile(r"\s*\([^()]*\)\s*$")
24
+ COMPONENT_MARKER = re.compile(r"\([^)]*(owned by|bile\u015fen|component)[^)]*\)", re.IGNORECASE)
25
+ PLACEHOLDER = re.compile(r"<[^<>]+>")
26
+ DYNAMIC_WORDS = ("dynamic", "dinamik")
27
+ RUNTIME_NOTE = "enumerate at runtime"
28
+
29
+ XML_INVALID = re.compile("[\x00-\x08\x0b\x0c\x0e-\x1f]")
30
+
31
+
32
+ def strip_accessor(chain):
33
+ segments = chain.strip(".").split(".")
34
+ while segments and segments[-1].startswith("localized"):
35
+ segments.pop()
36
+ return ".".join(segments)
37
+
38
+
39
+ def lsk_keys(text):
40
+ for hit in LSK.finditer(text):
41
+ key = strip_accessor(hit.group(1))
42
+ if key.count(".") >= 1:
43
+ yield key
44
+
45
+
46
+ def is_interpolated(literal):
47
+ return bool(INTERPOLATION.search(literal))
48
+
49
+
50
+ def normalize_key(key):
51
+ return ".".join(seg[:1].upper() + seg[1:] for seg in key.split("."))
52
+
53
+
54
+ def source_files(root):
55
+ if os.path.isfile(root):
56
+ yield root
57
+ return
58
+ for current, dirs, files in os.walk(root):
59
+ relative = os.path.relpath(current, root)
60
+ parts = [] if relative == os.curdir else relative.split(os.sep)
61
+ if SKIP_DIRS.intersection(parts):
62
+ dirs[:] = []
63
+ continue
64
+ for name in sorted(files):
65
+ if os.path.splitext(name)[1] in EXTENSIONS:
66
+ yield os.path.join(current, name)
67
+
68
+
69
+ def raw_key(row):
70
+ return str(row.get("newKey") or "").strip()
71
+
72
+
73
+ def clean_key(new_key):
74
+ text = str(new_key or "").strip()
75
+ if text.startswith("(dynamic"):
76
+ return text
77
+ return TRAILING_MARKER.sub("", text)
78
+
79
+
80
+ def is_component(row):
81
+ return bool(COMPONENT_MARKER.search(raw_key(row)))
82
+
83
+
84
+ def key_is_dynamic(key):
85
+ return key.startswith("(dynamic") or bool(PLACEHOLDER.search(key))
86
+
87
+
88
+ def is_dynamic(row, key):
89
+ element = str(row.get("element") or "").lower()
90
+ note = str(row.get("note") or "").lower()
91
+ return key_is_dynamic(key) or any(word in element for word in DYNAMIC_WORDS) or RUNTIME_NOTE in note
92
+
93
+
94
+ def xml_text(value):
95
+ return XML_INVALID.sub("", str(value))
96
+
97
+
98
+ def slug(text):
99
+ value = re.sub(r"[\W_]+", "-", str(text or "").strip().lower()).strip("-")
100
+ return value or "screen"
@@ -11,6 +11,7 @@ import html
11
11
  import json
12
12
  import os
13
13
  import re
14
+ import shlex
14
15
  import shutil
15
16
  import struct
16
17
  import subprocess
@@ -22,7 +23,8 @@ import zipfile
22
23
  from collections import namedtuple
23
24
  from xml.sax.saxutils import escape, quoteattr
24
25
 
25
- LANGS = ["en", "tr", "ar"] + sorted(["ru", "it", "fr", "es", "de"])
26
+ from _shared import LANGS, slug, xml_text
27
+
26
28
  WARN = "\u26a0\ufe0f"
27
29
  DASH = "-"
28
30
  VERDICTS = ("reuse", "review", "new")
@@ -120,11 +122,6 @@ def look(row):
120
122
  return LOOKS.get(row.get("verdict"), UNKNOWN_LOOK)
121
123
 
122
124
 
123
- def slug(text):
124
- value = re.sub(r"[\W_]+", "-", str(text or "").strip().lower()).strip("-")
125
- return value or "screen"
126
-
127
-
128
125
  def norm(value):
129
126
  text = unicodedata.normalize("NFC", str(value or "")).strip()
130
127
  text = re.sub(r"\s+", " ", text)
@@ -189,8 +186,12 @@ def load_keyshots(mapping, mapping_dir, out_dir):
189
186
  return shots
190
187
 
191
188
 
189
+ def xml_escape(value):
190
+ return escape(xml_text(value))
191
+
192
+
192
193
  def node(name, attrs=(), *children):
193
- opening = name + "".join(f" {k}={quoteattr(str(v))}" for k, v in attrs)
194
+ opening = name + "".join(f" {k}={quoteattr(xml_text(v))}" for k, v in attrs)
194
195
  if not children:
195
196
  return f"<{opening}/>"
196
197
  return f"<{opening}>{''.join(children)}</{name}>"
@@ -291,12 +292,12 @@ class Doc:
291
292
  return storage_macro(
292
293
  "status",
293
294
  node("ac:parameter", [("ac:name", "colour")], look(row).status),
294
- node("ac:parameter", [("ac:name", "title")], escape(self.verdict(row))),
295
+ node("ac:parameter", [("ac:name", "title")], xml_escape(self.verdict(row))),
295
296
  )
296
297
 
297
298
  def xml(self):
298
299
  t, m = self.t, self.m
299
- e = escape
300
+ e = xml_escape
300
301
 
301
302
  def heading(label):
302
303
  return f"<h2>{e(t[label])}</h2>"
@@ -504,7 +505,7 @@ def para(text, bold=False, size=None, rtl=False):
504
505
  if rtl:
505
506
  styling.append(w("rtl"))
506
507
  run = [w("rPr", *styling)] if styling else []
507
- run.append(node("w:t", [("xml:space", "preserve")], escape(str(text))))
508
+ run.append(node("w:t", [("xml:space", "preserve")], xml_escape(text)))
508
509
  lead = [w("pPr", w("bidi"))] if rtl else []
509
510
  return w("p", *lead, w("r", *run))
510
511
 
@@ -663,17 +664,21 @@ def chrome_candidates():
663
664
 
664
665
  def pdf_libreoffice(docx_path, out_dir):
665
666
  binaries = [shutil.which("soffice"), shutil.which("libreoffice"), mac_app("LibreOffice", "soffice")]
666
- expected = os.path.splitext(docx_path)[0] + ".pdf"
667
+ name = os.path.splitext(os.path.basename(docx_path))[0] + ".pdf"
667
668
  for binary in binaries:
668
669
  if not (binary and os.path.isfile(binary)):
669
670
  continue
670
- try:
671
- subprocess.run([binary, "--headless", "--convert-to", "pdf", "--outdir", out_dir, docx_path],
672
- capture_output=True, timeout=180)
673
- except (OSError, subprocess.SubprocessError):
674
- continue
675
- if os.path.isfile(expected) and os.path.getsize(expected) > 0:
676
- return expected
671
+ with tempfile.TemporaryDirectory() as scratch:
672
+ try:
673
+ subprocess.run([binary, "--headless", "--convert-to", "pdf", "--outdir", scratch, docx_path],
674
+ capture_output=True, timeout=180)
675
+ except (OSError, subprocess.SubprocessError):
676
+ continue
677
+ rendered = os.path.join(scratch, name)
678
+ if os.path.isfile(rendered) and os.path.getsize(rendered) > 0:
679
+ target = os.path.join(out_dir, name)
680
+ shutil.copyfile(rendered, target)
681
+ return target
677
682
  return None
678
683
 
679
684
 
@@ -698,7 +703,7 @@ def pdf_chrome(preview_path, pdf_path):
698
703
  with tempfile.TemporaryDirectory() as profile:
699
704
  if os.path.exists(pdf_path):
700
705
  os.remove(pdf_path)
701
- argv = [chrome, headless, "--disable-gpu", f"--user-data-dir={profile}", "--no-pdf-header-footer",
706
+ argv = [chrome, headless, "--no-sandbox", "--disable-gpu", f"--user-data-dir={profile}", "--no-pdf-header-footer",
702
707
  f"--print-to-pdf={pdf_path}", page]
703
708
  try:
704
709
  proc = subprocess.Popen(argv, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
@@ -721,17 +726,31 @@ def pdf_wkhtml(preview_path, pdf_path):
721
726
  return pdf_path if os.path.isfile(pdf_path) and os.path.getsize(pdf_path) > 0 else None
722
727
 
723
728
 
724
- def print_upload(name, mapping, screen):
729
+ def upload_path(name, mapping_dir, out_dir):
730
+ if os.path.isabs(name):
731
+ return name
732
+ for folder in (mapping_dir, out_dir):
733
+ if os.path.exists(os.path.join(folder, name)):
734
+ return os.path.join(folder, name)
735
+ return os.path.join(out_dir, name)
736
+
737
+
738
+ def print_upload(name, mapping, screen, mapping_dir, out_dir):
725
739
  prefix = os.environ.get("LOCALIZATION_PAGE_PREFIX", "Localization")
726
- flags = [f"--xml {name}.confluence.xml", f'--screen "{screen}"']
727
- flags += [f"--{field} {mapping[field]}" for field in ("screenshot", "overlay", "keyshots") if mapping.get(field)]
728
- flags.append(f"--attach {name}.localization.xlsx")
740
+ quote = shlex.quote
741
+ flags = [f"--xml {quote(os.path.join(out_dir, name + '.confluence.xml'))}", f"--screen {quote(str(screen))}"]
742
+ flags += [
743
+ f"--{field} {quote(upload_path(str(mapping[field]), mapping_dir, out_dir))}"
744
+ for field in ("screenshot", "overlay", "keyshots") if mapping.get(field)
745
+ ]
746
+ flags.append(f"--attach {quote(os.path.join(out_dir, name + '.localization.xlsx'))}")
729
747
  print(" \\\n ".join(["python3 scripts/publish-confluence.py"] + flags))
730
748
  print()
749
+ title = json.dumps(f"{prefix} - {screen}", ensure_ascii=False).replace("'", "'\\''")
731
750
  print("# The same call by hand, for Server/DC with a Bearer PAT read from the keychain (argv never sees the token):")
732
- print("# printf 'header = \"Authorization: Bearer %s\"\\n' \"$(security find-generic-password -s CONFLUENCE_API_TOKEN -w)\" > auth.curlrc; chmod 600 auth.curlrc")
751
+ print("# (umask 077 && rm -f auth.curlrc && printf 'header = \"Authorization: Bearer %s\"\\n' \"$(security find-generic-password -s CONFLUENCE_API_TOKEN -w)\" > auth.curlrc)")
733
752
  print("# curl -sS -K auth.curlrc -X POST -H 'Content-Type: application/json' \"$CONFLUENCE_BASE_URL/rest/api/content\" \\")
734
- print(f"# --data '{{\"type\":\"page\",\"title\":\"{prefix} - {screen}\",\"space\":{{\"key\":\"'$CONFLUENCE_SPACE'\"}},"
753
+ print(f"# --data '{{\"type\":\"page\",\"title\":{title},\"space\":{{\"key\":\"'$CONFLUENCE_SPACE'\"}},"
735
754
  "\"ancestors\":[{\"id\":\"'$CONFLUENCE_PARENT'\"}],\"body\":{\"storage\":{\"value\":\"...\",\"representation\":\"storage\"}}}'")
736
755
  print("# rm auth.curlrc")
737
756
 
@@ -797,7 +816,7 @@ def main():
797
816
  make_pdf(docx_path, out_dir, stem, keep_docx)
798
817
 
799
818
  if opts.print_upload:
800
- print_upload(stem, mapping, mapping.get("screen"))
819
+ print_upload(stem, mapping, mapping.get("screen"), mapping_dir, out_dir)
801
820
 
802
821
 
803
822
  if __name__ == "__main__":
@@ -15,6 +15,8 @@ import sys
15
15
  import zipfile
16
16
  from xml.sax.saxutils import escape
17
17
 
18
+ from _shared import clean_key, key_is_dynamic, slug, xml_text
19
+
18
20
  COLUMNS = [
19
21
  "Channel", "Property Group", "Property Module", "Key",
20
22
  "EN Value", "TR Value", "AR Value", "Anotation EN", "Anotation TR",
@@ -37,11 +39,6 @@ DEFAULT_TAXONOMY = {
37
39
  }
38
40
 
39
41
 
40
- def slug(text):
41
- value = re.sub(r"[\W_]+", "-", str(text or "").strip().lower()).strip("-")
42
- return value or "screen"
43
-
44
-
45
42
  def read_json(source):
46
43
  with open(source, encoding="utf-8") as stream:
47
44
  return json.load(stream)
@@ -62,12 +59,6 @@ def load_taxonomy(path):
62
59
  return taxonomy
63
60
 
64
61
 
65
- def clean_key(new_key):
66
- text = str(new_key or "")
67
- cut = text.find("(owned by")
68
- return (text[:cut] if cut >= 0 else text).strip()
69
-
70
-
71
62
  def bucket(key, taxonomy):
72
63
  lowered = key.lower()
73
64
  if "error" in lowered:
@@ -88,9 +79,12 @@ def bucket(key, taxonomy):
88
79
 
89
80
 
90
81
  def table_rows(mapping, channel, taxonomy):
91
- out = []
92
- for row in mapping.get("rows") or []:
82
+ out, skipped = [], []
83
+ for number, row in enumerate(mapping.get("rows") or [], 1):
93
84
  key = clean_key(row.get("newKey"))
85
+ if not key or key_is_dynamic(key):
86
+ skipped.append((number, key))
87
+ continue
94
88
  group, module = bucket(key, taxonomy)
95
89
  if row.get("propertyGroup"):
96
90
  group = row["propertyGroup"]
@@ -103,7 +97,7 @@ def table_rows(mapping, channel, taxonomy):
103
97
  new.get("en") or "", new.get("tr") or "", new.get("ar") or "",
104
98
  cms.get("en") or "", cms.get("tr") or "",
105
99
  ])
106
- return out
100
+ return out, skipped
107
101
 
108
102
 
109
103
  def column_letter(index):
@@ -137,7 +131,7 @@ def part(root, attrs, *children):
137
131
 
138
132
 
139
133
  def cell(ref, value, style):
140
- text = escape(str(value)).replace("\n", "&#10;")
134
+ text = escape(xml_text(value)).replace("\n", "&#10;")
141
135
  attrs = [("r", ref), ("t", "inlineStr")]
142
136
  if style:
143
137
  attrs.append(("s", style))
@@ -276,7 +270,7 @@ def main():
276
270
  if not isinstance(mapping, dict) or "rows" not in mapping:
277
271
  cli.error("mapping must be a JSON object with rows")
278
272
 
279
- rows = table_rows(mapping, opts.channel, load_taxonomy(opts.taxonomy))
273
+ rows, skipped = table_rows(mapping, opts.channel, load_taxonomy(opts.taxonomy))
280
274
  stem = opts.slug or slug(mapping.get("screen"))
281
275
  os.makedirs(opts.target_dir, exist_ok=True)
282
276
 
@@ -294,6 +288,10 @@ def main():
294
288
  summary = {"slug": stem, "rows": len(rows), "columns": COLUMNS, "channel": opts.channel, "written": produced}
295
289
  json.dump(summary, sys.stdout, indent=2, ensure_ascii=False)
296
290
  sys.stdout.write("\n")
291
+ if skipped:
292
+ print(f"# {len(skipped)} row(s) left out of the sheet: no literal key (empty, dynamic or <placeholder>)", file=sys.stderr)
293
+ for number, key in skipped:
294
+ print(f"# row {number}: {key or '(empty)'}", file=sys.stderr)
297
295
 
298
296
 
299
297
  if __name__ == "__main__":
@@ -24,8 +24,12 @@ TR_RE = re.compile("^(?:tr|tr[kc]e|t[\u00fcu]rk[\u00e7c]e)\\s*" + SEP + "\\s*(.+
24
24
  EN_RE = re.compile("^(?:en|eng|ing|[i\u0130]ngilizce)\\s*" + SEP + "\\s*(.+)$", re.IGNORECASE)
25
25
 
26
26
 
27
+ URL_NODE_ID = re.compile(r"^\d+-\d+$")
28
+
29
+
27
30
  def canon_id(node_id):
28
- return str(node_id).strip().replace("-", ":")
31
+ text = "" if node_id is None else str(node_id).strip()
32
+ return text.replace("-", ":") if URL_NODE_ID.match(text) else text
29
33
 
30
34
 
31
35
  def split_lang(text):
@@ -68,7 +72,7 @@ def pick_label(labels):
68
72
  def record(node_id, name, node_type, design_text, labels):
69
73
  raw, parsed = pick_label(labels)
70
74
  return {
71
- "nodeId": canon_id(node_id),
75
+ "nodeId": canon_id(node_id) or None,
72
76
  "name": name,
73
77
  "type": node_type,
74
78
  "designText": design_text,
@@ -246,10 +250,13 @@ def main():
246
250
 
247
251
  source, found, files, audited = pick_source(opts, cli, file_key, nodes)
248
252
 
249
- first_seen = {}
253
+ unique, seen = [], set()
250
254
  for item in found:
251
- first_seen.setdefault(item["nodeId"], item)
252
- unique = list(first_seen.values())
255
+ if item["nodeId"]:
256
+ if item["nodeId"] in seen:
257
+ continue
258
+ seen.add(item["nodeId"])
259
+ unique.append(item)
253
260
 
254
261
  def having(lang):
255
262
  return sum(1 for entry in unique if entry[lang])
@@ -11,7 +11,8 @@ import os
11
11
  import sys
12
12
  import urllib.request
13
13
 
14
- ALL_LANGS = ["en", "tr", "ar"] + sorted(["ru", "it", "fr", "es", "de"])
14
+ from _shared import LANGS as ALL_LANGS
15
+
15
16
 
16
17
 
17
18
  def parse_langs(value):
@@ -16,10 +16,10 @@ import subprocess
16
16
  import sys
17
17
  import tempfile
18
18
  import urllib.parse
19
- from xml.sax.saxutils import quoteattr
19
+ from xml.sax.saxutils import escape, quoteattr
20
20
 
21
21
  EMBED_EXTENSIONS = {".xlsx", ".xls", ".csv", ".docx", ".pdf"}
22
- EMBED_HEADING = "CMS Aktar\u0131m Dosyas\u0131"
22
+ EMBED_HEADING = "CMS Import File"
23
23
  CONTENT = "/rest/api/content"
24
24
 
25
25
 
@@ -82,8 +82,8 @@ def view_file_macro(path):
82
82
  return f'<p><ac:structured-macro ac:name="view-file">{parameter}</ac:structured-macro></p>'
83
83
 
84
84
 
85
- def embed_block(files):
86
- return f"<h2>{EMBED_HEADING}</h2>" + "".join(map(view_file_macro, files))
85
+ def embed_block(files, heading):
86
+ return f"<h2>{escape(heading)}</h2>" + "".join(map(view_file_macro, files))
87
87
 
88
88
 
89
89
  def keyshot_files(manifest_path):
@@ -113,6 +113,7 @@ def build_cli():
113
113
  cli.add_argument("--parent", default=os.environ.get("CONFLUENCE_PARENT"), help="id of the parent page")
114
114
  cli.add_argument("--token", help="Bearer PAT; CONFLUENCE_API_TOKEN or the keychain is safer since other processes can read argv")
115
115
  cli.add_argument("--keychain-name", default="CONFLUENCE_API_TOKEN", help="keychain service that stores the PAT")
116
+ cli.add_argument("--embed-heading", default=EMBED_HEADING, help="heading above the embedded spreadsheet / document previews")
116
117
  cli.add_argument("--dry-run", action="store_true", help="report the planned actions without touching Confluence")
117
118
  return cli
118
119
 
@@ -174,7 +175,7 @@ def main():
174
175
  body = stream.read()
175
176
  embedded = [p for p in opts.attach if os.path.splitext(p)[1].lower() in EMBED_EXTENSIONS]
176
177
  if embedded:
177
- body += embed_block(embedded)
178
+ body += embed_block(embedded, opts.embed_heading)
178
179
 
179
180
  if opts.dry_run:
180
181
  plan = {