pumuki 6.3.219 → 6.3.221

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.
@@ -61,6 +61,8 @@ import {
61
61
  hasSwiftSensitiveUserDefaultsStorageUsage,
62
62
  hasSwiftInsecureTransportUsage,
63
63
  hasSwiftJSONSerializationUsage,
64
+ hasSwiftExplicitColorStaticMemberUsage,
65
+ hasSwiftInlineForEachTransformUsage,
64
66
  hasSwiftStringFormatUsage,
65
67
  hasSwiftStrongDelegateReferenceUsage,
66
68
  hasSwiftStrongSelfEscapingClosureUsage,
@@ -732,10 +734,14 @@ GeometryReader { proxy in
732
734
  Text("x").frame(width: proxy.size.width)
733
735
  }
734
736
  Text("Headline").fontWeight(.bold)
737
+ Text("State").foregroundStyle(Color.green)
735
738
  let filtered = items.filter { $0.title.contains(searchText) }
736
739
  ForEach(items.indices, id: \\.self) { index in
737
740
  Text(items[index].title)
738
741
  }
742
+ ForEach(items.filter { $0.isVisible }) { item in
743
+ Text(item.title)
744
+ }
739
745
  Text("Primary").foregroundColor(.blue)
740
746
  Image("hero").cornerRadius(12)
741
747
  TabView {
@@ -771,9 +777,11 @@ MainActor.assumeIsolated { reload() }
771
777
  assert.equal(hasSwiftNonisolatedUnsafeUsage(source), true);
772
778
  assert.equal(hasSwiftAssumeIsolatedUsage(source), true);
773
779
  assert.equal(hasSwiftForEachIndicesUsage(source), true);
780
+ assert.equal(hasSwiftInlineForEachTransformUsage(source), true);
774
781
  assert.equal(hasSwiftContainsUserFilterUsage(source), true);
775
782
  assert.equal(hasSwiftGeometryReaderUsage(source), true);
776
783
  assert.equal(hasSwiftFontWeightBoldUsage(source), true);
784
+ assert.equal(hasSwiftExplicitColorStaticMemberUsage(source), true);
777
785
  assert.equal(hasSwiftObservableObjectUsage(source), true);
778
786
  assert.equal(hasSwiftLegacySwiftUiObservableWrapperUsage(source), true);
779
787
  assert.equal(hasSwiftNavigationViewUsage(source), true);
@@ -812,14 +820,18 @@ let q = ".fontWeight(.bold)"
812
820
  let r = "@preconcurrency import LegacyFramework"
813
821
  let s = "nonisolated(unsafe) static var sharedBridge: Model?"
814
822
  let t = "MainActor.assumeIsolated { reload() }"
823
+ let u = "ForEach(items.filter { $0.isVisible }) { item in }"
824
+ let v = "Color.green"
815
825
  `;
816
826
  assert.equal(hasSwiftPreconcurrencyUsage(source), false);
817
827
  assert.equal(hasSwiftNonisolatedUnsafeUsage(source), false);
818
828
  assert.equal(hasSwiftAssumeIsolatedUsage(source), false);
819
829
  assert.equal(hasSwiftForEachIndicesUsage(source), false);
830
+ assert.equal(hasSwiftInlineForEachTransformUsage(source), false);
820
831
  assert.equal(hasSwiftContainsUserFilterUsage(source), false);
821
832
  assert.equal(hasSwiftGeometryReaderUsage(source), false);
822
833
  assert.equal(hasSwiftFontWeightBoldUsage(source), false);
834
+ assert.equal(hasSwiftExplicitColorStaticMemberUsage(source), false);
823
835
  assert.equal(hasSwiftTaskDetachedUsage(source), false);
824
836
  assert.equal(hasSwiftNavigationViewUsage(source), false);
825
837
  assert.equal(hasSwiftForegroundColorUsage(source), false);
@@ -837,6 +849,7 @@ let t = "MainActor.assumeIsolated { reload() }"
837
849
  test('detectores snapshot SwiftUI ignoran reemplazos modernos', () => {
838
850
  const source = `
839
851
  Text("Primary").foregroundStyle(.blue)
852
+ Text("State").foregroundStyle(.green)
840
853
  Image("hero").clipShape(.rect(cornerRadius: 12))
841
854
  Text("Headline").bold()
842
855
  TabView {
@@ -870,6 +883,7 @@ ScrollView {
870
883
  assert.equal(hasSwiftContainsUserFilterUsage(source), false);
871
884
  assert.equal(hasSwiftGeometryReaderUsage(source), false);
872
885
  assert.equal(hasSwiftFontWeightBoldUsage(source), false);
886
+ assert.equal(hasSwiftExplicitColorStaticMemberUsage(source), false);
873
887
  assert.equal(hasSwiftForegroundColorUsage(source), false);
874
888
  assert.equal(hasSwiftCornerRadiusUsage(source), false);
875
889
  assert.equal(hasSwiftTabItemUsage(source), false);
@@ -878,6 +892,31 @@ ScrollView {
878
892
  assert.equal(hasSwiftLegacyOnChangeUsage(source), false);
879
893
  });
880
894
 
895
+ test('hasSwiftExplicitColorStaticMemberUsage detecta Color.* y preserva static member lookup', () => {
896
+ const source = `
897
+ struct StatusView: View {
898
+ var body: some View {
899
+ Text("Ready").foregroundStyle(Color.green)
900
+ Circle().fill(Color.primary)
901
+ }
902
+ }
903
+ `;
904
+ const safe = `
905
+ struct StatusView: View {
906
+ var body: some View {
907
+ Text("Ready").foregroundStyle(.green)
908
+ Circle().fill(.primary)
909
+ Rectangle().fill(Color("BrandPrimary"))
910
+ }
911
+ }
912
+ let ignored = "Color.green"
913
+ // Color.primary
914
+ `;
915
+
916
+ assert.equal(hasSwiftExplicitColorStaticMemberUsage(source), true);
917
+ assert.equal(hasSwiftExplicitColorStaticMemberUsage(safe), false);
918
+ });
919
+
881
920
  test('hasSwiftLegacyXCTestImportUsage detecta XCTest unitario y excluye UI/performance', () => {
882
921
  const unitTest = `
883
922
  import XCTest
@@ -949,6 +988,45 @@ struct DashboardView: View {
949
988
  assert.equal(hasSwiftNonPrivateStateOwnershipUsage(safe), false);
950
989
  });
951
990
 
991
+ test('hasSwiftInlineForEachTransformUsage detecta transformaciones inline y preserva colecciones precomputadas', () => {
992
+ const source = `
993
+ struct FeedView: View {
994
+ var body: some View {
995
+ List {
996
+ ForEach(items.filter { $0.isVisible }) { item in
997
+ Text(item.title)
998
+ }
999
+ ForEach(Array(sections.sorted(by: { $0.title < $1.title }))) { section in
1000
+ Text(section.title)
1001
+ }
1002
+ }
1003
+ }
1004
+ }
1005
+ `;
1006
+ const safe = `
1007
+ struct FeedView: View {
1008
+ let filteredItems: [Item]
1009
+ let sortedSections: [Section]
1010
+
1011
+ var body: some View {
1012
+ List {
1013
+ ForEach(filteredItems) { item in
1014
+ Text(item.title)
1015
+ }
1016
+ ForEach(sortedSections) { section in
1017
+ Text(section.title)
1018
+ }
1019
+ }
1020
+ }
1021
+ }
1022
+ let ignored = "ForEach(items.filter { $0.isVisible }) { item in }"
1023
+ // ForEach(items.filter { $0.isVisible }) { item in }
1024
+ `;
1025
+
1026
+ assert.equal(hasSwiftInlineForEachTransformUsage(source), true);
1027
+ assert.equal(hasSwiftInlineForEachTransformUsage(safe), false);
1028
+ });
1029
+
952
1030
  test('hasSwiftPassedValueStateWrapperUsage detecta valores inyectados guardados como @State o @StateObject', () => {
953
1031
  const invalidOwnership = `
954
1032
  struct DetailView: View {
@@ -810,6 +810,13 @@ export const hasSwiftForEachIndicesUsage = (source: string): boolean => {
810
810
  );
811
811
  };
812
812
 
813
+ export const hasSwiftInlineForEachTransformUsage = (source: string): boolean => {
814
+ const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
815
+ return /\bForEach\s*\(\s*(?:Array\s*\(\s*)?[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*\s*\.\s*(?:filter|map|compactMap|sorted)\s*(?:\{|\()/g.test(
816
+ sanitized
817
+ );
818
+ };
819
+
813
820
  const isUserSearchIdentifier = (value: string): boolean => {
814
821
  return /^(?:query|search(?:Text|Term|Query|Value)?|filter(?:Text|Value)?|text|term|input)$/i.test(
815
822
  value
@@ -856,6 +863,13 @@ export const hasSwiftFontWeightBoldUsage = (source: string): boolean => {
856
863
  return hasSwiftSanitizedRegexMatch(source, /\.\s*fontWeight\s*\(\s*\.bold\s*\)/g);
857
864
  };
858
865
 
866
+ export const hasSwiftExplicitColorStaticMemberUsage = (source: string): boolean => {
867
+ return hasSwiftSanitizedRegexMatch(
868
+ source,
869
+ /\bColor\s*\.\s*(?:accentColor|black|blue|brown|clear|cyan|gray|green|indigo|mint|orange|pink|primary|purple|red|secondary|teal|white|yellow)\b/g
870
+ );
871
+ };
872
+
859
873
  export const hasSwiftLegacySwiftUiObservableWrapperUsage = (source: string): boolean => {
860
874
  return hasSwiftSanitizedRegexMatch(source, /@\s*(?:StateObject|ObservedObject)\b/);
861
875
  };
@@ -678,9 +678,11 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
678
678
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonPrivateStateOwnershipUsage, ruleId: 'heuristics.ios.swiftui.non-private-state-ownership.ast', code: 'HEURISTICS_IOS_SWIFTUI_NON_PRIVATE_STATE_OWNERSHIP_AST', message: 'AST heuristic detected @State/@StateObject without private visibility; SwiftUI owned state should be private.' },
679
679
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPassedValueStateWrapperUsage, ruleId: 'heuristics.ios.passed-value-state-wrapper.ast', code: 'HEURISTICS_IOS_PASSED_VALUE_STATE_WRAPPER_AST', message: 'AST heuristic detected a passed value stored as @State/@StateObject via init wrapper ownership.' },
680
680
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForEachIndicesUsage, ruleId: 'heuristics.ios.foreach-indices.ast', code: 'HEURISTICS_IOS_FOREACH_INDICES_AST', message: 'AST heuristic detected ForEach(...indices...) usage where stable element identity may be preferred.' },
681
+ { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInlineForEachTransformUsage, ruleId: 'heuristics.ios.swiftui.inline-foreach-transform.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FOREACH_TRANSFORM_AST', message: 'AST heuristic detected inline filter/map/sort work inside ForEach; prefiltered or cached collections remain the preferred baseline.' },
681
682
  { platform: 'ios', pathCheck: isIOSApplicationOrPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftContainsUserFilterUsage, ruleId: 'heuristics.ios.contains-user-filter.ast', code: 'HEURISTICS_IOS_CONTAINS_USER_FILTER_AST', message: 'AST heuristic detected contains() in a user-facing filter where localizedStandardContains() may be preferred.' },
682
683
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftGeometryReaderUsage, ruleId: 'heuristics.ios.geometryreader.ast', code: 'HEURISTICS_IOS_GEOMETRYREADER_AST', message: 'AST heuristic detected GeometryReader usage that may be replaceable with modern layout APIs.' },
683
684
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftFontWeightBoldUsage, ruleId: 'heuristics.ios.font-weight-bold.ast', code: 'HEURISTICS_IOS_FONT_WEIGHT_BOLD_AST', message: 'AST heuristic detected fontWeight(.bold) usage where bold() may be preferred.' },
685
+ { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftExplicitColorStaticMemberUsage, ruleId: 'heuristics.ios.swiftui.explicit-color-static-member.ast', code: 'HEURISTICS_IOS_SWIFTUI_EXPLICIT_COLOR_STATIC_MEMBER_AST', message: 'AST heuristic detected Color.* static member usage where SwiftUI static member lookup may be preferred.' },
684
686
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNavigationViewUsage, ruleId: 'heuristics.ios.navigation-view.ast', code: 'HEURISTICS_IOS_NAVIGATION_VIEW_AST', message: 'AST heuristic detected NavigationView usage.' },
685
687
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForegroundColorUsage, ruleId: 'heuristics.ios.foreground-color.ast', code: 'HEURISTICS_IOS_FOREGROUND_COLOR_AST', message: 'AST heuristic detected foregroundColor usage.' },
686
688
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftCornerRadiusUsage, ruleId: 'heuristics.ios.corner-radius.ast', code: 'HEURISTICS_IOS_CORNER_RADIUS_AST', message: 'AST heuristic detected cornerRadius usage.' },
@@ -785,6 +785,24 @@ export const iosRules: RuleSet = [
785
785
  code: 'HEURISTICS_IOS_FOREACH_INDICES_AST',
786
786
  },
787
787
  },
788
+ {
789
+ id: 'heuristics.ios.swiftui.inline-foreach-transform.ast',
790
+ description: 'Detects inline filter/map/sort transformations inside SwiftUI ForEach calls.',
791
+ severity: 'WARN',
792
+ platform: 'ios',
793
+ locked: true,
794
+ when: {
795
+ kind: 'Heuristic',
796
+ where: {
797
+ ruleId: 'heuristics.ios.swiftui.inline-foreach-transform.ast',
798
+ },
799
+ },
800
+ then: {
801
+ kind: 'Finding',
802
+ message: 'AST heuristic detected inline filter/map/sort work inside ForEach; prefiltered or cached collections remain the preferred baseline.',
803
+ code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FOREACH_TRANSFORM_AST',
804
+ },
805
+ },
788
806
  {
789
807
  id: 'heuristics.ios.contains-user-filter.ast',
790
808
  description: 'Detects contains() usage in user-facing filter flows where localizedStandardContains() may be preferred.',
@@ -839,6 +857,24 @@ export const iosRules: RuleSet = [
839
857
  code: 'HEURISTICS_IOS_FONT_WEIGHT_BOLD_AST',
840
858
  },
841
859
  },
860
+ {
861
+ id: 'heuristics.ios.swiftui.explicit-color-static-member.ast',
862
+ description: 'Detects Color.* static member usage where SwiftUI static member lookup is available.',
863
+ severity: 'WARN',
864
+ platform: 'ios',
865
+ locked: true,
866
+ when: {
867
+ kind: 'Heuristic',
868
+ where: {
869
+ ruleId: 'heuristics.ios.swiftui.explicit-color-static-member.ast',
870
+ },
871
+ },
872
+ then: {
873
+ kind: 'Finding',
874
+ message: 'AST heuristic detected Color.* static member usage where SwiftUI static member lookup may be preferred.',
875
+ code: 'HEURISTICS_IOS_SWIFTUI_EXPLICIT_COLOR_STATIC_MEMBER_AST',
876
+ },
877
+ },
842
878
  {
843
879
  id: 'heuristics.ios.navigation-view.ast',
844
880
  description: 'Detects NavigationView usage in iOS production code.',
@@ -229,6 +229,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
229
229
  'skills.ios.no-foreach-indices': heuristicDetector('ios.foreach-indices', [
230
230
  'heuristics.ios.foreach-indices.ast',
231
231
  ]),
232
+ 'skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache':
233
+ heuristicDetector('ios.swiftui.inline-foreach-transform', [
234
+ 'heuristics.ios.swiftui.inline-foreach-transform.ast',
235
+ ]),
232
236
  'skills.ios.no-contains-user-filter': heuristicDetector('ios.contains-user-filter', [
233
237
  'heuristics.ios.contains-user-filter.ast',
234
238
  ]),
@@ -238,6 +242,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
238
242
  'skills.ios.no-font-weight-bold': heuristicDetector('ios.font-weight-bold', [
239
243
  'heuristics.ios.font-weight-bold.ast',
240
244
  ]),
245
+ 'skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue':
246
+ heuristicDetector('ios.swiftui.explicit-color-static-member', [
247
+ 'heuristics.ios.swiftui.explicit-color-static-member.ast',
248
+ ]),
241
249
  'skills.ios.no-scrollview-shows-indicators': heuristicDetector(
242
250
  'ios.scrollview-shows-indicators',
243
251
  ['heuristics.ios.scrollview-shows-indicators.ast']
@@ -309,6 +309,13 @@ const normalizeKnownRuleTarget = (
309
309
  ) {
310
310
  return 'skills.ios.no-foreach-indices';
311
311
  }
312
+ if (
313
+ (includes('inline filtering') && includes('foreach')) ||
314
+ (includes('no inline filtering') && includes('foreach')) ||
315
+ (includes('prefilter') && includes('cache') && includes('foreach'))
316
+ ) {
317
+ return 'skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache';
318
+ }
312
319
  if (
313
320
  includes('localizedstandardcontains') ||
314
321
  includes('localized standard contains') ||
@@ -332,6 +339,13 @@ const normalizeKnownRuleTarget = (
332
339
  ) {
333
340
  return 'skills.ios.no-font-weight-bold';
334
341
  }
342
+ if (
343
+ includes('static member lookup') ||
344
+ includes('color.blue') ||
345
+ (includes('.blue') && includes('color'))
346
+ ) {
347
+ return 'skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue';
348
+ }
335
349
  if (
336
350
  includes('scrollindicators hidden') ||
337
351
  includes('scroll indicators hidden') ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.219",
3
+ "version": "6.3.221",
4
4
  "description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/skills.lock.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "1.0",
3
3
  "compilerVersion": "1.0.0",
4
- "generatedAt": "2026-05-13T15:50:45.972Z",
4
+ "generatedAt": "2026-05-13T16:00:47.572Z",
5
5
  "bundles": [
6
6
  {
7
7
  "name": "android-guidelines",
@@ -8632,7 +8632,7 @@
8632
8632
  "name": "ios-swiftui-expert-guidelines",
8633
8633
  "version": "1.0.0",
8634
8634
  "source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
8635
- "hash": "b936508c5fea766c83bca01087374a8c0281ba86c4ee022d43db316b89c07cd6",
8635
+ "hash": "50cdc96e8e5b03ef6709edea0cde8959344ce5a3c57dfc93fe39d03d5695b7aa",
8636
8636
  "rules": [
8637
8637
  {
8638
8638
  "id": "skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear",
@@ -8655,7 +8655,7 @@
8655
8655
  "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8656
8656
  "confidence": "MEDIUM",
8657
8657
  "locked": true,
8658
- "evaluationMode": "DECLARATIVE",
8658
+ "evaluationMode": "AUTO",
8659
8659
  "origin": "core"
8660
8660
  },
8661
8661
  {
@@ -8715,7 +8715,7 @@
8715
8715
  "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8716
8716
  "confidence": "MEDIUM",
8717
8717
  "locked": true,
8718
- "evaluationMode": "DECLARATIVE",
8718
+ "evaluationMode": "AUTO",
8719
8719
  "origin": "core"
8720
8720
  },
8721
8721
  {