@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.
- package/CHANGELOG.md +21 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +285 -285
- package/package.json +3 -3
- package/pipeline/lib/redact.mjs +3 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/scripts/gen-skills-index.mjs +13 -1
- package/pipeline/scripts/pre-commit-check.sh +4 -0
- package/pipeline/skills/.skill-manifest.json +69 -69
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +345 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +107 -121
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +335 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +277 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +274 -384
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +228 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +302 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +236 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +302 -241
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +304 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +183 -162
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +411 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +375 -491
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +397 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +201 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +160 -315
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
- 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
|
|
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
|
|
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**:
|
|
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"
|
package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md
CHANGED
|
@@ -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 ...)`
|
|
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` |
|
package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md
CHANGED
|
@@ -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
|
|
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
|
|
package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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")],
|
|
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 =
|
|
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")],
|
|
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
|
-
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
|
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
|
-
|
|
727
|
-
flags
|
|
728
|
-
flags
|
|
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
|
|
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\"
|
|
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(
|
|
134
|
+
text = escape(xml_text(value)).replace("\n", " ")
|
|
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
|
-
|
|
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
|
-
|
|
253
|
+
unique, seen = [], set()
|
|
250
254
|
for item in found:
|
|
251
|
-
|
|
252
|
-
|
|
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])
|
package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py
CHANGED
|
@@ -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
|
|
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>{
|
|
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 = {
|