pumuki 6.3.221 → 6.3.223

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.
@@ -62,6 +62,8 @@ import {
62
62
  hasSwiftInsecureTransportUsage,
63
63
  hasSwiftJSONSerializationUsage,
64
64
  hasSwiftExplicitColorStaticMemberUsage,
65
+ hasSwiftClosureBasedViewBuilderContentUsage,
66
+ hasSwiftRedundantReactiveStateAssignmentUsage,
65
67
  hasSwiftInlineForEachTransformUsage,
66
68
  hasSwiftStringFormatUsage,
67
69
  hasSwiftStrongDelegateReferenceUsage,
@@ -735,6 +737,10 @@ GeometryReader { proxy in
735
737
  }
736
738
  Text("Headline").fontWeight(.bold)
737
739
  Text("State").foregroundStyle(Color.green)
740
+ let content: () -> Content
741
+ .onChange(of: query) { newValue in
742
+ query = newValue
743
+ }
738
744
  let filtered = items.filter { $0.title.contains(searchText) }
739
745
  ForEach(items.indices, id: \\.self) { index in
740
746
  Text(items[index].title)
@@ -782,6 +788,8 @@ MainActor.assumeIsolated { reload() }
782
788
  assert.equal(hasSwiftGeometryReaderUsage(source), true);
783
789
  assert.equal(hasSwiftFontWeightBoldUsage(source), true);
784
790
  assert.equal(hasSwiftExplicitColorStaticMemberUsage(source), true);
791
+ assert.equal(hasSwiftClosureBasedViewBuilderContentUsage(source), true);
792
+ assert.equal(hasSwiftRedundantReactiveStateAssignmentUsage(source), true);
785
793
  assert.equal(hasSwiftObservableObjectUsage(source), true);
786
794
  assert.equal(hasSwiftLegacySwiftUiObservableWrapperUsage(source), true);
787
795
  assert.equal(hasSwiftNavigationViewUsage(source), true);
@@ -822,6 +830,8 @@ let s = "nonisolated(unsafe) static var sharedBridge: Model?"
822
830
  let t = "MainActor.assumeIsolated { reload() }"
823
831
  let u = "ForEach(items.filter { $0.isVisible }) { item in }"
824
832
  let v = "Color.green"
833
+ let w = "let content: () -> Content"
834
+ let x = ".onChange(of: query) { newValue in query = newValue }"
825
835
  `;
826
836
  assert.equal(hasSwiftPreconcurrencyUsage(source), false);
827
837
  assert.equal(hasSwiftNonisolatedUnsafeUsage(source), false);
@@ -832,6 +842,8 @@ let v = "Color.green"
832
842
  assert.equal(hasSwiftGeometryReaderUsage(source), false);
833
843
  assert.equal(hasSwiftFontWeightBoldUsage(source), false);
834
844
  assert.equal(hasSwiftExplicitColorStaticMemberUsage(source), false);
845
+ assert.equal(hasSwiftClosureBasedViewBuilderContentUsage(source), false);
846
+ assert.equal(hasSwiftRedundantReactiveStateAssignmentUsage(source), false);
835
847
  assert.equal(hasSwiftTaskDetachedUsage(source), false);
836
848
  assert.equal(hasSwiftNavigationViewUsage(source), false);
837
849
  assert.equal(hasSwiftForegroundColorUsage(source), false);
@@ -850,6 +862,7 @@ test('detectores snapshot SwiftUI ignoran reemplazos modernos', () => {
850
862
  const source = `
851
863
  Text("Primary").foregroundStyle(.blue)
852
864
  Text("State").foregroundStyle(.green)
865
+ @ViewBuilder let content: Content
853
866
  Image("hero").clipShape(.rect(cornerRadius: 12))
854
867
  Text("Headline").bold()
855
868
  TabView {
@@ -884,6 +897,8 @@ ScrollView {
884
897
  assert.equal(hasSwiftGeometryReaderUsage(source), false);
885
898
  assert.equal(hasSwiftFontWeightBoldUsage(source), false);
886
899
  assert.equal(hasSwiftExplicitColorStaticMemberUsage(source), false);
900
+ assert.equal(hasSwiftClosureBasedViewBuilderContentUsage(source), false);
901
+ assert.equal(hasSwiftRedundantReactiveStateAssignmentUsage(source), false);
887
902
  assert.equal(hasSwiftForegroundColorUsage(source), false);
888
903
  assert.equal(hasSwiftCornerRadiusUsage(source), false);
889
904
  assert.equal(hasSwiftTabItemUsage(source), false);
@@ -917,6 +932,69 @@ let ignored = "Color.green"
917
932
  assert.equal(hasSwiftExplicitColorStaticMemberUsage(safe), false);
918
933
  });
919
934
 
935
+ test('hasSwiftClosureBasedViewBuilderContentUsage detecta content closure y preserva @ViewBuilder let content', () => {
936
+ const source = `
937
+ struct Card<Content: View>: View {
938
+ private let content: () -> Content
939
+
940
+ init(@ViewBuilder content: @escaping () -> Content) {
941
+ self.content = content
942
+ }
943
+ }
944
+ `;
945
+ const safe = `
946
+ struct Card<Content: View>: View {
947
+ @ViewBuilder let content: Content
948
+ }
949
+ let ignored = "let content: () -> Content"
950
+ // let content: () -> Content
951
+ `;
952
+
953
+ assert.equal(hasSwiftClosureBasedViewBuilderContentUsage(source), true);
954
+ assert.equal(hasSwiftClosureBasedViewBuilderContentUsage(safe), false);
955
+ });
956
+
957
+ test('hasSwiftRedundantReactiveStateAssignmentUsage detecta asignaciones reactivas redundantes y preserva guard de cambio', () => {
958
+ const source = `
959
+ struct SearchView: View {
960
+ @State private var query = ""
961
+
962
+ var body: some View {
963
+ TextField("Search", text: $query)
964
+ .onChange(of: query) { newValue in
965
+ query = newValue
966
+ }
967
+ .onReceive(model.$value) { value in
968
+ self.query = value
969
+ }
970
+ }
971
+ }
972
+ `;
973
+ const safe = `
974
+ struct SearchView: View {
975
+ @State private var query = ""
976
+
977
+ var body: some View {
978
+ TextField("Search", text: $query)
979
+ .onChange(of: query) { newValue in
980
+ if query != newValue {
981
+ query = newValue
982
+ }
983
+ }
984
+ .onReceive(model.$value) { value in
985
+ guard self.query != value else { return }
986
+ self.query = value
987
+ }
988
+ }
989
+ }
990
+ let ignored = ".onChange(of: query) { newValue in query = newValue }"
991
+ // .onReceive(model.$value) { value in query = value }
992
+ `;
993
+
994
+ assert.equal(hasSwiftRedundantReactiveStateAssignmentUsage(source), true);
995
+ assert.equal(hasSwiftRedundantReactiveStateAssignmentUsage(safe), false);
996
+ });
997
+
920
998
  test('hasSwiftLegacyXCTestImportUsage detecta XCTest unitario y excluye UI/performance', () => {
921
999
  const unitTest = `
922
1000
  import XCTest
@@ -870,6 +870,35 @@ export const hasSwiftExplicitColorStaticMemberUsage = (source: string): boolean
870
870
  );
871
871
  };
872
872
 
873
+ export const hasSwiftClosureBasedViewBuilderContentUsage = (source: string): boolean => {
874
+ return hasSwiftSanitizedRegexMatch(
875
+ source,
876
+ /\b(?:let|var)\s+content\s*:\s*(?:\(\s*\)\s*->|@\s*escaping\s*\(\s*\)\s*->)\s*(?:some\s+View|Content)\b/g
877
+ );
878
+ };
879
+
880
+ export const hasSwiftRedundantReactiveStateAssignmentUsage = (source: string): boolean => {
881
+ const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
882
+ const reactiveAssignmentPattern =
883
+ /\.(?:onChange|onReceive)\s*\([^)]*\)\s*\{[\s\S]{0,500}?\b(?:self\s*\.\s*)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:newValue|value|output|receivedValue)\b/g;
884
+
885
+ for (const match of sanitized.matchAll(reactiveAssignmentPattern)) {
886
+ const target = match[1];
887
+ const segment = match[0] ?? '';
888
+ if (!target) {
889
+ continue;
890
+ }
891
+ const guardedAssignmentPattern = new RegExp(
892
+ `\\b(?:if|guard)\\s+(?:self\\s*\\.\\s*)?${target}\\s*!=\\s*(?:newValue|value|output|receivedValue)\\b`
893
+ );
894
+ if (!guardedAssignmentPattern.test(segment)) {
895
+ return true;
896
+ }
897
+ }
898
+
899
+ return false;
900
+ };
901
+
873
902
  export const hasSwiftLegacySwiftUiObservableWrapperUsage = (source: string): boolean => {
874
903
  return hasSwiftSanitizedRegexMatch(source, /@\s*(?:StateObject|ObservedObject)\b/);
875
904
  };
@@ -683,6 +683,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
683
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.' },
684
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
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.' },
686
+ { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftClosureBasedViewBuilderContentUsage, ruleId: 'heuristics.ios.swiftui.closure-based-viewbuilder-content.ast', code: 'HEURISTICS_IOS_SWIFTUI_CLOSURE_BASED_VIEWBUILDER_CONTENT_AST', message: 'AST heuristic detected closure-based content storage; @ViewBuilder let content: Content remains the preferred SwiftUI container baseline.' },
687
+ { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftRedundantReactiveStateAssignmentUsage, ruleId: 'heuristics.ios.swiftui.redundant-reactive-state-assignment.ast', code: 'HEURISTICS_IOS_SWIFTUI_REDUNDANT_REACTIVE_STATE_ASSIGNMENT_AST', message: 'AST heuristic detected reactive state assignment without a value-change guard; check for value changes before assigning state in hot paths.' },
686
688
  { 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.' },
687
689
  { 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.' },
688
690
  { 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.' },
@@ -875,6 +875,42 @@ export const iosRules: RuleSet = [
875
875
  code: 'HEURISTICS_IOS_SWIFTUI_EXPLICIT_COLOR_STATIC_MEMBER_AST',
876
876
  },
877
877
  },
878
+ {
879
+ id: 'heuristics.ios.swiftui.closure-based-viewbuilder-content.ast',
880
+ description: 'Detects closure-based SwiftUI content properties where @ViewBuilder let content: Content is preferred.',
881
+ severity: 'WARN',
882
+ platform: 'ios',
883
+ locked: true,
884
+ when: {
885
+ kind: 'Heuristic',
886
+ where: {
887
+ ruleId: 'heuristics.ios.swiftui.closure-based-viewbuilder-content.ast',
888
+ },
889
+ },
890
+ then: {
891
+ kind: 'Finding',
892
+ message: 'AST heuristic detected closure-based content storage; @ViewBuilder let content: Content remains the preferred SwiftUI container baseline.',
893
+ code: 'HEURISTICS_IOS_SWIFTUI_CLOSURE_BASED_VIEWBUILDER_CONTENT_AST',
894
+ },
895
+ },
896
+ {
897
+ id: 'heuristics.ios.swiftui.redundant-reactive-state-assignment.ast',
898
+ description: 'Detects onChange/onReceive state assignments without a value-change guard.',
899
+ severity: 'WARN',
900
+ platform: 'ios',
901
+ locked: true,
902
+ when: {
903
+ kind: 'Heuristic',
904
+ where: {
905
+ ruleId: 'heuristics.ios.swiftui.redundant-reactive-state-assignment.ast',
906
+ },
907
+ },
908
+ then: {
909
+ kind: 'Finding',
910
+ message: 'AST heuristic detected reactive state assignment without a value-change guard; check for value changes before assigning state in hot paths.',
911
+ code: 'HEURISTICS_IOS_SWIFTUI_REDUNDANT_REACTIVE_STATE_ASSIGNMENT_AST',
912
+ },
913
+ },
878
914
  {
879
915
  id: 'heuristics.ios.navigation-view.ast',
880
916
  description: 'Detects NavigationView usage in iOS production code.',
@@ -246,6 +246,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
246
246
  heuristicDetector('ios.swiftui.explicit-color-static-member', [
247
247
  'heuristics.ios.swiftui.explicit-color-static-member.ast',
248
248
  ]),
249
+ 'skills.ios.guideline.ios-swiftui-expert.prefer-viewbuilder-let-content-content-over-closure-based-content-prop':
250
+ heuristicDetector('ios.swiftui.closure-based-viewbuilder-content', [
251
+ 'heuristics.ios.swiftui.closure-based-viewbuilder-content.ast',
252
+ ]),
253
+ 'skills.ios.guideline.ios-swiftui-expert.avoid-redundant-state-updates-in-onreceive-onchange-scroll-handlers':
254
+ heuristicDetector('ios.swiftui.redundant-reactive-state-assignment', [
255
+ 'heuristics.ios.swiftui.redundant-reactive-state-assignment.ast',
256
+ ]),
249
257
  'skills.ios.no-scrollview-shows-indicators': heuristicDetector(
250
258
  'ios.scrollview-shows-indicators',
251
259
  ['heuristics.ios.scrollview-shows-indicators.ast']
@@ -346,6 +346,20 @@ const normalizeKnownRuleTarget = (
346
346
  ) {
347
347
  return 'skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue';
348
348
  }
349
+ if (
350
+ includes('viewbuilder let content') ||
351
+ (includes('closure-based content') && includes('content')) ||
352
+ (includes('content: content') && includes('viewbuilder'))
353
+ ) {
354
+ return 'skills.ios.guideline.ios-swiftui-expert.prefer-viewbuilder-let-content-content-over-closure-based-content-prop';
355
+ }
356
+ if (
357
+ includes('redundant state updates') ||
358
+ (includes('onreceive') && includes('onchange') && includes('state updates')) ||
359
+ (includes('check for value changes') && includes('assigning state'))
360
+ ) {
361
+ return 'skills.ios.guideline.ios-swiftui-expert.avoid-redundant-state-updates-in-onreceive-onchange-scroll-handlers';
362
+ }
349
363
  if (
350
364
  includes('scrollindicators hidden') ||
351
365
  includes('scroll indicators hidden') ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.221",
3
+ "version": "6.3.223",
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-13T16:00:47.572Z",
4
+ "generatedAt": "2026-05-13T16:08:30.839Z",
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": "50cdc96e8e5b03ef6709edea0cde8959344ce5a3c57dfc93fe39d03d5695b7aa",
8635
+ "hash": "6570119e6399677bda8b010b801f6397859f7a066eeecf1a33fcfe9ca97348fe",
8636
8636
  "rules": [
8637
8637
  {
8638
8638
  "id": "skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear",
@@ -8667,7 +8667,7 @@
8667
8667
  "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8668
8668
  "confidence": "MEDIUM",
8669
8669
  "locked": true,
8670
- "evaluationMode": "DECLARATIVE",
8670
+ "evaluationMode": "AUTO",
8671
8671
  "origin": "core"
8672
8672
  },
8673
8673
  {
@@ -8727,7 +8727,7 @@
8727
8727
  "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8728
8728
  "confidence": "MEDIUM",
8729
8729
  "locked": true,
8730
- "evaluationMode": "DECLARATIVE",
8730
+ "evaluationMode": "AUTO",
8731
8731
  "origin": "core"
8732
8732
  },
8733
8733
  {