pumuki 6.3.227 → 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.
@@ -36,6 +36,7 @@ import {
36
36
  hasSwiftLegacyXCTestImportUsage,
37
37
  hasSwiftModernizableXCTestSuiteUsage,
38
38
  hasSwiftNonLazyScrollForEachUsage,
39
+ hasSwiftUiForEachConditionalViewCountUsage,
39
40
  hasSwiftViewBodyObjectCreationUsage,
40
41
  hasSwiftUiImageDataDecodingUsage,
41
42
  hasSwiftUiInlineActionLogicUsage,
@@ -47,6 +48,7 @@ import {
47
48
  hasSwiftNSManagedObjectBoundaryUsage,
48
49
  hasSwiftNSManagedObjectStateLeakUsage,
49
50
  hasSwiftNavigationViewUsage,
51
+ hasSwiftUntypedNavigationLinkDestinationUsage,
50
52
  hasSwiftNonPrivateStateOwnershipUsage,
51
53
  hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage,
52
54
  hasSwiftObservableObjectUsage,
@@ -165,6 +167,40 @@ struct FeedView: View {
165
167
  assert.equal(hasSwiftNonLazyScrollForEachUsage(safe), false);
166
168
  });
167
169
 
170
+ test('hasSwiftUiForEachConditionalViewCountUsage detecta branching condicional dentro de ForEach', () => {
171
+ const source = `
172
+ struct FeedView: View {
173
+ let items: [Item]
174
+
175
+ var body: some View {
176
+ ForEach(items) { item in
177
+ if item.isPromoted {
178
+ PromotedRow(item: item)
179
+ } else {
180
+ RegularRow(item: item)
181
+ }
182
+ }
183
+ }
184
+ }
185
+ `;
186
+ const safe = `
187
+ struct FeedView: View {
188
+ let items: [Item]
189
+
190
+ var body: some View {
191
+ ForEach(items) { item in
192
+ FeedRow(item: item)
193
+ }
194
+ let sample = "ForEach(items) { item in if item.isPromoted { PromotedRow(item: item) } }"
195
+ // ForEach(items) { item in if item.isPromoted { PromotedRow(item: item) } }
196
+ }
197
+ }
198
+ `;
199
+
200
+ assert.equal(hasSwiftUiForEachConditionalViewCountUsage(source), true);
201
+ assert.equal(hasSwiftUiForEachConditionalViewCountUsage(safe), false);
202
+ });
203
+
168
204
  test('hasSwiftViewBodyObjectCreationUsage detecta formatter creado en body y preserva dependencia externa', () => {
169
205
  const source = `
170
206
  struct PriceView: View {
@@ -256,6 +292,59 @@ struct CheckoutView: View {
256
292
  assert.equal(hasSwiftUiInlineActionLogicUsage(safe), false);
257
293
  });
258
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
+
259
348
  test('hasSwiftForceTryUsage detecta try! y descarta try?', () => {
260
349
  const positive = `
261
350
  func load() {
@@ -388,6 +388,14 @@ export const hasSwiftNonLazyScrollForEachUsage = (source: string): boolean => {
388
388
  return nonLazyScrollableCollectionPattern.test(swiftSource);
389
389
  };
390
390
 
391
+ export const hasSwiftUiForEachConditionalViewCountUsage = (source: string): boolean => {
392
+ const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
393
+ const conditionalForEachPattern =
394
+ /\bForEach\s*\([^)]*\)\s*\{[\s\S]{0,1600}\b(?:if|switch)\b[\s\S]{0,1600}\}/;
395
+
396
+ return conditionalForEachPattern.test(swiftSource);
397
+ };
398
+
391
399
  export const hasSwiftViewBodyObjectCreationUsage = (source: string): boolean => {
392
400
  const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
393
401
  const viewBodyObjectCreationPattern =
@@ -1009,6 +1017,15 @@ export const hasSwiftNavigationViewUsage = (source: string): boolean => {
1009
1017
  });
1010
1018
  };
1011
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
+
1012
1029
  export const hasSwiftForegroundColorUsage = (source: string): boolean => {
1013
1030
  return hasSwiftUiModernizationSnapshotMatch(source, 'foreground-color');
1014
1031
  };
@@ -679,6 +679,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
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
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.' },
682
+ { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiForEachConditionalViewCountUsage, ruleId: 'heuristics.ios.swiftui.foreach-conditional-view-count.ast', code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_CONDITIONAL_VIEW_COUNT_AST', message: 'AST heuristic detected conditional view count inside ForEach; keep a constant number of views per element by moving branching into row views or modifiers.' },
682
683
  { 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.' },
683
684
  { 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
685
  { 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.' },
@@ -690,6 +691,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
690
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.' },
691
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.' },
692
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.' },
693
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.' },
694
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.' },
695
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.' },
@@ -803,6 +803,25 @@ export const iosRules: RuleSet = [
803
803
  code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FOREACH_TRANSFORM_AST',
804
804
  },
805
805
  },
806
+ {
807
+ id: 'heuristics.ios.swiftui.foreach-conditional-view-count.ast',
808
+ description: 'Detects conditional view counts inside SwiftUI ForEach rows.',
809
+ severity: 'WARN',
810
+ platform: 'ios',
811
+ locked: true,
812
+ when: {
813
+ kind: 'Heuristic',
814
+ where: {
815
+ ruleId: 'heuristics.ios.swiftui.foreach-conditional-view-count.ast',
816
+ },
817
+ },
818
+ then: {
819
+ kind: 'Finding',
820
+ message:
821
+ 'AST heuristic detected conditional view count inside ForEach; keep a constant number of views per element by moving branching into row views or modifiers.',
822
+ code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_CONDITIONAL_VIEW_COUNT_AST',
823
+ },
824
+ },
806
825
  {
807
826
  id: 'heuristics.ios.contains-user-filter.ast',
808
827
  description: 'Detects contains() usage in user-facing filter flows where localizedStandardContains() may be preferred.',
@@ -1005,6 +1024,25 @@ export const iosRules: RuleSet = [
1005
1024
  code: 'HEURISTICS_IOS_NAVIGATION_VIEW_AST',
1006
1025
  },
1007
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
+ },
1008
1046
  {
1009
1047
  id: 'heuristics.ios.foreground-color.ast',
1010
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
  ]),
@@ -233,6 +237,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
233
237
  heuristicDetector('ios.swiftui.inline-foreach-transform', [
234
238
  'heuristics.ios.swiftui.inline-foreach-transform.ast',
235
239
  ]),
240
+ 'skills.ios.guideline.ios-swiftui-expert.ensure-constant-number-of-views-per-foreach-element':
241
+ heuristicDetector('ios.swiftui.foreach-conditional-view-count', [
242
+ 'heuristics.ios.swiftui.foreach-conditional-view-count.ast',
243
+ ]),
236
244
  'skills.ios.no-contains-user-filter': heuristicDetector('ios.contains-user-filter', [
237
245
  'heuristics.ios.contains-user-filter.ast',
238
246
  ]),
@@ -16,7 +16,7 @@ const MARKDOWN_BOLD_PATTERN = /[*_]{1,3}/g;
16
16
  const MULTISPACE_PATTERN = /\s+/g;
17
17
  const AST_NODE_ID_PATTERN = /\bheuristics\.[a-z0-9._-]+\.ast\b/gi;
18
18
  const RULE_KEYWORDS =
19
- /\b(always|siempre|prefer|use|usar|avoid|evitar|never|nunca|must|obligatorio|required|disallow|do not|no|suggest|should)\b/i;
19
+ /\b(always|siempre|prefer|use|usar|avoid|evitar|ensure|never|nunca|must|obligatorio|required|disallow|do not|no|suggest|should)\b/i;
20
20
 
21
21
  const normalizeForLookup = (value: string): string => {
22
22
  return value
@@ -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') ||
@@ -316,6 +323,12 @@ const normalizeKnownRuleTarget = (
316
323
  ) {
317
324
  return 'skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache';
318
325
  }
326
+ if (
327
+ includes('constant number of views per foreach element') ||
328
+ (includes('foreach') && includes('constant number of views'))
329
+ ) {
330
+ return 'skills.ios.guideline.ios-swiftui-expert.ensure-constant-number-of-views-per-foreach-element';
331
+ }
319
332
  if (
320
333
  includes('localizedstandardcontains') ||
321
334
  includes('localized standard contains') ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.227",
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:27:11.446Z",
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": "a69126588892428f136f55666d721346015d4684ded6dfa07509045db95f7f48",
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",
@@ -8682,6 +8670,30 @@
8682
8670
  "evaluationMode": "AUTO",
8683
8671
  "origin": "core"
8684
8672
  },
8673
+ {
8674
+ "id": "skills.ios.guideline.ios-swiftui-expert.ensure-constant-number-of-views-per-foreach-element",
8675
+ "description": "Ensure constant number of views per ForEach element",
8676
+ "severity": "WARN",
8677
+ "platform": "ios",
8678
+ "sourceSkill": "ios-swiftui-expert-guidelines",
8679
+ "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8680
+ "confidence": "MEDIUM",
8681
+ "locked": true,
8682
+ "evaluationMode": "AUTO",
8683
+ "origin": "core"
8684
+ },
8685
+ {
8686
+ "id": "skills.ios.guideline.ios-swiftui-expert.ensure-foreach-uses-stable-identity-see-references-list-patterns-md",
8687
+ "description": "Ensure ForEach uses stable identity (see references/list-patterns.md)",
8688
+ "severity": "WARN",
8689
+ "platform": "ios",
8690
+ "sourceSkill": "ios-swiftui-expert-guidelines",
8691
+ "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8692
+ "confidence": "MEDIUM",
8693
+ "locked": true,
8694
+ "evaluationMode": "DECLARATIVE",
8695
+ "origin": "core"
8696
+ },
8685
8697
  {
8686
8698
  "id": "skills.ios.guideline.ios-swiftui-expert.keep-view-body-simple-and-pure-no-side-effects-or-complex-logic",
8687
8699
  "description": "Keep view body simple and pure (no side effects or complex logic)",
@@ -8871,7 +8883,7 @@
8871
8883
  "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8872
8884
  "confidence": "MEDIUM",
8873
8885
  "locked": true,
8874
- "evaluationMode": "DECLARATIVE",
8886
+ "evaluationMode": "AUTO",
8875
8887
  "origin": "core"
8876
8888
  },
8877
8889
  {