pumuki 6.3.228 → 6.3.229

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.
@@ -48,6 +48,7 @@ import {
48
48
  hasSwiftNSManagedObjectBoundaryUsage,
49
49
  hasSwiftNSManagedObjectStateLeakUsage,
50
50
  hasSwiftNavigationViewUsage,
51
+ hasSwiftUntypedNavigationLinkDestinationUsage,
51
52
  hasSwiftNonPrivateStateOwnershipUsage,
52
53
  hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage,
53
54
  hasSwiftObservableObjectUsage,
@@ -291,6 +292,59 @@ struct CheckoutView: View {
291
292
  assert.equal(hasSwiftUiInlineActionLogicUsage(safe), false);
292
293
  });
293
294
 
295
+ test('hasSwiftUntypedNavigationLinkDestinationUsage detecta NavigationLink no tipado y preserva value navigation', () => {
296
+ const source = `
297
+ struct FeedView: View {
298
+ let items: [Item]
299
+
300
+ var body: some View {
301
+ NavigationStack {
302
+ List(items) { item in
303
+ NavigationLink(destination: DetailView(item: item)) {
304
+ Text(item.title)
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+ `;
311
+ const trailing = `
312
+ struct FeedView: View {
313
+ var body: some View {
314
+ NavigationLink {
315
+ DetailView()
316
+ } label: {
317
+ Text("Open")
318
+ }
319
+ }
320
+ }
321
+ `;
322
+ const safe = `
323
+ struct FeedView: View {
324
+ let items: [Item]
325
+
326
+ var body: some View {
327
+ NavigationStack {
328
+ List(items) { item in
329
+ NavigationLink(value: item) {
330
+ Text(item.title)
331
+ }
332
+ }
333
+ .navigationDestination(for: Item.self) { item in
334
+ DetailView(item: item)
335
+ }
336
+ }
337
+ let sample = "NavigationLink(destination: DetailView())"
338
+ // NavigationLink { DetailView() } label: { Text("Open") }
339
+ }
340
+ }
341
+ `;
342
+
343
+ assert.equal(hasSwiftUntypedNavigationLinkDestinationUsage(source), true);
344
+ assert.equal(hasSwiftUntypedNavigationLinkDestinationUsage(trailing), true);
345
+ assert.equal(hasSwiftUntypedNavigationLinkDestinationUsage(safe), false);
346
+ });
347
+
294
348
  test('hasSwiftForceTryUsage detecta try! y descarta try?', () => {
295
349
  const positive = `
296
350
  func load() {
@@ -1017,6 +1017,15 @@ export const hasSwiftNavigationViewUsage = (source: string): boolean => {
1017
1017
  });
1018
1018
  };
1019
1019
 
1020
+ export const hasSwiftUntypedNavigationLinkDestinationUsage = (source: string): boolean => {
1021
+ const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
1022
+ const destinationParameterPattern = /\bNavigationLink\s*\([^)]*\bdestination\s*:/;
1023
+ const trailingDestinationPattern =
1024
+ /\bNavigationLink\s*\{[\s\S]{0,900}\b[A-Z][A-Za-z0-9_]*View\s*\([^}]*\)[\s\S]{0,900}\}\s*label\s*:/;
1025
+
1026
+ return destinationParameterPattern.test(swiftSource) || trailingDestinationPattern.test(swiftSource);
1027
+ };
1028
+
1020
1029
  export const hasSwiftForegroundColorUsage = (source: string): boolean => {
1021
1030
  return hasSwiftUiModernizationSnapshotMatch(source, 'foreground-color');
1022
1031
  };
@@ -691,6 +691,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
691
691
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiImageDataDecodingUsage, ruleId: 'heuristics.ios.swiftui.image-data-decoding.ast', code: 'HEURISTICS_IOS_SWIFTUI_IMAGE_DATA_DECODING_AST', message: 'AST heuristic detected UIImage(data:) in SwiftUI presentation; downsample image data before rendering large images.' },
692
692
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiInlineActionLogicUsage, ruleId: 'heuristics.ios.swiftui.inline-action-logic.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_ACTION_LOGIC_AST', message: 'AST heuristic detected inline logic inside a SwiftUI action handler; action handlers should reference methods and keep view declarations focused.' },
693
693
  { 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.' },
694
+ { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUntypedNavigationLinkDestinationUsage, ruleId: 'heuristics.ios.swiftui.untyped-navigation-link-destination.ast', code: 'HEURISTICS_IOS_SWIFTUI_UNTYPED_NAVIGATION_LINK_DESTINATION_AST', message: 'AST heuristic detected untyped NavigationLink destination usage; prefer NavigationLink(value:) with navigationDestination(for:) for type-safe navigation.' },
694
695
  { 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.' },
695
696
  { 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.' },
696
697
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftTabItemUsage, ruleId: 'heuristics.ios.tab-item.ast', code: 'HEURISTICS_IOS_TAB_ITEM_AST', message: 'AST heuristic detected tabItem usage.' },
@@ -1024,6 +1024,25 @@ export const iosRules: RuleSet = [
1024
1024
  code: 'HEURISTICS_IOS_NAVIGATION_VIEW_AST',
1025
1025
  },
1026
1026
  },
1027
+ {
1028
+ id: 'heuristics.ios.swiftui.untyped-navigation-link-destination.ast',
1029
+ description: 'Detects untyped SwiftUI NavigationLink destination usage.',
1030
+ severity: 'WARN',
1031
+ platform: 'ios',
1032
+ locked: true,
1033
+ when: {
1034
+ kind: 'Heuristic',
1035
+ where: {
1036
+ ruleId: 'heuristics.ios.swiftui.untyped-navigation-link-destination.ast',
1037
+ },
1038
+ },
1039
+ then: {
1040
+ kind: 'Finding',
1041
+ message:
1042
+ 'AST heuristic detected untyped NavigationLink destination usage; prefer NavigationLink(value:) with navigationDestination(for:) for type-safe navigation.',
1043
+ code: 'HEURISTICS_IOS_SWIFTUI_UNTYPED_NAVIGATION_LINK_DESTINATION_AST',
1044
+ },
1045
+ },
1027
1046
  {
1028
1047
  id: 'heuristics.ios.foreground-color.ast',
1029
1048
  description: 'Detects foregroundColor usage in modern SwiftUI code paths.',
@@ -211,6 +211,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
211
211
  'skills.ios.no-navigation-view': heuristicDetector('ios.navigation-view', [
212
212
  'heuristics.ios.navigation-view.ast',
213
213
  ]),
214
+ 'skills.ios.guideline.ios-swiftui-expert.use-navigationdestination-for-for-type-safe-navigation':
215
+ heuristicDetector('ios.swiftui.untyped-navigation-link-destination', [
216
+ 'heuristics.ios.swiftui.untyped-navigation-link-destination.ast',
217
+ ]),
214
218
  'skills.ios.no-foreground-color': heuristicDetector('ios.foreground-color', [
215
219
  'heuristics.ios.foreground-color.ast',
216
220
  ]),
@@ -277,6 +277,13 @@ const normalizeKnownRuleTarget = (
277
277
  if (includes('navigationview') || includes('navigation view')) {
278
278
  return 'skills.ios.no-navigation-view';
279
279
  }
280
+ if (
281
+ includes('navigationdestination for') ||
282
+ (includes('navigationdestination') && includes('type safe navigation')) ||
283
+ (includes('navigationlink') && includes('value') && includes('navigationdestination'))
284
+ ) {
285
+ return 'skills.ios.guideline.ios-swiftui-expert.use-navigationdestination-for-for-type-safe-navigation';
286
+ }
280
287
  if (
281
288
  includes('foregroundstyle instead of foregroundcolor') ||
282
289
  includes('foregroundstyle over foregroundcolor') ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.228",
3
+ "version": "6.3.229",
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:36:55.011Z",
4
+ "generatedAt": "2026-05-13T16:44:08.385Z",
5
5
  "bundles": [
6
6
  {
7
7
  "name": "android-guidelines",
@@ -5764,8 +5764,20 @@
5764
5764
  "name": "ios-guidelines",
5765
5765
  "version": "1.0.0",
5766
5766
  "source": "file:vendor/skills/ios-enterprise-rules/SKILL.md",
5767
- "hash": "2d56094eff6a0b688d9f20ef6970de5945dfa74aaf3a3973dfee2091b9e98542",
5767
+ "hash": "a70065766533e822f2139da42d7933b6f240634c93939ee60969e3fd009409ce",
5768
5768
  "rules": [
5769
+ {
5770
+ "id": "skills.ios.guideline.ios-swiftui-expert.use-navigationdestination-for-for-type-safe-navigation",
5771
+ "description": "navigationDestination(for:) - Destinos tipados",
5772
+ "severity": "WARN",
5773
+ "platform": "ios",
5774
+ "sourceSkill": "ios-guidelines",
5775
+ "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
5776
+ "confidence": "MEDIUM",
5777
+ "locked": true,
5778
+ "evaluationMode": "AUTO",
5779
+ "origin": "core"
5780
+ },
5769
5781
  {
5770
5782
  "id": "skills.ios.guideline.ios-swiftui-expert.use-task-modifier-for-automatic-cancellation-of-async-work",
5771
5783
  "description": ".task/.task(id:) - Trabajos async con cancelación automática",
@@ -7051,30 +7063,6 @@
7051
7063
  "evaluationMode": "DECLARATIVE",
7052
7064
  "origin": "core"
7053
7065
  },
7054
- {
7055
- "id": "skills.ios.guideline.ios.navigationdestination-for-destinos-tipados",
7056
- "description": "navigationDestination(for:) - Destinos tipados",
7057
- "severity": "WARN",
7058
- "platform": "ios",
7059
- "sourceSkill": "ios-guidelines",
7060
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7061
- "confidence": "MEDIUM",
7062
- "locked": true,
7063
- "evaluationMode": "DECLARATIVE",
7064
- "origin": "core"
7065
- },
7066
- {
7067
- "id": "skills.ios.guideline.ios.navigationdestination-for-para-destinos",
7068
- "description": "NavigationDestination(for:) para destinos",
7069
- "severity": "WARN",
7070
- "platform": "ios",
7071
- "sourceSkill": "ios-guidelines",
7072
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7073
- "confidence": "MEDIUM",
7074
- "locked": true,
7075
- "evaluationMode": "DECLARATIVE",
7076
- "origin": "core"
7077
- },
7078
7066
  {
7079
7067
  "id": "skills.ios.guideline.ios.navigationstack-navigationpath-con-rutas-tipadas",
7080
7068
  "description": "NavigationStack + NavigationPath con rutas tipadas",
@@ -8632,7 +8620,7 @@
8632
8620
  "name": "ios-swiftui-expert-guidelines",
8633
8621
  "version": "1.0.0",
8634
8622
  "source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
8635
- "hash": "87916172b71811a09b147336b1625bce94571b1f9c66885bd136cfac27079b0d",
8623
+ "hash": "92aa9bdb43b5641206e53aaba4647a5c88b88a7aca83dc00eea373d5e70b7080",
8636
8624
  "rules": [
8637
8625
  {
8638
8626
  "id": "skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic",
@@ -8895,7 +8883,7 @@
8895
8883
  "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8896
8884
  "confidence": "MEDIUM",
8897
8885
  "locked": true,
8898
- "evaluationMode": "DECLARATIVE",
8886
+ "evaluationMode": "AUTO",
8899
8887
  "origin": "core"
8900
8888
  },
8901
8889
  {