pumuki 6.3.219 → 6.3.220

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,7 @@ import {
61
61
  hasSwiftSensitiveUserDefaultsStorageUsage,
62
62
  hasSwiftInsecureTransportUsage,
63
63
  hasSwiftJSONSerializationUsage,
64
+ hasSwiftInlineForEachTransformUsage,
64
65
  hasSwiftStringFormatUsage,
65
66
  hasSwiftStrongDelegateReferenceUsage,
66
67
  hasSwiftStrongSelfEscapingClosureUsage,
@@ -736,6 +737,9 @@ let filtered = items.filter { $0.title.contains(searchText) }
736
737
  ForEach(items.indices, id: \\.self) { index in
737
738
  Text(items[index].title)
738
739
  }
740
+ ForEach(items.filter { $0.isVisible }) { item in
741
+ Text(item.title)
742
+ }
739
743
  Text("Primary").foregroundColor(.blue)
740
744
  Image("hero").cornerRadius(12)
741
745
  TabView {
@@ -771,6 +775,7 @@ MainActor.assumeIsolated { reload() }
771
775
  assert.equal(hasSwiftNonisolatedUnsafeUsage(source), true);
772
776
  assert.equal(hasSwiftAssumeIsolatedUsage(source), true);
773
777
  assert.equal(hasSwiftForEachIndicesUsage(source), true);
778
+ assert.equal(hasSwiftInlineForEachTransformUsage(source), true);
774
779
  assert.equal(hasSwiftContainsUserFilterUsage(source), true);
775
780
  assert.equal(hasSwiftGeometryReaderUsage(source), true);
776
781
  assert.equal(hasSwiftFontWeightBoldUsage(source), true);
@@ -812,11 +817,13 @@ let q = ".fontWeight(.bold)"
812
817
  let r = "@preconcurrency import LegacyFramework"
813
818
  let s = "nonisolated(unsafe) static var sharedBridge: Model?"
814
819
  let t = "MainActor.assumeIsolated { reload() }"
820
+ let u = "ForEach(items.filter { $0.isVisible }) { item in }"
815
821
  `;
816
822
  assert.equal(hasSwiftPreconcurrencyUsage(source), false);
817
823
  assert.equal(hasSwiftNonisolatedUnsafeUsage(source), false);
818
824
  assert.equal(hasSwiftAssumeIsolatedUsage(source), false);
819
825
  assert.equal(hasSwiftForEachIndicesUsage(source), false);
826
+ assert.equal(hasSwiftInlineForEachTransformUsage(source), false);
820
827
  assert.equal(hasSwiftContainsUserFilterUsage(source), false);
821
828
  assert.equal(hasSwiftGeometryReaderUsage(source), false);
822
829
  assert.equal(hasSwiftFontWeightBoldUsage(source), false);
@@ -949,6 +956,45 @@ struct DashboardView: View {
949
956
  assert.equal(hasSwiftNonPrivateStateOwnershipUsage(safe), false);
950
957
  });
951
958
 
959
+ test('hasSwiftInlineForEachTransformUsage detecta transformaciones inline y preserva colecciones precomputadas', () => {
960
+ const source = `
961
+ struct FeedView: View {
962
+ var body: some View {
963
+ List {
964
+ ForEach(items.filter { $0.isVisible }) { item in
965
+ Text(item.title)
966
+ }
967
+ ForEach(Array(sections.sorted(by: { $0.title < $1.title }))) { section in
968
+ Text(section.title)
969
+ }
970
+ }
971
+ }
972
+ }
973
+ `;
974
+ const safe = `
975
+ struct FeedView: View {
976
+ let filteredItems: [Item]
977
+ let sortedSections: [Section]
978
+
979
+ var body: some View {
980
+ List {
981
+ ForEach(filteredItems) { item in
982
+ Text(item.title)
983
+ }
984
+ ForEach(sortedSections) { section in
985
+ Text(section.title)
986
+ }
987
+ }
988
+ }
989
+ }
990
+ let ignored = "ForEach(items.filter { $0.isVisible }) { item in }"
991
+ // ForEach(items.filter { $0.isVisible }) { item in }
992
+ `;
993
+
994
+ assert.equal(hasSwiftInlineForEachTransformUsage(source), true);
995
+ assert.equal(hasSwiftInlineForEachTransformUsage(safe), false);
996
+ });
997
+
952
998
  test('hasSwiftPassedValueStateWrapperUsage detecta valores inyectados guardados como @State o @StateObject', () => {
953
999
  const invalidOwnership = `
954
1000
  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
@@ -678,6 +678,7 @@ 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.' },
@@ -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.',
@@ -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
  ]),
@@ -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') ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.219",
3
+ "version": "6.3.220",
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-13T15:54:55.035Z",
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": "ad099eaf37bfe92bde672c1042c7d4c18e3af4bbf91a1b9ee6f6a0dc0e5381b5",
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
  {