pumuki 6.3.284 → 6.3.285

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/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.284
1
+ v6.3.285
@@ -49,6 +49,10 @@ import {
49
49
  collectSwiftInteractiveControlWithoutAccessibilityIdentifierLines,
50
50
  hasSwiftInteractiveControlWithoutAccessibilityIdentifierUsage,
51
51
  collectSwiftBindableMissingForObservableBindingUsageLines,
52
+ collectSwiftExplicitColorStaticMemberLines,
53
+ collectSwiftFixedFontSizeLines,
54
+ collectSwiftHardcodedSensitiveStringLines,
55
+ collectSwiftMagicNumberLayoutLines,
52
56
  hasSwiftBindableMissingForObservableBindingUsage,
53
57
  hasSwiftLooseAssetResourceUsage,
54
58
  hasSwiftLegacyOnChangeUsage,
@@ -237,12 +241,15 @@ send()
237
241
  assert.equal(hasSwiftProductionCommentUsage(safe), false);
238
242
  });
239
243
 
240
- test('hasSwiftCombineSinkWithoutStoreUsage detecta sink sin store y preserva cancellables', () => {
244
+ test('hasSwiftCombineSinkWithoutStoreUsage detecta sink/assign sin store y preserva cancellables', () => {
241
245
  const source = `
242
246
  publisher
243
247
  .sink { value in
244
248
  render(value)
245
249
  }
250
+
251
+ publisher
252
+ .assign(to: \\Model.title, on: model)
246
253
  `;
247
254
  const safe = `
248
255
  publisher
@@ -250,7 +257,11 @@ publisher
250
257
  render(value)
251
258
  }
252
259
  .store(in: &cancellables)
260
+ publisher
261
+ .assign(to: \\Model.title, on: model)
262
+ .store(in: &cancellables)
253
263
  let ignored = ".sink { value in }"
264
+ let ignoredAssign = ".assign(to: \\\\Model.title, on: model)"
254
265
  // publisher.sink { value in }
255
266
  `;
256
267
 
@@ -1011,7 +1022,9 @@ struct ProfileView: View {
1011
1022
  `;
1012
1023
 
1013
1024
  assert.equal(hasSwiftMagicNumberLayoutUsage(source), true);
1025
+ assert.deepEqual(collectSwiftMagicNumberLayoutLines(source), [4, 6, 7]);
1014
1026
  assert.equal(hasSwiftMagicNumberLayoutUsage(constants), false);
1027
+ assert.deepEqual(collectSwiftMagicNumberLayoutLines(constants), []);
1015
1028
  });
1016
1029
 
1017
1030
  test('detectores de logging iOS detectan logs ad-hoc y PII en produccion', () => {
@@ -1054,7 +1067,9 @@ final class Credentials {
1054
1067
  `;
1055
1068
 
1056
1069
  assert.equal(hasSwiftHardcodedSensitiveStringUsage(source), true);
1070
+ assert.deepEqual(collectSwiftHardcodedSensitiveStringLines(source), [3, 4]);
1057
1071
  assert.equal(hasSwiftHardcodedSensitiveStringUsage(safe), false);
1072
+ assert.deepEqual(collectSwiftHardcodedSensitiveStringLines(safe), []);
1058
1073
  });
1059
1074
 
1060
1075
  test('hasSwiftUnlocalizedDateFormatterUsage detecta dateFormat fijo sin locale explicito', () => {
@@ -1225,7 +1240,9 @@ let text = "UIFont.systemFont(ofSize: 16)"
1225
1240
  `;
1226
1241
 
1227
1242
  assert.equal(hasSwiftFixedFontSizeUsage(source), true);
1243
+ assert.deepEqual(collectSwiftFixedFontSizeLines(source), [2, 3, 4]);
1228
1244
  assert.equal(hasSwiftFixedFontSizeUsage(ignored), false);
1245
+ assert.deepEqual(collectSwiftFixedFontSizeLines(ignored), []);
1229
1246
  });
1230
1247
 
1231
1248
  test('detector iOS de localización detecta alineación física sin confundir leading/trailing', () => {
@@ -1630,7 +1647,9 @@ let ignored = "Color.green"
1630
1647
  `;
1631
1648
 
1632
1649
  assert.equal(hasSwiftExplicitColorStaticMemberUsage(source), true);
1650
+ assert.deepEqual(collectSwiftExplicitColorStaticMemberLines(source), [4, 5]);
1633
1651
  assert.equal(hasSwiftExplicitColorStaticMemberUsage(safe), false);
1652
+ assert.deepEqual(collectSwiftExplicitColorStaticMemberLines(safe), []);
1634
1653
  });
1635
1654
 
1636
1655
  test('hasSwiftClosureBasedViewBuilderContentUsage detecta content closure y preserva @ViewBuilder let content', () => {
@@ -114,7 +114,7 @@ export const hasSwiftCombineSinkWithoutStoreUsage = (source: string): boolean =>
114
114
 
115
115
  for (let index = 0; index < lines.length; index += 1) {
116
116
  const line = stripSwiftLineForSemanticScan(lines[index] ?? '');
117
- if (!/\.sink\s*(?:\(|\{)/.test(line)) {
117
+ if (!/\.(?:sink|assign)\s*(?:\(|\{)/.test(line)) {
118
118
  continue;
119
119
  }
120
120
 
@@ -831,6 +831,13 @@ export const hasSwiftMagicNumberLayoutUsage = (source: string): boolean => {
831
831
  return collectSwiftRegexLines(source, swiftUiLayoutNumberPattern).length > 0;
832
832
  };
833
833
 
834
+ export const collectSwiftMagicNumberLayoutLines = (source: string): readonly number[] => {
835
+ const swiftUiLayoutNumberPattern =
836
+ /(?:\b(?:VStack|HStack|ZStack|LazyVStack|LazyHStack)\s*\([^)]*\bspacing\s*:\s*|\.(?:padding|frame|offset|position|shadow|blur)\s*\([^)]*(?:\b(?:width|height|spacing|radius|x|y)\s*:\s*)?)\b(?:[3-9]|[1-9][0-9]+)(?:\.[0-9]+)?\b/;
837
+
838
+ return collectSwiftRegexLines(source, swiftUiLayoutNumberPattern);
839
+ };
840
+
834
841
  export const hasSwiftAdHocLoggingUsage = (source: string): boolean => {
835
842
  return collectSwiftRegexLines(
836
843
  source,
@@ -865,6 +872,13 @@ export const hasSwiftHardcodedSensitiveStringUsage = (source: string): boolean =
865
872
  ).length > 0;
866
873
  };
867
874
 
875
+ export const collectSwiftHardcodedSensitiveStringLines = (source: string): readonly number[] => {
876
+ return collectSwiftRegexLines(
877
+ source,
878
+ /\b(?:(?:private|fileprivate|internal|public|open|static|class|final|lazy)\s+)*(?:let|var)\s+(?=[A-Za-z_])[A-Za-z0-9_]*(?:token|secret|password|apikey|clientsecret|privatekey|sessionid)[A-Za-z0-9_]*\s*(?::\s*String\s*)?=\s*""/i
879
+ );
880
+ };
881
+
868
882
  export const hasSwiftUnlocalizedDateFormatterUsage = (source: string): boolean => {
869
883
  const sanitizedSource = sanitizeSwiftSourceForMultilineRegex(source);
870
884
  const formatterDeclarations = sanitizedSource.matchAll(
@@ -1009,6 +1023,27 @@ export const hasSwiftFixedFontSizeUsage = (source: string): boolean => {
1009
1023
  });
1010
1024
  };
1011
1025
 
1026
+ export const collectSwiftFixedFontSizeLines = (source: string): readonly number[] => {
1027
+ const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
1028
+ const matches: number[] = [];
1029
+ withoutBlockComments.split(/\r?\n/).forEach((line, index) => {
1030
+ if (/^\s*\/\//.test(line)) {
1031
+ return;
1032
+ }
1033
+ const sanitized = stripSwiftLineForSemanticScan(line);
1034
+ if (
1035
+ /\.\s*font\s*\(\s*\.\s*system\s*\(\s*size\s*:/.test(sanitized) ||
1036
+ /\bFont\s*\.\s*system\s*\(\s*size\s*:/.test(sanitized) ||
1037
+ /\bUIFont\s*\.\s*(?:systemFont|boldSystemFont|italicSystemFont)\s*\(\s*ofSize\s*:/.test(
1038
+ sanitized
1039
+ )
1040
+ ) {
1041
+ matches.push(index + 1);
1042
+ }
1043
+ });
1044
+ return matches;
1045
+ };
1046
+
1012
1047
  export const hasSwiftPhysicalTextAlignmentUsage = (source: string): boolean => {
1013
1048
  const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
1014
1049
  return withoutBlockComments.split(/\r?\n/).some((line) => {
@@ -1281,6 +1316,13 @@ export const hasSwiftExplicitColorStaticMemberUsage = (source: string): boolean
1281
1316
  );
1282
1317
  };
1283
1318
 
1319
+ export const collectSwiftExplicitColorStaticMemberLines = (source: string): readonly number[] => {
1320
+ return collectSwiftRegexLines(
1321
+ source,
1322
+ /\bColor\s*\.\s*(?:accentColor|black|blue|brown|clear|cyan|gray|green|indigo|mint|orange|pink|primary|purple|red|secondary|teal|white|yellow)\b/g
1323
+ );
1324
+ };
1325
+
1284
1326
  export const hasSwiftClosureBasedViewBuilderContentUsage = (source: string): boolean => {
1285
1327
  return hasSwiftSanitizedRegexMatch(
1286
1328
  source,
@@ -732,10 +732,10 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
732
732
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSwinjectUsage, ruleId: 'heuristics.ios.architecture.swinject.ast', code: 'HEURISTICS_IOS_ARCHITECTURE_SWINJECT_AST', message: 'AST heuristic detected Swinject usage; manual dependency injection or SwiftUI Environment remain the preferred native baseline.' },
733
733
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMassiveViewControllerResponsibilityUsage, ruleId: 'heuristics.ios.architecture.massive-view-controller.ast', code: 'HEURISTICS_IOS_ARCHITECTURE_MASSIVE_VIEW_CONTROLLER_AST', message: 'AST heuristic detected a UIViewController with direct infrastructure/data access; move data access behind application/domain boundaries.' },
734
734
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage, ruleId: 'heuristics.ios.safety.non-iboutlet-iuo.ast', code: 'HEURISTICS_IOS_SAFETY_NON_IBOUTLET_IUO_AST', message: 'AST heuristic detected an implicitly unwrapped optional outside IBOutlet wiring; explicit optionals or initialization guarantees remain the preferred baseline.' },
735
- { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMagicNumberLayoutUsage, ruleId: 'heuristics.ios.maintainability.magic-number-layout.ast', code: 'HEURISTICS_IOS_MAINTAINABILITY_MAGIC_NUMBER_LAYOUT_AST', message: 'AST heuristic detected SwiftUI layout magic numbers; named constants or design tokens remain the preferred baseline.' },
735
+ { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMagicNumberLayoutUsage, locateLines: TextIOS.collectSwiftMagicNumberLayoutLines, primaryNode: (lines) => ({ kind: 'call', name: 'SwiftUI layout numeric literal', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: named metric constant or design token', lines }], why: 'Inline numeric layout literals hide design intent and make visual remediation dependent on scanning the whole view body.', impact: 'Pixel-perfect slices can be blocked without a concrete node unless the finding points to the exact spacing, frame or padding call to fix.', expected_fix: 'Move repeated or meaningful layout numbers into named constants or design tokens, or use relative layout APIs when the number encodes screen geometry.', ruleId: 'heuristics.ios.maintainability.magic-number-layout.ast', code: 'HEURISTICS_IOS_MAINTAINABILITY_MAGIC_NUMBER_LAYOUT_AST', message: 'AST heuristic detected SwiftUI layout magic numbers; named constants or design tokens remain the preferred baseline.' },
736
736
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAdHocLoggingUsage, ruleId: 'heuristics.ios.logging.adhoc-print.ast', code: 'HEURISTICS_IOS_LOGGING_ADHOC_PRINT_AST', message: 'AST heuristic detected print/debugPrint/dump/NSLog/os_log usage in iOS production code.' },
737
737
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSensitiveLoggingUsage, ruleId: 'heuristics.ios.logging.sensitive-data.ast', code: 'HEURISTICS_IOS_LOGGING_SENSITIVE_DATA_AST', message: 'AST heuristic detected sensitive data in an iOS logging call.' },
738
- { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftHardcodedSensitiveStringUsage, ruleId: 'heuristics.ios.security.hardcoded-sensitive-string.ast', code: 'HEURISTICS_IOS_SECURITY_HARDCODED_SENSITIVE_STRING_AST', message: 'AST heuristic detected hardcoded sensitive Swift string; Keychain, secure config or environment-specific secrets remain the preferred baseline.' },
738
+ { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftHardcodedSensitiveStringUsage, locateLines: TextIOS.collectSwiftHardcodedSensitiveStringLines, primaryNode: (lines) => ({ kind: 'property', name: 'hardcoded sensitive Swift string', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: Keychain, secure config or environment-specific secret source', lines }], why: 'Sensitive strings assigned directly to token/password/secret properties create static production secrets and cannot be rotated safely.', impact: 'Credentials or identifiers can leak through source, binaries, logs or screenshots and block release until the concrete assignment is removed.', expected_fix: 'Read sensitive values from Keychain, secure configuration, injected environment or a repository-approved secret provider. Keep user-facing copy in localization assets, not sensitive variables.', ruleId: 'heuristics.ios.security.hardcoded-sensitive-string.ast', code: 'HEURISTICS_IOS_SECURITY_HARDCODED_SENSITIVE_STRING_AST', message: 'AST heuristic detected hardcoded sensitive Swift string; Keychain, secure config or environment-specific secrets remain the preferred baseline.' },
739
739
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUnlocalizedDateFormatterUsage, ruleId: 'heuristics.ios.localization.unlocalized-dateformatter.ast', code: 'HEURISTICS_IOS_LOCALIZATION_UNLOCALIZED_DATEFORMATTER_AST', message: 'AST heuristic detected DateFormatter dateFormat usage without an explicit locale.' },
740
740
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAlamofireUsage, ruleId: 'heuristics.ios.networking.alamofire.ast', code: 'HEURISTICS_IOS_NETWORKING_ALAMOFIRE_AST', message: 'AST heuristic detected Alamofire usage in iOS production code; URLSession remains the preferred baseline for new code.' },
741
741
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftJSONSerializationUsage, ruleId: 'heuristics.ios.json.jsonserialization.ast', code: 'HEURISTICS_IOS_JSON_JSONSERIALIZATION_AST', message: 'AST heuristic detected JSONSerialization usage in iOS production code; Codable remains the preferred baseline for new code.' },
@@ -746,7 +746,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
746
746
  { platform: 'ios', pathCheck: isIOSInterfaceBuilderPath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.interface-builder.storyboard-xib.ast', code: 'HEURISTICS_IOS_INTERFACE_BUILDER_STORYBOARD_XIB_AST', message: 'AST heuristic detected Storyboard/XIB usage; programmatic SwiftUI/UIKit UI remains the preferred baseline for versionable iOS code.' },
747
747
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftHardcodedUiStringUsage, ruleId: 'heuristics.ios.localization.hardcoded-ui-string.ast', code: 'HEURISTICS_IOS_LOCALIZATION_HARDCODED_UI_STRING_AST', message: 'AST heuristic detected hardcoded user-facing SwiftUI text; String(localized:) and String Catalogs remain the preferred baseline.' },
748
748
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftLooseAssetResourceUsage, ruleId: 'heuristics.ios.assets.loose-resource.ast', code: 'HEURISTICS_IOS_ASSETS_LOOSE_RESOURCE_AST', message: 'AST heuristic detected loose image resource loading in iOS production code; Asset Catalogs remain the preferred baseline.' },
749
- { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftFixedFontSizeUsage, ruleId: 'heuristics.ios.accessibility.fixed-font-size.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_FIXED_FONT_SIZE_AST', message: 'AST heuristic detected fixed font sizing in iOS production code; Dynamic Type semantic text styles remain the preferred baseline.' },
749
+ { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftFixedFontSizeUsage, locateLines: TextIOS.collectSwiftFixedFontSizeLines, primaryNode: (lines) => ({ kind: 'call', name: 'fixed font size API', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: Dynamic Type semantic text style or scaled metric', lines }], why: 'Fixed font sizes bypass Dynamic Type unless explicitly scaled through the text system.', impact: 'Accessibility regressions become hard to remediate if the gate reports only the file instead of the exact font call.', expected_fix: 'Use semantic SwiftUI text styles such as .headline/.body, Font.TextStyle, or UIFontMetrics/scaled metrics when a custom size is required.', ruleId: 'heuristics.ios.accessibility.fixed-font-size.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_FIXED_FONT_SIZE_AST', message: 'AST heuristic detected fixed font sizing in iOS production code; Dynamic Type semantic text styles remain the preferred baseline.' },
750
750
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPhysicalTextAlignmentUsage, ruleId: 'heuristics.ios.localization.physical-text-alignment.ast', code: 'HEURISTICS_IOS_LOCALIZATION_PHYSICAL_TEXT_ALIGNMENT_AST', message: 'AST heuristic detected physical left/right text alignment in iOS production code; leading/trailing remain the preferred RTL-safe baseline.' },
751
751
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMainThreadBlockingSleepUsage, ruleId: 'heuristics.ios.performance.blocking-sleep.ast', code: 'HEURISTICS_IOS_PERFORMANCE_BLOCKING_SLEEP_AST', message: 'AST heuristic detected blocking sleep usage in iOS production code; async clocks, suspension or cancellable scheduling remain the preferred baseline.' },
752
752
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage, ruleId: 'heuristics.ios.accessibility.icon-only-control-label.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_ICON_ONLY_CONTROL_LABEL_AST', message: 'AST heuristic detected an icon-only SwiftUI control without accessibilityLabel; explicit accessible labels remain the preferred baseline.' },
@@ -772,7 +772,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
772
772
  { 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.' },
773
773
  { 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.' },
774
774
  { 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.' },
775
- { 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.' },
775
+ { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftExplicitColorStaticMemberUsage, locateLines: TextIOS.collectSwiftExplicitColorStaticMemberLines, primaryNode: (lines) => ({ kind: 'member', name: 'explicit Color.* static member', lines }), relatedNodes: (lines) => [{ kind: 'member', name: 'replacement: SwiftUI static member lookup .colorName', lines }], why: 'Explicit Color.* in SwiftUI view modifiers is noisier than static member lookup and can hide style token drift in presentation code.', impact: 'The fix is local and mechanical, but without line evidence the user sees a whole-file block instead of the exact member access.', expected_fix: 'Replace Color.blue/Color.primary/etc. with .blue/.primary in SwiftUI style contexts, or use named asset colors such as Color("BrandPrimary") when design tokens are required.', 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.' },
776
776
  { 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.' },
777
777
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftLargeConfigContextViewPropertyUsage, ruleId: 'heuristics.ios.swiftui.large-config-context-prop.ast', code: 'HEURISTICS_IOS_SWIFTUI_LARGE_CONFIG_CONTEXT_PROP_AST', message: 'AST heuristic detected a SwiftUI View storing a broad config/context object; pass only needed values to reduce update fan-out.' },
778
778
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiConditionalSameViewIdentityUsage, ruleId: 'heuristics.ios.swiftui.conditional-same-view-identity.ast', code: 'HEURISTICS_IOS_SWIFTUI_CONDITIONAL_SAME_VIEW_IDENTITY_AST', message: 'AST heuristic detected conditional branches rebuilding the same SwiftUI View type; prefer conditional modifiers or values to preserve view identity.' },
@@ -111,7 +111,7 @@ export const iosRules: RuleSet = [
111
111
  },
112
112
  {
113
113
  id: 'heuristics.ios.combine.sink-without-store.ast',
114
- description: 'Detects Combine sink subscriptions that are not retained with store(in:).',
114
+ description: 'Detects Combine sink/assign subscriptions that are not retained with store(in:).',
115
115
  severity: 'WARN',
116
116
  platform: 'ios',
117
117
  locked: true,
@@ -124,7 +124,7 @@ export const iosRules: RuleSet = [
124
124
  then: {
125
125
  kind: 'Finding',
126
126
  message:
127
- 'AST heuristic detected Combine sink without store(in:); keep cancellables retained explicitly.',
127
+ 'AST heuristic detected Combine sink/assign without store(in:); keep cancellables retained explicitly.',
128
128
  code: 'HEURISTICS_IOS_COMBINE_SINK_WITHOUT_STORE_AST',
129
129
  },
130
130
  },
@@ -53,6 +53,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
53
53
  'ios.combine.sink-without-store',
54
54
  ['heuristics.ios.combine.sink-without-store.ast']
55
55
  ),
56
+ 'skills.ios.guideline.ios.subscribers-sink-assign': heuristicDetector(
57
+ 'ios.combine.subscription-without-store',
58
+ ['heuristics.ios.combine.sink-without-store.ast']
59
+ ),
56
60
  'skills.ios.no-dispatchqueue': heuristicDetector('ios.dispatchqueue', [
57
61
  'heuristics.ios.dispatchqueue.ast',
58
62
  ]),
@@ -80,6 +84,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
80
84
  'skills.ios.no-async-without-await': heuristicDetector('ios.concurrency.async-without-await', [
81
85
  'heuristics.ios.concurrency.async-without-await.ast',
82
86
  ]),
87
+ 'skills.ios.guideline.ios.avoid-over-use-async-await-ma-s-simple-para-single-values':
88
+ heuristicDetector('ios.concurrency.avoid-overuse-async-without-await', [
89
+ 'heuristics.ios.concurrency.async-without-await.ast',
90
+ ]),
83
91
  'skills.ios.guideline.ios.delegation-pattern-weak-delegates-para-evitar-retain-cycles': heuristicDetector(
84
92
  'ios.memory.strong-delegate',
85
93
  ['heuristics.ios.memory.strong-delegate.ast']
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.284",
3
+ "version": "6.3.285",
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": {