pumuki 6.3.306 → 6.3.308

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [6.3.308] - 2026-05-20
4
+
5
+ - `R_GO`: PRE_WRITE deja de exigir `skills.ios.critical-test-quality` cuando no hay cambios efectivos de test iOS en el scope actual aunque la evidencia previa arrastre `platforms.ios`. Evita el bloqueo falso `EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING` sobre `.ai_evidence.json` en repos limpios/no-code.
6
+ - iOS AST skills: añadidos detectores nodales con lineas y metadata accionable para `clean architecture layer direction`, `third-party UI test framework` y `excessive public API`.
7
+ - Canary multi-plataforma: endurecido para exigir bloqueo real, diagnostico `violations-detected`, rule id esperado/equivalente y finding asociado al archivo canario.
8
+
3
9
  ## [6.3.301] - 2026-05-19
4
10
 
5
11
  - `PUMUKI-INC-157`: PRE_WRITE no longer hard-blocks when evidence reports `ai_gate.status=BLOCKED` but exposes no real blocking causes and only non-blocking WARN signals such as worktree hygiene warnings. Explicit AST/skills findings with `blocking=true` still block, even when their inherited severity is WARN.
@@ -12,6 +12,7 @@ import {
12
12
  collectSwiftMakeSUTWithoutMemoryTrackingLines,
13
13
  collectSwiftMixedTestingFrameworkLines,
14
14
  collectSwiftQuickNimbleLines,
15
+ collectSwiftThirdPartyUiTestFrameworkLines,
15
16
  collectSwiftWaitForExpectationsLines,
16
17
  collectSwiftXCTestAssertionLines,
17
18
  collectSwiftXCTUnwrapLines,
@@ -20,6 +21,7 @@ import {
20
21
  collectSwiftCallbackStyleSignatureLines,
21
22
  collectSwiftDispatchGroupLines,
22
23
  collectSwiftDispatchSemaphoreLines,
24
+ collectSwiftDummyAwaitLines,
23
25
  hasSwiftAnyViewUsage,
24
26
  hasSwiftAnyTypeErasureUsage,
25
27
  hasSwiftAsyncWithoutAwaitUsage,
@@ -29,7 +31,9 @@ import {
29
31
  hasSwiftDispatchGroupUsage,
30
32
  collectSwiftDispatchQueueLines,
31
33
  hasSwiftDispatchQueueUsage,
34
+ hasSwiftDummyAwaitUsage,
32
35
  hasSwiftDispatchSemaphoreUsage,
36
+ collectSwiftLongAsyncOperationWithoutCancellationCheckLines,
33
37
  hasSwiftAdHocLoggingUsage,
34
38
  hasSwiftAlamofireUsage,
35
39
  collectSwiftComposableArchitectureUsageLines,
@@ -40,8 +44,20 @@ import {
40
44
  hasSwiftPackageBranchDependencyUsage,
41
45
  collectSwiftPackageToolsVersionBelow62Lines,
42
46
  hasSwiftPackageToolsVersionBelow62Usage,
47
+ collectSwiftPackageDefaultIsolationNotMainActorLines,
48
+ hasSwiftPackageDefaultIsolationNotMainActorUsage,
49
+ collectSwiftPackageStrictConcurrencyBelowCompleteLines,
50
+ hasSwiftPackageStrictConcurrencyBelowCompleteUsage,
51
+ collectSwiftCrossFeatureImportLines,
52
+ hasSwiftCrossFeatureImportUsage,
53
+ collectSwiftExcessivePublicApiLines,
54
+ hasSwiftExcessivePublicApiUsage,
55
+ collectSwiftLayerDirectionViolationLines,
56
+ hasSwiftLayerDirectionViolationUsage,
43
57
  collectSwiftStrictConcurrencyBelowCompleteLines,
44
58
  hasSwiftStrictConcurrencyBelowCompleteUsage,
59
+ collectSwiftDefaultActorIsolationNotMainActorLines,
60
+ hasSwiftDefaultActorIsolationNotMainActorUsage,
45
61
  hasSwiftForEachIndicesUsage,
46
62
  hasSwiftForEachSelfIdentityUsage,
47
63
  collectSwiftForceCastLines,
@@ -70,6 +86,8 @@ import {
70
86
  collectSwiftAnimationWithoutReduceMotionLines,
71
87
  collectSwiftUiInlineActionLogicLines,
72
88
  collectSwiftOnTapGestureWithoutButtonTraitLines,
89
+ collectSwiftGlassInteractiveOnStaticElementLines,
90
+ collectSwiftGlassEffectIDWithoutNamespaceLines,
73
91
  collectSwiftForEachIndicesLines,
74
92
  collectSwiftForEachSelfIdentityLines,
75
93
  collectSwiftInlineForEachTransformLines,
@@ -86,6 +104,7 @@ import {
86
104
  collectSwiftEnvironmentObjectLines,
87
105
  hasSwiftEnvironmentObjectUsage,
88
106
  hasSwiftLowContrastStaticColorPairUsage,
107
+ hasSwiftLongAsyncOperationWithoutCancellationCheckUsage,
89
108
  hasSwiftMainThreadBlockingSleepUsage,
90
109
  hasSwiftDirectSUTInstantiationWithoutMakeSUTUsage,
91
110
  hasSwiftMakeSUTWithoutMemoryTrackingUsage,
@@ -125,6 +144,8 @@ import {
125
144
  collectSwiftOnAppearTaskLines,
126
145
  hasSwiftOnTapGestureUsage,
127
146
  hasSwiftOnTapGestureWithoutButtonTraitUsage,
147
+ hasSwiftGlassInteractiveOnStaticElementUsage,
148
+ hasSwiftGlassEffectIDWithoutNamespaceUsage,
128
149
  hasSwiftOperationQueueUsage,
129
150
  hasSwiftContainsUserFilterUsage,
130
151
  hasSwiftCustomSingletonUsage,
@@ -132,8 +153,11 @@ import {
132
153
  hasSwiftPhysicalTextAlignmentUsage,
133
154
  hasSwiftPreconcurrencyUsage,
134
155
  hasSwiftProductionCommentUsage,
156
+ collectSwiftThreadCentricDebuggingLines,
157
+ hasSwiftThreadCentricDebuggingUsage,
135
158
  hasSwiftProductionTestDoubleUsage,
136
159
  hasSwiftQuickNimbleUsage,
160
+ hasSwiftThirdPartyUiTestFrameworkUsage,
137
161
  hasSwiftTestDoubleWithoutProtocolConformanceUsage,
138
162
  hasSwiftSheetIsPresentedUsage,
139
163
  hasSwiftScrollViewShowsIndicatorsUsage,
@@ -164,6 +188,8 @@ import {
164
188
  hasSwiftManualMemoryManagementUsage,
165
189
  collectSwiftNonPascalCaseTypeDeclarationLines,
166
190
  hasSwiftNonPascalCaseTypeDeclarationUsage,
191
+ collectSwiftEndpointEnumLines,
192
+ hasSwiftEndpointEnumUsage,
167
193
  collectSwiftCellCreationWithoutReuseLines,
168
194
  hasSwiftCellCreationWithoutReuseUsage,
169
195
  hasSwiftWaitForExpectationsUsage,
@@ -256,6 +282,46 @@ let ignored = "[unowned self]"
256
282
  assert.equal(hasSwiftUnownedSelfCaptureUsage(safe), false);
257
283
  });
258
284
 
285
+ test('hasSwiftLongAsyncOperationWithoutCancellationCheckUsage detecta bucles async sin cancelacion cooperativa', () => {
286
+ const source = `
287
+ final class SyncService {
288
+ func syncAll(items: [Item]) async throws {
289
+ for item in items {
290
+ try await upload(item)
291
+ }
292
+ }
293
+
294
+ func poll() async {
295
+ while true {
296
+ await refresh()
297
+ }
298
+ }
299
+ }
300
+ `;
301
+ const safe = `
302
+ final class SyncService {
303
+ func syncAll(items: [Item]) async throws {
304
+ for item in items {
305
+ if Task.isCancelled { return }
306
+ try await upload(item)
307
+ }
308
+ }
309
+
310
+ func poll() async throws {
311
+ while isRunning {
312
+ try Task.checkCancellation()
313
+ await refresh()
314
+ }
315
+ }
316
+ }
317
+ `;
318
+
319
+ assert.equal(hasSwiftLongAsyncOperationWithoutCancellationCheckUsage(source), true);
320
+ assert.deepEqual(collectSwiftLongAsyncOperationWithoutCancellationCheckLines(source), [4, 10]);
321
+ assert.equal(hasSwiftLongAsyncOperationWithoutCancellationCheckUsage(safe), false);
322
+ assert.deepEqual(collectSwiftLongAsyncOperationWithoutCancellationCheckLines(safe), []);
323
+ });
324
+
259
325
  test('hasSwiftManualMemoryManagementUsage detecta bypass manual de ARC', () => {
260
326
  const source = `
261
327
  final class LegacyBridge {
@@ -307,6 +373,36 @@ let sample = "struct user_profile {}"
307
373
  assert.deepEqual(collectSwiftNonPascalCaseTypeDeclarationLines(safe), []);
308
374
  });
309
375
 
376
+ test('hasSwiftEndpointEnumUsage detecta endpoints iOS como enum central no OCP', () => {
377
+ const source = `
378
+ enum APIEndpoint {
379
+ case login
380
+ }
381
+
382
+ enum OrdersEndpoint: String {
383
+ case list = "/orders"
384
+ }
385
+ `;
386
+ const safe = `
387
+ struct APIEndpoint: Sendable {
388
+ let path: String
389
+ let method: HTTPMethod
390
+
391
+ static func login() -> APIEndpoint {
392
+ APIEndpoint(path: "/login", method: .post)
393
+ }
394
+ }
395
+
396
+ let sample = "enum APIEndpoint { case login }"
397
+ // enum OrdersEndpoint { case list }
398
+ `;
399
+
400
+ assert.equal(hasSwiftEndpointEnumUsage(source), true);
401
+ assert.deepEqual(collectSwiftEndpointEnumLines(source), [2, 6]);
402
+ assert.equal(hasSwiftEndpointEnumUsage(safe), false);
403
+ assert.deepEqual(collectSwiftEndpointEnumLines(safe), []);
404
+ });
405
+
310
406
  test('hasSwiftCellCreationWithoutReuseUsage detecta celdas UIKit sin reutilizacion', () => {
311
407
  const source = `
312
408
  final class OrdersDataSource: NSObject, UITableViewDataSource {
@@ -596,6 +692,198 @@ buildSettings = {
596
692
  assert.deepEqual(collectSwiftStrictConcurrencyBelowCompleteLines(safe), []);
597
693
  });
598
694
 
695
+ test('detectores SwiftPM de concurrencia detectan aislamiento y strict concurrency inseguros', () => {
696
+ const source = `
697
+ let package = Package(
698
+ name: "App",
699
+ swiftSettings: [
700
+ .defaultIsolation(nonisolated),
701
+ .enableExperimentalFeature("StrictConcurrency=targeted"),
702
+ .enableUpcomingFeature("StrictConcurrency=minimal")
703
+ ]
704
+ )
705
+ `;
706
+ const safe = `
707
+ let package = Package(
708
+ name: "App",
709
+ swiftSettings: [
710
+ .defaultIsolation(MainActor.self),
711
+ .enableUpcomingFeature("NonisolatedNonsendingByDefault")
712
+ ]
713
+ )
714
+ let sample = ".defaultIsolation(nonisolated)"
715
+ // .enableExperimentalFeature("StrictConcurrency=targeted")
716
+ `;
717
+
718
+ assert.equal(hasSwiftPackageDefaultIsolationNotMainActorUsage(source), true);
719
+ assert.deepEqual(collectSwiftPackageDefaultIsolationNotMainActorLines(source), [5]);
720
+ assert.equal(hasSwiftPackageStrictConcurrencyBelowCompleteUsage(source), true);
721
+ assert.deepEqual(collectSwiftPackageStrictConcurrencyBelowCompleteLines(source), [6, 7]);
722
+ assert.equal(hasSwiftPackageDefaultIsolationNotMainActorUsage(safe), false);
723
+ assert.deepEqual(collectSwiftPackageDefaultIsolationNotMainActorLines(safe), []);
724
+ assert.equal(hasSwiftPackageStrictConcurrencyBelowCompleteUsage(safe), false);
725
+ assert.deepEqual(collectSwiftPackageStrictConcurrencyBelowCompleteLines(safe), []);
726
+ });
727
+
728
+ test('hasSwiftCrossFeatureImportUsage detecta imports entre bounded contexts iOS', () => {
729
+ const source = `
730
+ import SwiftUI
731
+ import SharedKernel
732
+ import Orders
733
+ import BuyerCommerce
734
+ `;
735
+ const path = 'apps/ios/Presentation/Features/BuyerCommerce/LoginView.swift';
736
+ const safe = `
737
+ import SwiftUI
738
+ import SharedKernel
739
+ import BuyerCommerce
740
+ // import Orders
741
+ let sample = "import Orders"
742
+ `;
743
+
744
+ assert.equal(hasSwiftCrossFeatureImportUsage(source, path), true);
745
+ assert.deepEqual(collectSwiftCrossFeatureImportLines(source, path), [4]);
746
+ assert.equal(hasSwiftCrossFeatureImportUsage(safe, path), false);
747
+ assert.deepEqual(collectSwiftCrossFeatureImportLines(safe, path), []);
748
+ assert.equal(hasSwiftCrossFeatureImportUsage(source, 'apps/ios/Presentation/LoginView.swift'), false);
749
+ });
750
+
751
+ test('hasSwiftLayerDirectionViolationUsage detecta imports prohibidos por capa Clean Architecture', () => {
752
+ const domainSource = `
753
+ import Foundation
754
+ import SwiftUI
755
+ import CoreData
756
+ import CheckoutApplication
757
+ import Combine
758
+ `;
759
+ const applicationSource = `
760
+ import Foundation
761
+ import UIKit
762
+ import Alamofire
763
+ import AuthInfrastructure
764
+ `;
765
+ const presentationSource = `
766
+ import SwiftUI
767
+ import CoreData
768
+ import DesignSystem
769
+ import AuthInfrastructure
770
+ `;
771
+ const safeDomain = `
772
+ import Foundation
773
+ import SharedKernel
774
+ `;
775
+
776
+ assert.equal(
777
+ hasSwiftLayerDirectionViolationUsage(
778
+ domainSource,
779
+ 'apps/ios/Features/Orders/Domain/Entities/Order.swift'
780
+ ),
781
+ true
782
+ );
783
+ assert.deepEqual(
784
+ collectSwiftLayerDirectionViolationLines(
785
+ domainSource,
786
+ 'apps/ios/Features/Orders/Domain/Entities/Order.swift'
787
+ ),
788
+ [3, 4, 5, 6]
789
+ );
790
+ assert.deepEqual(
791
+ collectSwiftLayerDirectionViolationLines(
792
+ applicationSource,
793
+ 'apps/ios/Features/Orders/Application/UseCases/LoadOrders.swift'
794
+ ),
795
+ [3, 4, 5]
796
+ );
797
+ assert.deepEqual(
798
+ collectSwiftLayerDirectionViolationLines(
799
+ presentationSource,
800
+ 'apps/ios/Features/Orders/Presentation/OrdersView.swift'
801
+ ),
802
+ [3, 5]
803
+ );
804
+ assert.equal(
805
+ hasSwiftLayerDirectionViolationUsage(
806
+ safeDomain,
807
+ 'apps/ios/Features/Orders/Domain/Entities/Order.swift'
808
+ ),
809
+ false
810
+ );
811
+ });
812
+
813
+ test('hasSwiftExcessivePublicApiUsage detecta public/open excesivo en app iOS', () => {
814
+ const appSource = `
815
+ import SwiftUI
816
+
817
+ public final class BuyerShellViewModel {
818
+ public var selectedTab: Tab = .home
819
+ public init() {}
820
+ public func openCheckout() {}
821
+ }
822
+ `;
823
+ const minimalPublicSource = `
824
+ public struct CheckoutRoute {}
825
+ internal struct CheckoutState {}
826
+ private struct CheckoutReducer {}
827
+ `;
828
+ const packageSource = `
829
+ public struct PublicSDKSurface {}
830
+ public func makeClient() {}
831
+ public let version = "1"
832
+ `;
833
+
834
+ assert.equal(
835
+ hasSwiftExcessivePublicApiUsage(
836
+ appSource,
837
+ 'apps/ios/Presentation/Features/BuyerCommerce/BuyerShellViewModel.swift'
838
+ ),
839
+ true
840
+ );
841
+ assert.deepEqual(
842
+ collectSwiftExcessivePublicApiLines(
843
+ appSource,
844
+ 'apps/ios/Presentation/Features/BuyerCommerce/BuyerShellViewModel.swift'
845
+ ),
846
+ [4, 5, 6, 7]
847
+ );
848
+ assert.equal(
849
+ hasSwiftExcessivePublicApiUsage(
850
+ minimalPublicSource,
851
+ 'apps/ios/Presentation/Features/BuyerCommerce/CheckoutRoute.swift'
852
+ ),
853
+ false
854
+ );
855
+ assert.deepEqual(
856
+ collectSwiftExcessivePublicApiLines(
857
+ packageSource,
858
+ 'apps/ios/Sources/PublicSDK/PublicSDK.swift'
859
+ ),
860
+ []
861
+ );
862
+ });
863
+
864
+ test('hasSwiftDefaultActorIsolationNotMainActorUsage detecta aislamiento por defecto distinto de MainActor', () => {
865
+ const source = `
866
+ buildSettings = {
867
+ SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
868
+ OTHER_SWIFT_FLAGS = "$(inherited)";
869
+ SWIFT_DEFAULT_ACTOR_ISOLATION = SomeCustomActor;
870
+ };
871
+ `;
872
+ const safe = `
873
+ buildSettings = {
874
+ SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
875
+ SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor.self;
876
+ let sample = "SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;"
877
+ // SWIFT_DEFAULT_ACTOR_ISOLATION = SomeCustomActor;
878
+ };
879
+ `;
880
+
881
+ assert.equal(hasSwiftDefaultActorIsolationNotMainActorUsage(source), true);
882
+ assert.deepEqual(collectSwiftDefaultActorIsolationNotMainActorLines(source), [3, 5]);
883
+ assert.equal(hasSwiftDefaultActorIsolationNotMainActorUsage(safe), false);
884
+ assert.deepEqual(collectSwiftDefaultActorIsolationNotMainActorLines(safe), []);
885
+ });
886
+
599
887
  test('hasSwiftNonLazyScrollForEachUsage detecta ScrollView con stack no lazy y preserva LazyVStack', () => {
600
888
  const source = `
601
889
  struct FeedView: View {
@@ -1733,6 +2021,30 @@ func wait() async throws {
1733
2021
  assert.equal(hasSwiftMainThreadBlockingSleepUsage(ignored), false);
1734
2022
  });
1735
2023
 
2024
+ test('detector iOS de concurrencia detecta depuracion centrada en hilos', () => {
2025
+ const source = `
2026
+ func traceCurrentExecution() {
2027
+ print(Thread.current)
2028
+ if Thread.isMainThread { recordMainThread() }
2029
+ let pthread = pthread_self()
2030
+ let mach = pthread_mach_thread_np(pthread)
2031
+ }
2032
+ `;
2033
+ const ignored = `
2034
+ @MainActor
2035
+ func updateUI() {
2036
+ let description = "Thread.current"
2037
+ // Thread.isMainThread
2038
+ Task { await reload() }
2039
+ }
2040
+ `;
2041
+
2042
+ assert.equal(hasSwiftThreadCentricDebuggingUsage(source), true);
2043
+ assert.deepEqual(collectSwiftThreadCentricDebuggingLines(source), [3, 4, 5, 6]);
2044
+ assert.equal(hasSwiftThreadCentricDebuggingUsage(ignored), false);
2045
+ assert.deepEqual(collectSwiftThreadCentricDebuggingLines(ignored), []);
2046
+ });
2047
+
1736
2048
  test('detector iOS de concurrencia detecta async privado sin await y evita boundaries publicos', () => {
1737
2049
  const source = `
1738
2050
  final class ProfileLoader {
@@ -1761,6 +2073,33 @@ final class ProfileLoader: RemoteLoader {
1761
2073
  assert.equal(hasSwiftAsyncWithoutAwaitUsage(ignored), false);
1762
2074
  });
1763
2075
 
2076
+ test('detector iOS de concurrencia detecta dummy awaits usados como workaround', () => {
2077
+ const source = `
2078
+ final class Loader {
2079
+ func load() async {
2080
+ await Task.yield()
2081
+ try? await Task.sleep(nanoseconds: 0)
2082
+ try? await Task.sleep(for: .zero)
2083
+ try? await Task.sleep(for: .seconds(0))
2084
+ }
2085
+ }
2086
+ `;
2087
+ const ignored = `
2088
+ final class Loader {
2089
+ func load() async throws {
2090
+ try await Task.sleep(for: .seconds(1))
2091
+ let message = "await Task.yield()"
2092
+ // await Task.yield()
2093
+ }
2094
+ }
2095
+ `;
2096
+
2097
+ assert.equal(hasSwiftDummyAwaitUsage(source), true);
2098
+ assert.deepEqual(collectSwiftDummyAwaitLines(source), [4, 5, 6, 7]);
2099
+ assert.equal(hasSwiftDummyAwaitUsage(ignored), false);
2100
+ assert.deepEqual(collectSwiftDummyAwaitLines(ignored), []);
2101
+ });
2102
+
1764
2103
  test('detector iOS de accesibilidad detecta botones icon-only sin label explicita', () => {
1765
2104
  const source = `
1766
2105
  struct ToolbarView: View {
@@ -2005,6 +2344,81 @@ struct RowView: View {
2005
2344
  assert.deepEqual(collectSwiftOnTapGestureWithoutButtonTraitLines(safe), []);
2006
2345
  });
2007
2346
 
2347
+ test('hasSwiftGlassInteractiveOnStaticElementUsage detecta Liquid Glass interactivo en contenido estatico', () => {
2348
+ const source = `
2349
+ struct CardView: View {
2350
+ var body: some View {
2351
+ Text("Estado")
2352
+ .padding()
2353
+ .glassEffect(.regular.interactive(), in: .rect(cornerRadius: 16))
2354
+ }
2355
+ }
2356
+ `;
2357
+ const safe = `
2358
+ struct CardView: View {
2359
+ var body: some View {
2360
+ Button("Continuar") { submit() }
2361
+ .glassEffect(.regular.interactive(), in: .capsule)
2362
+
2363
+ Text("Mas informacion")
2364
+ .onTapGesture { showDetails() }
2365
+ .glassEffect(.regular.interactive(), in: .rect(cornerRadius: 16))
2366
+
2367
+ Text("decorativo")
2368
+ .glassEffect(.regular, in: .rect(cornerRadius: 16))
2369
+
2370
+ let sample = ".glassEffect(.regular.interactive())"
2371
+ // .glassEffect(.regular.interactive())
2372
+ }
2373
+ }
2374
+ `;
2375
+
2376
+ assert.equal(hasSwiftGlassInteractiveOnStaticElementUsage(source), true);
2377
+ assert.deepEqual(collectSwiftGlassInteractiveOnStaticElementLines(source), [6]);
2378
+ assert.equal(hasSwiftGlassInteractiveOnStaticElementUsage(safe), false);
2379
+ assert.deepEqual(collectSwiftGlassInteractiveOnStaticElementLines(safe), []);
2380
+ });
2381
+
2382
+ test('hasSwiftGlassEffectIDWithoutNamespaceUsage detecta glassEffectID sin Namespace real', () => {
2383
+ const source = `
2384
+ struct CardView: View {
2385
+ var body: some View {
2386
+ Text("Card")
2387
+ .glassEffect()
2388
+ .glassEffectID("card", in: animation)
2389
+ }
2390
+ }
2391
+ `;
2392
+ const safe = `
2393
+ struct CardView: View {
2394
+ @Namespace private var animation
2395
+
2396
+ var body: some View {
2397
+ Text("Card")
2398
+ .glassEffect()
2399
+ .glassEffectID("card", in: animation)
2400
+ }
2401
+ }
2402
+
2403
+ struct ChildCard: View {
2404
+ let animation: Namespace.ID
2405
+
2406
+ var body: some View {
2407
+ Text("Card")
2408
+ .glassEffectID("card", in: animation)
2409
+ }
2410
+ }
2411
+
2412
+ let sample = ".glassEffectID(\\"card\\", in: animation)"
2413
+ // .glassEffectID("card", in: animation)
2414
+ `;
2415
+
2416
+ assert.equal(hasSwiftGlassEffectIDWithoutNamespaceUsage(source), true);
2417
+ assert.deepEqual(collectSwiftGlassEffectIDWithoutNamespaceLines(source), [6]);
2418
+ assert.equal(hasSwiftGlassEffectIDWithoutNamespaceUsage(safe), false);
2419
+ assert.deepEqual(collectSwiftGlassEffectIDWithoutNamespaceLines(safe), []);
2420
+ });
2421
+
2008
2422
  test('detectores legacy ignoran strings y comentarios', () => {
2009
2423
  const source = `
2010
2424
  // Task.detached { }
@@ -2779,6 +3193,51 @@ struct LoginTests {
2779
3193
  assert.deepEqual(collectSwiftQuickNimbleLines(swiftTesting), []);
2780
3194
  });
2781
3195
 
3196
+ test('hasSwiftThirdPartyUiTestFrameworkUsage detecta frameworks UI test no nativos', () => {
3197
+ const kifSuite = `
3198
+ import XCTest
3199
+ import KIF
3200
+
3201
+ final class CheckoutUITests: XCTestCase {
3202
+ func testCheckout() {
3203
+ tester().tapView(withAccessibilityLabel: "Pay")
3204
+ }
3205
+ }
3206
+ `;
3207
+ const earlGreySuite = `
3208
+ import XCTest
3209
+ import EarlGrey
3210
+
3211
+ final class LoginUITests: XCTestCase {
3212
+ func testLogin() {
3213
+ EarlGrey.selectElement(with: grey_accessibilityID("login")).perform(grey_tap())
3214
+ }
3215
+ }
3216
+ `;
3217
+ const nativeXcuiTest = `
3218
+ import XCTest
3219
+
3220
+ final class LoginUITests: XCTestCase {
3221
+ func testLogin() {
3222
+ let app = XCUIApplication()
3223
+ app.buttons["login"].tap()
3224
+ }
3225
+ }
3226
+ `;
3227
+ const ignored = `
3228
+ let text = "import KIF"
3229
+ // EarlGrey.selectElement(with: grey_accessibilityID("login"))
3230
+ `;
3231
+
3232
+ assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(kifSuite), true);
3233
+ assert.deepEqual(collectSwiftThirdPartyUiTestFrameworkLines(kifSuite), [3, 7]);
3234
+ assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(earlGreySuite), true);
3235
+ assert.deepEqual(collectSwiftThirdPartyUiTestFrameworkLines(earlGreySuite), [3, 7]);
3236
+ assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(nativeXcuiTest), false);
3237
+ assert.deepEqual(collectSwiftThirdPartyUiTestFrameworkLines(nativeXcuiTest), []);
3238
+ assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(ignored), false);
3239
+ });
3240
+
2782
3241
  test('hasSwiftXCTestAssertionUsage detecta XCTAssert y XCTFail reales', () => {
2783
3242
  const source = `
2784
3243
  XCTAssertEqual(value, expected)
@@ -2795,6 +3254,24 @@ let text = "XCTAssertEqual(value, expected)"
2795
3254
  assert.deepEqual(collectSwiftXCTestAssertionLines(ignored), []);
2796
3255
  });
2797
3256
 
3257
+ test('hasSwiftXCTestAssertionUsage permite UI tests XCTest brownfield explicitos', () => {
3258
+ const uiTest = `
3259
+ import XCTest
3260
+
3261
+ final class BuyerCommerceUISmokeTests: XCTestCase {
3262
+ func testCheckoutFlow() {
3263
+ let app = XCUIApplication()
3264
+ app.launch()
3265
+ XCTAssertTrue(app.buttons["Continue"].exists)
3266
+ XCTAssertEqual(app.staticTexts["Title"].label, "RuralGo")
3267
+ }
3268
+ }
3269
+ `;
3270
+
3271
+ assert.equal(hasSwiftXCTestAssertionUsage(uiTest), false);
3272
+ assert.deepEqual(collectSwiftXCTestAssertionLines(uiTest), []);
3273
+ });
3274
+
2798
3275
  test('hasSwiftXCTUnwrapUsage detecta XCTUnwrap real y evita strings', () => {
2799
3276
  const source = `
2800
3277
  let value = try XCTUnwrap(optionalValue)
@@ -2832,6 +3309,24 @@ let sample = "waitForExpectations(timeout: 1)"
2832
3309
  assert.deepEqual(collectSwiftWaitForExpectationsLines(modernWait), []);
2833
3310
  });
2834
3311
 
3312
+ test('hasSwiftWaitForExpectationsUsage permite waits en UI tests XCTest brownfield explicitos', () => {
3313
+ const uiWait = `
3314
+ import XCTest
3315
+
3316
+ final class BuyerCommerceUISmokeTests: XCTestCase {
3317
+ func testCheckoutFlow() {
3318
+ let app = XCUIApplication()
3319
+ app.launch()
3320
+ XCTAssertTrue(app.buttons["Continue"].waitForExistence(timeout: 5))
3321
+ wait(for: [expectation(description: "Loaded")], timeout: 1)
3322
+ }
3323
+ }
3324
+ `;
3325
+
3326
+ assert.equal(hasSwiftWaitForExpectationsUsage(uiWait), false);
3327
+ assert.deepEqual(collectSwiftWaitForExpectationsLines(uiWait), []);
3328
+ });
3329
+
2835
3330
  test('hasSwiftLegacyExpectationDescriptionUsage detecta expectation(description:) sin flujo moderno', () => {
2836
3331
  const legacyExpectation = `
2837
3332
  let expectation = expectation(description: "Done")