pumuki 6.3.218 → 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.
@@ -43,6 +43,7 @@ import {
43
43
  hasSwiftNSManagedObjectBoundaryUsage,
44
44
  hasSwiftNSManagedObjectStateLeakUsage,
45
45
  hasSwiftNavigationViewUsage,
46
+ hasSwiftNonPrivateStateOwnershipUsage,
46
47
  hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage,
47
48
  hasSwiftObservableObjectUsage,
48
49
  hasSwiftOnAppearTaskUsage,
@@ -60,6 +61,7 @@ import {
60
61
  hasSwiftSensitiveUserDefaultsStorageUsage,
61
62
  hasSwiftInsecureTransportUsage,
62
63
  hasSwiftJSONSerializationUsage,
64
+ hasSwiftInlineForEachTransformUsage,
63
65
  hasSwiftStringFormatUsage,
64
66
  hasSwiftStrongDelegateReferenceUsage,
65
67
  hasSwiftStrongSelfEscapingClosureUsage,
@@ -735,6 +737,9 @@ let filtered = items.filter { $0.title.contains(searchText) }
735
737
  ForEach(items.indices, id: \\.self) { index in
736
738
  Text(items[index].title)
737
739
  }
740
+ ForEach(items.filter { $0.isVisible }) { item in
741
+ Text(item.title)
742
+ }
738
743
  Text("Primary").foregroundColor(.blue)
739
744
  Image("hero").cornerRadius(12)
740
745
  TabView {
@@ -770,6 +775,7 @@ MainActor.assumeIsolated { reload() }
770
775
  assert.equal(hasSwiftNonisolatedUnsafeUsage(source), true);
771
776
  assert.equal(hasSwiftAssumeIsolatedUsage(source), true);
772
777
  assert.equal(hasSwiftForEachIndicesUsage(source), true);
778
+ assert.equal(hasSwiftInlineForEachTransformUsage(source), true);
773
779
  assert.equal(hasSwiftContainsUserFilterUsage(source), true);
774
780
  assert.equal(hasSwiftGeometryReaderUsage(source), true);
775
781
  assert.equal(hasSwiftFontWeightBoldUsage(source), true);
@@ -811,11 +817,13 @@ let q = ".fontWeight(.bold)"
811
817
  let r = "@preconcurrency import LegacyFramework"
812
818
  let s = "nonisolated(unsafe) static var sharedBridge: Model?"
813
819
  let t = "MainActor.assumeIsolated { reload() }"
820
+ let u = "ForEach(items.filter { $0.isVisible }) { item in }"
814
821
  `;
815
822
  assert.equal(hasSwiftPreconcurrencyUsage(source), false);
816
823
  assert.equal(hasSwiftNonisolatedUnsafeUsage(source), false);
817
824
  assert.equal(hasSwiftAssumeIsolatedUsage(source), false);
818
825
  assert.equal(hasSwiftForEachIndicesUsage(source), false);
826
+ assert.equal(hasSwiftInlineForEachTransformUsage(source), false);
819
827
  assert.equal(hasSwiftContainsUserFilterUsage(source), false);
820
828
  assert.equal(hasSwiftGeometryReaderUsage(source), false);
821
829
  assert.equal(hasSwiftFontWeightBoldUsage(source), false);
@@ -928,6 +936,65 @@ struct ContentView: View {
928
936
  assert.equal(hasSwiftLegacySwiftUiObservableWrapperUsage(modernWrapper), false);
929
937
  });
930
938
 
939
+ test('hasSwiftNonPrivateStateOwnershipUsage detecta @State y @StateObject no privados', () => {
940
+ const source = `
941
+ struct DashboardView: View {
942
+ @State var query = ""
943
+ @StateObject var viewModel = DashboardViewModel()
944
+ }
945
+ `;
946
+ const safe = `
947
+ struct DashboardView: View {
948
+ @State private var query = ""
949
+ @StateObject private var viewModel = DashboardViewModel()
950
+ let text = "@State var query = \\"\\""
951
+ // @State var query = ""
952
+ }
953
+ `;
954
+
955
+ assert.equal(hasSwiftNonPrivateStateOwnershipUsage(source), true);
956
+ assert.equal(hasSwiftNonPrivateStateOwnershipUsage(safe), false);
957
+ });
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
+
931
998
  test('hasSwiftPassedValueStateWrapperUsage detecta valores inyectados guardados como @State o @StateObject', () => {
932
999
  const invalidOwnership = `
933
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
@@ -860,6 +867,17 @@ export const hasSwiftLegacySwiftUiObservableWrapperUsage = (source: string): boo
860
867
  return hasSwiftSanitizedRegexMatch(source, /@\s*(?:StateObject|ObservedObject)\b/);
861
868
  };
862
869
 
870
+ export const hasSwiftNonPrivateStateOwnershipUsage = (source: string): boolean => {
871
+ return source.split(/\r?\n/).some((line) => {
872
+ const sanitizedLine = stripSwiftLineForSemanticScan(line);
873
+ return (
874
+ /@\s*(?:State|StateObject)\b/.test(sanitizedLine) &&
875
+ /\bvar\b/.test(sanitizedLine) &&
876
+ !/\bprivate\b/.test(sanitizedLine)
877
+ );
878
+ });
879
+ };
880
+
863
881
  const hasSwiftPassedValueWrapperInitialization = (
864
882
  source: string,
865
883
  options: {
@@ -675,8 +675,10 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
675
675
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAssumeIsolatedUsage, ruleId: 'heuristics.ios.assume-isolated.ast', code: 'HEURISTICS_IOS_ASSUME_ISOLATED_AST', message: 'AST heuristic detected assumeIsolated usage.' },
676
676
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftObservableObjectUsage, ruleId: 'heuristics.ios.observable-object.ast', code: 'HEURISTICS_IOS_OBSERVABLE_OBJECT_AST', message: 'AST heuristic detected ObservableObject usage.' },
677
677
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftLegacySwiftUiObservableWrapperUsage, ruleId: 'heuristics.ios.legacy-swiftui-observable-wrapper.ast', code: 'HEURISTICS_IOS_LEGACY_SWIFTUI_OBSERVABLE_WRAPPER_AST', message: 'AST heuristic detected @StateObject/@ObservedObject usage in a modern SwiftUI path.' },
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.' },
678
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.' },
679
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.' },
680
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.' },
681
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.' },
682
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.' },
@@ -730,6 +730,25 @@ export const iosRules: RuleSet = [
730
730
  code: 'HEURISTICS_IOS_LEGACY_SWIFTUI_OBSERVABLE_WRAPPER_AST',
731
731
  },
732
732
  },
733
+ {
734
+ id: 'heuristics.ios.swiftui.non-private-state-ownership.ast',
735
+ description: 'Detects @State/@StateObject declarations without private visibility in SwiftUI presentation code.',
736
+ severity: 'WARN',
737
+ platform: 'ios',
738
+ locked: true,
739
+ when: {
740
+ kind: 'Heuristic',
741
+ where: {
742
+ ruleId: 'heuristics.ios.swiftui.non-private-state-ownership.ast',
743
+ },
744
+ },
745
+ then: {
746
+ kind: 'Finding',
747
+ message:
748
+ 'AST heuristic detected @State/@StateObject without private visibility; SwiftUI owned state should be private.',
749
+ code: 'HEURISTICS_IOS_SWIFTUI_NON_PRIVATE_STATE_OWNERSHIP_AST',
750
+ },
751
+ },
733
752
  {
734
753
  id: 'heuristics.ios.passed-value-state-wrapper.ast',
735
754
  description: 'Detects passed values stored as @State or @StateObject through init ownership in SwiftUI production code.',
@@ -766,6 +785,24 @@ export const iosRules: RuleSet = [
766
785
  code: 'HEURISTICS_IOS_FOREACH_INDICES_AST',
767
786
  },
768
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
+ },
769
806
  {
770
807
  id: 'heuristics.ios.contains-user-filter.ast',
771
808
  description: 'Detects contains() usage in user-facing filter flows where localizedStandardContains() may be preferred.',
@@ -201,6 +201,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
201
201
  'ios.legacy-swiftui-observable-wrapper',
202
202
  ['heuristics.ios.legacy-swiftui-observable-wrapper.ast']
203
203
  ),
204
+ 'skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear': heuristicDetector(
205
+ 'ios.swiftui.non-private-state-ownership',
206
+ ['heuristics.ios.swiftui.non-private-state-ownership.ast']
207
+ ),
204
208
  'skills.ios.no-passed-value-state-wrapper': heuristicDetector('ios.passed-value-state-wrapper', [
205
209
  'heuristics.ios.passed-value-state-wrapper.ast',
206
210
  ]),
@@ -225,6 +229,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
225
229
  'skills.ios.no-foreach-indices': heuristicDetector('ios.foreach-indices', [
226
230
  'heuristics.ios.foreach-indices.ast',
227
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
+ ]),
228
236
  'skills.ios.no-contains-user-filter': heuristicDetector('ios.contains-user-filter', [
229
237
  'heuristics.ios.contains-user-filter.ast',
230
238
  ]),
@@ -260,6 +260,13 @@ const normalizeKnownRuleTarget = (
260
260
  ) {
261
261
  return 'skills.ios.no-legacy-swiftui-observable-wrapper';
262
262
  }
263
+ if (
264
+ includes('state and stateobject as private') ||
265
+ includes('stateobject as private') ||
266
+ (includes('mark state') && includes('private'))
267
+ ) {
268
+ return 'skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear';
269
+ }
263
270
  if (
264
271
  includes('passed values as state') ||
265
272
  includes('passed values as state or stateobject') ||
@@ -302,6 +309,13 @@ const normalizeKnownRuleTarget = (
302
309
  ) {
303
310
  return 'skills.ios.no-foreach-indices';
304
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
+ }
305
319
  if (
306
320
  includes('localizedstandardcontains') ||
307
321
  includes('localized standard contains') ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.218",
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-13T13:40:06.121Z",
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": "05e71c648612ed794d4e8122105fc3f508e09b7f92db94a24bd3daa45335d6b7",
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",
@@ -8643,7 +8643,7 @@
8643
8643
  "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8644
8644
  "confidence": "MEDIUM",
8645
8645
  "locked": true,
8646
- "evaluationMode": "DECLARATIVE",
8646
+ "evaluationMode": "AUTO",
8647
8647
  "origin": "core"
8648
8648
  },
8649
8649
  {
@@ -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
  {