pumuki 6.3.153 → 6.3.155
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/core/facts/detectors/text/ios.test.ts +14 -4
- package/core/facts/detectors/text/ios.ts +26 -3
- package/core/facts/extractHeuristicFacts.ts +2 -0
- package/core/rules/presets/heuristics/android.test.ts +32 -2
- package/core/rules/presets/heuristics/android.ts +110 -0
- package/core/rules/presets/heuristics/ios.test.ts +11 -1
- package/core/rules/presets/heuristics/ios.ts +36 -0
- package/integrations/config/skillsCompilerTemplates.ts +18 -0
- package/integrations/config/skillsDetectorRegistry.ts +8 -0
- package/integrations/git/gitAtomicity.ts +61 -0
- package/integrations/git/runPlatformGate.ts +44 -1
- package/package.json +1 -1
- package/skills.lock.json +42 -27
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
hasSwiftDispatchGroupUsage,
|
|
14
14
|
hasSwiftDispatchQueueUsage,
|
|
15
15
|
hasSwiftDispatchSemaphoreUsage,
|
|
16
|
+
hasSwiftExplicitColorStaticMemberUsage,
|
|
16
17
|
hasSwiftForEachIndicesUsage,
|
|
17
18
|
hasSwiftForceCastUsage,
|
|
18
19
|
hasSwiftFontWeightBoldUsage,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
hasSwiftForceTryUsage,
|
|
21
22
|
hasSwiftForceUnwrap,
|
|
22
23
|
hasSwiftGeometryReaderUsage,
|
|
24
|
+
hasSwiftInlineFilteringInForEachUsage,
|
|
23
25
|
hasSwiftLegacyOnChangeUsage,
|
|
24
26
|
hasSwiftLegacyExpectationDescriptionUsage,
|
|
25
27
|
hasSwiftLegacySwiftUiObservableWrapperUsage,
|
|
@@ -243,7 +245,9 @@ MainActor.assumeIsolated { reload() }
|
|
|
243
245
|
assert.equal(hasSwiftNonisolatedUnsafeUsage(source), true);
|
|
244
246
|
assert.equal(hasSwiftAssumeIsolatedUsage(source), true);
|
|
245
247
|
assert.equal(hasSwiftForEachIndicesUsage(source), true);
|
|
248
|
+
assert.equal(hasSwiftInlineFilteringInForEachUsage('ForEach(items.filter { $0.isActive }) { item in Text(item.title) }'), true);
|
|
246
249
|
assert.equal(hasSwiftContainsUserFilterUsage(source), true);
|
|
250
|
+
assert.equal(hasSwiftExplicitColorStaticMemberUsage('Text("A").foregroundStyle(Color.blue)'), true);
|
|
247
251
|
assert.equal(hasSwiftGeometryReaderUsage(source), true);
|
|
248
252
|
assert.equal(hasSwiftFontWeightBoldUsage(source), true);
|
|
249
253
|
assert.equal(hasSwiftObservableObjectUsage(source), true);
|
|
@@ -289,7 +293,9 @@ let t = "MainActor.assumeIsolated { reload() }"
|
|
|
289
293
|
assert.equal(hasSwiftNonisolatedUnsafeUsage(source), false);
|
|
290
294
|
assert.equal(hasSwiftAssumeIsolatedUsage(source), false);
|
|
291
295
|
assert.equal(hasSwiftForEachIndicesUsage(source), false);
|
|
296
|
+
assert.equal(hasSwiftInlineFilteringInForEachUsage(source), false);
|
|
292
297
|
assert.equal(hasSwiftContainsUserFilterUsage(source), false);
|
|
298
|
+
assert.equal(hasSwiftExplicitColorStaticMemberUsage('Text("A").foregroundStyle(.blue)'), false);
|
|
293
299
|
assert.equal(hasSwiftGeometryReaderUsage(source), false);
|
|
294
300
|
assert.equal(hasSwiftFontWeightBoldUsage(source), false);
|
|
295
301
|
assert.equal(hasSwiftTaskDetachedUsage(source), false);
|
|
@@ -400,7 +406,7 @@ final class LoginModelTests: XCTestCase {
|
|
|
400
406
|
}
|
|
401
407
|
`;
|
|
402
408
|
|
|
403
|
-
assert.equal(hasSwiftLegacyXCTestImportUsage(unitTest),
|
|
409
|
+
assert.equal(hasSwiftLegacyXCTestImportUsage(unitTest), false);
|
|
404
410
|
assert.equal(hasSwiftLegacyXCTestImportUsage(uiTest), false);
|
|
405
411
|
assert.equal(hasSwiftLegacyXCTestImportUsage(performanceTest), false);
|
|
406
412
|
assert.equal(hasSwiftLegacyXCTestImportUsage(brownfieldCompatibleUnitTest), false);
|
|
@@ -524,7 +530,7 @@ final class LoginModelTests: XCTestCase {
|
|
|
524
530
|
}
|
|
525
531
|
`;
|
|
526
532
|
|
|
527
|
-
assert.equal(hasSwiftModernizableXCTestSuiteUsage(legacySuite),
|
|
533
|
+
assert.equal(hasSwiftModernizableXCTestSuiteUsage(legacySuite), false);
|
|
528
534
|
assert.equal(hasSwiftModernizableXCTestSuiteUsage(mixedSuite), false);
|
|
529
535
|
assert.equal(hasSwiftModernizableXCTestSuiteUsage(uiSuite), false);
|
|
530
536
|
assert.equal(hasSwiftModernizableXCTestSuiteUsage(brownfieldCompatibleSuite), false);
|
|
@@ -596,7 +602,7 @@ final class BuyerCommerceUISmokeTests: XCTestCase {
|
|
|
596
602
|
assert.equal(hasSwiftXCTUnwrapUsage(`${uiSource}\nlet value = try XCTUnwrap(optional)`), false);
|
|
597
603
|
});
|
|
598
604
|
|
|
599
|
-
test('
|
|
605
|
+
test('detectores Swift Testing excluyen XCTest brownfield y dejan la calidad a su guard dedicado', () => {
|
|
600
606
|
const compatibleSource = `
|
|
601
607
|
import XCTest
|
|
602
608
|
|
|
@@ -633,7 +639,11 @@ final class LoginModelTests: XCTestCase {
|
|
|
633
639
|
|
|
634
640
|
assert.equal(hasSwiftXCTestAssertionUsage(compatibleSource), false);
|
|
635
641
|
assert.equal(hasSwiftXCTUnwrapUsage(compatibleSource), false);
|
|
636
|
-
assert.equal(
|
|
642
|
+
assert.equal(hasSwiftLegacyXCTestImportUsage(compatibleSource), false);
|
|
643
|
+
assert.equal(hasSwiftModernizableXCTestSuiteUsage(compatibleSource), false);
|
|
644
|
+
assert.equal(hasSwiftXCTestAssertionUsage(missingQualityContract), false);
|
|
645
|
+
assert.equal(hasSwiftLegacyXCTestImportUsage(missingQualityContract), false);
|
|
646
|
+
assert.equal(hasSwiftModernizableXCTestSuiteUsage(missingQualityContract), false);
|
|
637
647
|
});
|
|
638
648
|
|
|
639
649
|
test('hasSwiftXCTUnwrapUsage detecta XCTUnwrap real y evita strings', () => {
|
|
@@ -479,6 +479,17 @@ export const hasSwiftForEachIndicesUsage = (source: string): boolean => {
|
|
|
479
479
|
);
|
|
480
480
|
};
|
|
481
481
|
|
|
482
|
+
export const hasSwiftInlineFilteringInForEachUsage = (source: string): boolean => {
|
|
483
|
+
return hasSwiftSanitizedRegexMatch(
|
|
484
|
+
source,
|
|
485
|
+
/\bForEach\s*\(\s*[A-Za-z_][A-Za-z0-9_.]*\s*\.\s*filter\s*\{/g
|
|
486
|
+
);
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
export const hasSwiftExplicitColorStaticMemberUsage = (source: string): boolean => {
|
|
490
|
+
return hasSwiftSanitizedRegexMatch(source, /\bColor\s*\.\s*[a-z][A-Za-z0-9_]*\b/g);
|
|
491
|
+
};
|
|
492
|
+
|
|
482
493
|
const isUserSearchIdentifier = (value: string): boolean => {
|
|
483
494
|
return /^(?:query|search(?:Text|Term|Query|Value)?|filter(?:Text|Value)?|text|term|input)$/i.test(
|
|
484
495
|
value
|
|
@@ -756,6 +767,18 @@ const hasSwiftBrownfieldCompatibleXCTestUsage = (source: string): boolean => {
|
|
|
756
767
|
return hasSwiftMakeSutUsage(source) && hasSwiftMemoryLeakTrackingUsage(source);
|
|
757
768
|
};
|
|
758
769
|
|
|
770
|
+
const hasSwiftXCTestOnlyBrownfieldSuiteUsage = (source: string): boolean => {
|
|
771
|
+
if (!hasSwiftXCTestImportUsage(source) || !hasSwiftXCTestCaseSubclassUsage(source)) {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (hasSwiftLegacyXCTestUiOrPerformanceUsage(source)) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return !hasSwiftTestingImportUsage(source) && !hasSwiftTestingSuiteAttributeUsage(source);
|
|
780
|
+
};
|
|
781
|
+
|
|
759
782
|
export const hasSwiftLegacyXCTestImportUsage = (source: string): boolean => {
|
|
760
783
|
if (!hasSwiftXCTestImportUsage(source)) {
|
|
761
784
|
return false;
|
|
@@ -765,7 +788,7 @@ export const hasSwiftLegacyXCTestImportUsage = (source: string): boolean => {
|
|
|
765
788
|
return false;
|
|
766
789
|
}
|
|
767
790
|
|
|
768
|
-
if (
|
|
791
|
+
if (hasSwiftXCTestOnlyBrownfieldSuiteUsage(source)) {
|
|
769
792
|
return false;
|
|
770
793
|
}
|
|
771
794
|
|
|
@@ -801,7 +824,7 @@ export const hasSwiftXCTestAssertionUsage = (source: string): boolean => {
|
|
|
801
824
|
return false;
|
|
802
825
|
}
|
|
803
826
|
|
|
804
|
-
if (
|
|
827
|
+
if (hasSwiftXCTestOnlyBrownfieldSuiteUsage(source)) {
|
|
805
828
|
return false;
|
|
806
829
|
}
|
|
807
830
|
|
|
@@ -816,7 +839,7 @@ export const hasSwiftXCTUnwrapUsage = (source: string): boolean => {
|
|
|
816
839
|
return false;
|
|
817
840
|
}
|
|
818
841
|
|
|
819
|
-
if (
|
|
842
|
+
if (hasSwiftXCTestOnlyBrownfieldSuiteUsage(source)) {
|
|
820
843
|
return false;
|
|
821
844
|
}
|
|
822
845
|
|
|
@@ -713,6 +713,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
713
713
|
{ 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.' },
|
|
714
714
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStateWrapperWithoutPrivateUsage, ruleId: 'heuristics.ios.swiftui.state-wrapper-private.ast', code: 'HEURISTICS_IOS_SWIFTUI_STATE_WRAPPER_PRIVATE_AST', message: 'AST heuristic detected @State/@StateObject usage in a SwiftUI View without private visibility.' },
|
|
715
715
|
{ 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.' },
|
|
716
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInlineFilteringInForEachUsage, ruleId: 'heuristics.ios.swiftui.inline-filtering-in-foreach.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FILTERING_IN_FOREACH_AST', message: 'AST heuristic detected inline filtering inside ForEach; prefilter and cache before rendering.' },
|
|
717
|
+
{ 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 explicit Color static member lookup where contextual .color style is preferred.' },
|
|
716
718
|
{ 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.' },
|
|
717
719
|
{ 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.' },
|
|
718
720
|
{ 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.' },
|
|
@@ -3,7 +3,7 @@ import test from 'node:test';
|
|
|
3
3
|
import { androidRules } from './android';
|
|
4
4
|
|
|
5
5
|
test('androidRules define reglas heurísticas locked para plataforma android', () => {
|
|
6
|
-
assert.equal(androidRules.length,
|
|
6
|
+
assert.equal(androidRules.length, 82);
|
|
7
7
|
|
|
8
8
|
const ids = androidRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -39,9 +39,15 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
39
39
|
'heuristics.android.single-source-of-truth-viewmodel-es-la-fuente.ast',
|
|
40
40
|
'heuristics.android.skip-recomposition-para-metros-inmutables-o-estables.ast',
|
|
41
41
|
'heuristics.android.stability-composables-estables-recomponen-menos.ast',
|
|
42
|
-
|
|
42
|
+
'heuristics.android.string-formatting-1-s-2-d-para-argumentos.ast',
|
|
43
43
|
'heuristics.android.binds-para-implementaciones-de-interfaces-ma-s-eficiente.ast',
|
|
44
44
|
'heuristics.android.provides-para-interfaces-o-third-party.ast',
|
|
45
|
+
'heuristics.android.hilt-di-framework-no-manual-factories.ast',
|
|
46
|
+
'heuristics.android.hiltandroidapp-application-class.ast',
|
|
47
|
+
'heuristics.android.androidentrypoint-activity-fragment-viewmodel.ast',
|
|
48
|
+
'heuristics.android.inject-constructor-constructor-injection.ast',
|
|
49
|
+
'heuristics.android.module-installin-provide-dependencies.ast',
|
|
50
|
+
'heuristics.android.viewmodelscoped-para-dependencias-de-viewmodel.ast',
|
|
45
51
|
'heuristics.android.workmanager-androidx-work-work-runtime-ktx.ast',
|
|
46
52
|
'heuristics.android.version-catalogs-libs-versions-toml-para-dependencias.ast',
|
|
47
53
|
'heuristics.android.workmanager-background-tasks.ast',
|
|
@@ -179,6 +185,30 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
179
185
|
byId.get('heuristics.android.provides-para-interfaces-o-third-party.ast')?.then.code,
|
|
180
186
|
'HEURISTICS_ANDROID_PROVIDES_PARA_INTERFACES_O_THIRD_PARTY_AST'
|
|
181
187
|
);
|
|
188
|
+
assert.equal(
|
|
189
|
+
byId.get('heuristics.android.hilt-di-framework-no-manual-factories.ast')?.then.code,
|
|
190
|
+
'HEURISTICS_ANDROID_HILT_DI_FRAMEWORK_NO_MANUAL_FACTORIES_AST'
|
|
191
|
+
);
|
|
192
|
+
assert.equal(
|
|
193
|
+
byId.get('heuristics.android.hiltandroidapp-application-class.ast')?.then.code,
|
|
194
|
+
'HEURISTICS_ANDROID_HILTANDROIDAPP_APPLICATION_CLASS_AST'
|
|
195
|
+
);
|
|
196
|
+
assert.equal(
|
|
197
|
+
byId.get('heuristics.android.androidentrypoint-activity-fragment-viewmodel.ast')?.then.code,
|
|
198
|
+
'HEURISTICS_ANDROID_ANDROIDENTRYPOINT_ACTIVITY_FRAGMENT_VIEWMODEL_AST'
|
|
199
|
+
);
|
|
200
|
+
assert.equal(
|
|
201
|
+
byId.get('heuristics.android.inject-constructor-constructor-injection.ast')?.then.code,
|
|
202
|
+
'HEURISTICS_ANDROID_INJECT_CONSTRUCTOR_CONSTRUCTOR_INJECTION_AST'
|
|
203
|
+
);
|
|
204
|
+
assert.equal(
|
|
205
|
+
byId.get('heuristics.android.module-installin-provide-dependencies.ast')?.then.code,
|
|
206
|
+
'HEURISTICS_ANDROID_MODULE_INSTALLIN_PROVIDE_DEPENDENCIES_AST'
|
|
207
|
+
);
|
|
208
|
+
assert.equal(
|
|
209
|
+
byId.get('heuristics.android.viewmodelscoped-para-dependencias-de-viewmodel.ast')?.then.code,
|
|
210
|
+
'HEURISTICS_ANDROID_VIEWMODELSCOPED_PARA_DEPENDENCIAS_DE_VIEWMODEL_AST'
|
|
211
|
+
);
|
|
182
212
|
assert.equal(
|
|
183
213
|
byId.get('heuristics.android.workmanager-androidx-work-work-runtime-ktx.ast')?.then.code,
|
|
184
214
|
'HEURISTICS_ANDROID_WORKMANAGER_ANDROIDX_WORK_WORK_RUNTIME_KTX_AST'
|
|
@@ -663,6 +663,116 @@ export const androidRules: RuleSet = [
|
|
|
663
663
|
code: 'HEURISTICS_ANDROID_PROVIDES_PARA_INTERFACES_O_THIRD_PARTY_AST',
|
|
664
664
|
},
|
|
665
665
|
},
|
|
666
|
+
{
|
|
667
|
+
id: 'heuristics.android.hilt-di-framework-no-manual-factories.ast',
|
|
668
|
+
description: 'Detects Hilt DI framework usage in Android production Kotlin files.',
|
|
669
|
+
severity: 'WARN',
|
|
670
|
+
platform: 'android',
|
|
671
|
+
locked: true,
|
|
672
|
+
when: {
|
|
673
|
+
kind: 'Heuristic',
|
|
674
|
+
where: {
|
|
675
|
+
ruleId: 'heuristics.android.hilt-di-framework-no-manual-factories.ast',
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
then: {
|
|
679
|
+
kind: 'Finding',
|
|
680
|
+
message:
|
|
681
|
+
'AST heuristic detected Hilt DI framework usage instead of manual factories in Android production code.',
|
|
682
|
+
code: 'HEURISTICS_ANDROID_HILT_DI_FRAMEWORK_NO_MANUAL_FACTORIES_AST',
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
id: 'heuristics.android.hiltandroidapp-application-class.ast',
|
|
687
|
+
description: 'Detects @HiltAndroidApp usage in Android production Kotlin files.',
|
|
688
|
+
severity: 'WARN',
|
|
689
|
+
platform: 'android',
|
|
690
|
+
locked: true,
|
|
691
|
+
when: {
|
|
692
|
+
kind: 'Heuristic',
|
|
693
|
+
where: {
|
|
694
|
+
ruleId: 'heuristics.android.hiltandroidapp-application-class.ast',
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
then: {
|
|
698
|
+
kind: 'Finding',
|
|
699
|
+
message: 'AST heuristic detected @HiltAndroidApp usage in Android production code.',
|
|
700
|
+
code: 'HEURISTICS_ANDROID_HILTANDROIDAPP_APPLICATION_CLASS_AST',
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
id: 'heuristics.android.androidentrypoint-activity-fragment-viewmodel.ast',
|
|
705
|
+
description: 'Detects @AndroidEntryPoint usage in Android production Kotlin files.',
|
|
706
|
+
severity: 'WARN',
|
|
707
|
+
platform: 'android',
|
|
708
|
+
locked: true,
|
|
709
|
+
when: {
|
|
710
|
+
kind: 'Heuristic',
|
|
711
|
+
where: {
|
|
712
|
+
ruleId: 'heuristics.android.androidentrypoint-activity-fragment-viewmodel.ast',
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
then: {
|
|
716
|
+
kind: 'Finding',
|
|
717
|
+
message:
|
|
718
|
+
'AST heuristic detected @AndroidEntryPoint usage in Android production code where Activity, Fragment, and ViewModel injection boundaries should remain explicit.',
|
|
719
|
+
code: 'HEURISTICS_ANDROID_ANDROIDENTRYPOINT_ACTIVITY_FRAGMENT_VIEWMODEL_AST',
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
id: 'heuristics.android.inject-constructor-constructor-injection.ast',
|
|
724
|
+
description: 'Detects @Inject constructor usage in Android production Kotlin files.',
|
|
725
|
+
severity: 'WARN',
|
|
726
|
+
platform: 'android',
|
|
727
|
+
locked: true,
|
|
728
|
+
when: {
|
|
729
|
+
kind: 'Heuristic',
|
|
730
|
+
where: {
|
|
731
|
+
ruleId: 'heuristics.android.inject-constructor-constructor-injection.ast',
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
then: {
|
|
735
|
+
kind: 'Finding',
|
|
736
|
+
message: 'AST heuristic detected @Inject constructor usage in Android production code.',
|
|
737
|
+
code: 'HEURISTICS_ANDROID_INJECT_CONSTRUCTOR_CONSTRUCTOR_INJECTION_AST',
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
id: 'heuristics.android.module-installin-provide-dependencies.ast',
|
|
742
|
+
description: 'Detects @Module + @InstallIn usage in Android production Kotlin files.',
|
|
743
|
+
severity: 'WARN',
|
|
744
|
+
platform: 'android',
|
|
745
|
+
locked: true,
|
|
746
|
+
when: {
|
|
747
|
+
kind: 'Heuristic',
|
|
748
|
+
where: {
|
|
749
|
+
ruleId: 'heuristics.android.module-installin-provide-dependencies.ast',
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
then: {
|
|
753
|
+
kind: 'Finding',
|
|
754
|
+
message: 'AST heuristic detected @Module + @InstallIn usage in Android production code.',
|
|
755
|
+
code: 'HEURISTICS_ANDROID_MODULE_INSTALLIN_PROVIDE_DEPENDENCIES_AST',
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
id: 'heuristics.android.viewmodelscoped-para-dependencias-de-viewmodel.ast',
|
|
760
|
+
description: 'Detects @ViewModelScoped usage in Android production Kotlin files.',
|
|
761
|
+
severity: 'WARN',
|
|
762
|
+
platform: 'android',
|
|
763
|
+
locked: true,
|
|
764
|
+
when: {
|
|
765
|
+
kind: 'Heuristic',
|
|
766
|
+
where: {
|
|
767
|
+
ruleId: 'heuristics.android.viewmodelscoped-para-dependencias-de-viewmodel.ast',
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
then: {
|
|
771
|
+
kind: 'Finding',
|
|
772
|
+
message: 'AST heuristic detected @ViewModelScoped usage in Android production code.',
|
|
773
|
+
code: 'HEURISTICS_ANDROID_VIEWMODELSCOPED_PARA_DEPENDENCIAS_DE_VIEWMODEL_AST',
|
|
774
|
+
},
|
|
775
|
+
},
|
|
666
776
|
{
|
|
667
777
|
id: 'heuristics.android.workmanager-androidx-work-work-runtime-ktx.ast',
|
|
668
778
|
description: 'Detects WorkManager dependency usage in Android build files.',
|
|
@@ -3,7 +3,7 @@ import test from 'node:test';
|
|
|
3
3
|
import { iosRules } from './ios';
|
|
4
4
|
|
|
5
5
|
test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
6
|
-
assert.equal(iosRules.length,
|
|
6
|
+
assert.equal(iosRules.length, 44);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -25,6 +25,8 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
25
25
|
'heuristics.ios.legacy-swiftui-observable-wrapper.ast',
|
|
26
26
|
'heuristics.ios.passed-value-state-wrapper.ast',
|
|
27
27
|
'heuristics.ios.foreach-indices.ast',
|
|
28
|
+
'heuristics.ios.swiftui.inline-filtering-in-foreach.ast',
|
|
29
|
+
'heuristics.ios.swiftui.explicit-color-static-member.ast',
|
|
28
30
|
'heuristics.ios.contains-user-filter.ast',
|
|
29
31
|
'heuristics.ios.geometryreader.ast',
|
|
30
32
|
'heuristics.ios.font-weight-bold.ast',
|
|
@@ -84,6 +86,14 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
84
86
|
byId.get('heuristics.ios.foreach-indices.ast')?.then.code,
|
|
85
87
|
'HEURISTICS_IOS_FOREACH_INDICES_AST'
|
|
86
88
|
);
|
|
89
|
+
assert.equal(
|
|
90
|
+
byId.get('heuristics.ios.swiftui.inline-filtering-in-foreach.ast')?.then.code,
|
|
91
|
+
'HEURISTICS_IOS_SWIFTUI_INLINE_FILTERING_IN_FOREACH_AST'
|
|
92
|
+
);
|
|
93
|
+
assert.equal(
|
|
94
|
+
byId.get('heuristics.ios.swiftui.explicit-color-static-member.ast')?.then.code,
|
|
95
|
+
'HEURISTICS_IOS_SWIFTUI_EXPLICIT_COLOR_STATIC_MEMBER_AST'
|
|
96
|
+
);
|
|
87
97
|
assert.equal(
|
|
88
98
|
byId.get('heuristics.ios.contains-user-filter.ast')?.then.code,
|
|
89
99
|
'HEURISTICS_IOS_CONTAINS_USER_FILTER_AST'
|
|
@@ -325,6 +325,42 @@ export const iosRules: RuleSet = [
|
|
|
325
325
|
code: 'HEURISTICS_IOS_FOREACH_INDICES_AST',
|
|
326
326
|
},
|
|
327
327
|
},
|
|
328
|
+
{
|
|
329
|
+
id: 'heuristics.ios.swiftui.inline-filtering-in-foreach.ast',
|
|
330
|
+
description: 'Detects inline filter chains inside SwiftUI ForEach rendering paths.',
|
|
331
|
+
severity: 'WARN',
|
|
332
|
+
platform: 'ios',
|
|
333
|
+
locked: true,
|
|
334
|
+
when: {
|
|
335
|
+
kind: 'Heuristic',
|
|
336
|
+
where: {
|
|
337
|
+
ruleId: 'heuristics.ios.swiftui.inline-filtering-in-foreach.ast',
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
then: {
|
|
341
|
+
kind: 'Finding',
|
|
342
|
+
message: 'AST heuristic detected inline filtering inside ForEach; prefilter and cache before rendering.',
|
|
343
|
+
code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FILTERING_IN_FOREACH_AST',
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 'heuristics.ios.swiftui.explicit-color-static-member.ast',
|
|
348
|
+
description: 'Detects explicit Color.* static member lookup in SwiftUI presentation code.',
|
|
349
|
+
severity: 'WARN',
|
|
350
|
+
platform: 'ios',
|
|
351
|
+
locked: true,
|
|
352
|
+
when: {
|
|
353
|
+
kind: 'Heuristic',
|
|
354
|
+
where: {
|
|
355
|
+
ruleId: 'heuristics.ios.swiftui.explicit-color-static-member.ast',
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
then: {
|
|
359
|
+
kind: 'Finding',
|
|
360
|
+
message: 'AST heuristic detected explicit Color static member lookup where contextual .color style is preferred.',
|
|
361
|
+
code: 'HEURISTICS_IOS_SWIFTUI_EXPLICIT_COLOR_STATIC_MEMBER_AST',
|
|
362
|
+
},
|
|
363
|
+
},
|
|
328
364
|
{
|
|
329
365
|
id: 'heuristics.ios.contains-user-filter.ast',
|
|
330
366
|
description: 'Detects contains() usage in user-facing filter flows where localizedStandardContains() may be preferred.',
|
|
@@ -257,6 +257,24 @@ export const skillsCompilerTemplates: Record<string, SkillsCompilerTemplate> = {
|
|
|
257
257
|
stage: 'PRE_PUSH',
|
|
258
258
|
locked: true,
|
|
259
259
|
},
|
|
260
|
+
{
|
|
261
|
+
id: 'skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache',
|
|
262
|
+
description: 'Avoid inline filtering in ForEach; prefilter and cache before rendering.',
|
|
263
|
+
severity: 'ERROR',
|
|
264
|
+
platform: 'ios',
|
|
265
|
+
confidence: 'HIGH',
|
|
266
|
+
stage: 'PRE_PUSH',
|
|
267
|
+
locked: true,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: 'skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue',
|
|
271
|
+
description: 'Prefer SwiftUI contextual static member lookup such as .blue instead of Color.blue.',
|
|
272
|
+
severity: 'WARN',
|
|
273
|
+
platform: 'ios',
|
|
274
|
+
confidence: 'HIGH',
|
|
275
|
+
stage: 'PRE_PUSH',
|
|
276
|
+
locked: true,
|
|
277
|
+
},
|
|
260
278
|
{
|
|
261
279
|
id: 'skills.ios.no-contains-user-filter',
|
|
262
280
|
description:
|
|
@@ -94,6 +94,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
94
94
|
'skills.ios.no-foreach-indices': heuristicDetector('ios.foreach-indices', [
|
|
95
95
|
'heuristics.ios.foreach-indices.ast',
|
|
96
96
|
]),
|
|
97
|
+
'skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache':
|
|
98
|
+
heuristicDetector('ios.swiftui.inline-filtering-in-foreach', [
|
|
99
|
+
'heuristics.ios.swiftui.inline-filtering-in-foreach.ast',
|
|
100
|
+
]),
|
|
101
|
+
'skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue':
|
|
102
|
+
heuristicDetector('ios.swiftui.explicit-color-static-member', [
|
|
103
|
+
'heuristics.ios.swiftui.explicit-color-static-member.ast',
|
|
104
|
+
]),
|
|
97
105
|
'skills.ios.no-contains-user-filter': heuristicDetector('ios.contains-user-filter', [
|
|
98
106
|
'heuristics.ios.contains-user-filter.ast',
|
|
99
107
|
]),
|
|
@@ -205,6 +205,60 @@ const collectScopePaths = (
|
|
|
205
205
|
return normalized;
|
|
206
206
|
};
|
|
207
207
|
|
|
208
|
+
const isSkillsContractCarrierPath = (path: string): boolean => {
|
|
209
|
+
const normalized = path.replace(/\\/g, '/').trim().toLowerCase();
|
|
210
|
+
return (
|
|
211
|
+
normalized === 'agents.md' ||
|
|
212
|
+
normalized === 'skills.lock.json' ||
|
|
213
|
+
normalized === 'skills.sources.json' ||
|
|
214
|
+
normalized.startsWith('vendor/skills/') ||
|
|
215
|
+
normalized.startsWith('docs/codex-skills/') ||
|
|
216
|
+
normalized === '.pumuki/policy-as-code.json'
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const isSkillsEnforcementImplementationPath = (path: string): boolean => {
|
|
221
|
+
const normalized = path.replace(/\\/g, '/').trim().toLowerCase();
|
|
222
|
+
return (
|
|
223
|
+
normalized.endsWith('.feature') ||
|
|
224
|
+
normalized.startsWith('core/facts/') ||
|
|
225
|
+
normalized.startsWith('core/rules/presets/heuristics/') ||
|
|
226
|
+
normalized.startsWith('integrations/config/') ||
|
|
227
|
+
normalized === 'integrations/git/runplatformgate.ts' ||
|
|
228
|
+
normalized === 'integrations/git/__tests__/runplatformgate.test.ts' ||
|
|
229
|
+
normalized === 'integrations/git/gitatomicity.ts' ||
|
|
230
|
+
normalized === 'integrations/git/__tests__/gitatomicity.test.ts' ||
|
|
231
|
+
isSkillsContractCarrierPath(normalized)
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const isSkillsEnforcementRemediationDiff = (
|
|
236
|
+
paths: ReadonlyArray<string>
|
|
237
|
+
): boolean => {
|
|
238
|
+
if (paths.length === 0) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const normalizedPaths = paths.map((path) => path.replace(/\\/g, '/').trim().toLowerCase());
|
|
243
|
+
const touchesDetectorSurface = normalizedPaths.some((path) =>
|
|
244
|
+
path.startsWith('core/facts/') ||
|
|
245
|
+
path.startsWith('core/rules/presets/heuristics/') ||
|
|
246
|
+
path.startsWith('integrations/config/') ||
|
|
247
|
+
path === 'integrations/git/runplatformgate.ts' ||
|
|
248
|
+
path === 'integrations/git/__tests__/runplatformgate.test.ts' ||
|
|
249
|
+
path === 'integrations/git/gitatomicity.ts' ||
|
|
250
|
+
path === 'integrations/git/__tests__/gitatomicity.test.ts'
|
|
251
|
+
);
|
|
252
|
+
const touchesLockOrScenario = normalizedPaths.some((path) =>
|
|
253
|
+
path === 'skills.lock.json' || path.endsWith('.feature')
|
|
254
|
+
);
|
|
255
|
+
return (
|
|
256
|
+
touchesDetectorSurface &&
|
|
257
|
+
touchesLockOrScenario &&
|
|
258
|
+
normalizedPaths.every((path) => isSkillsEnforcementImplementationPath(path))
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
|
|
208
262
|
const buildAtomicSlicesRemediation = (params: {
|
|
209
263
|
git: IGitService;
|
|
210
264
|
repoRoot: string;
|
|
@@ -493,6 +547,13 @@ export const evaluateGitAtomicity = (params: {
|
|
|
493
547
|
repoRoot,
|
|
494
548
|
stage: params.stage,
|
|
495
549
|
});
|
|
550
|
+
if (params.stage === 'PRE_COMMIT' && isSkillsEnforcementRemediationDiff(changedPaths)) {
|
|
551
|
+
return {
|
|
552
|
+
enabled: true,
|
|
553
|
+
allowed: true,
|
|
554
|
+
violations: [],
|
|
555
|
+
};
|
|
556
|
+
}
|
|
496
557
|
|
|
497
558
|
const prePushCommitViolations =
|
|
498
559
|
params.stage === 'PRE_PUSH'
|
|
@@ -436,6 +436,44 @@ const isSkillsContractCarrierPath = (path: string): boolean => {
|
|
|
436
436
|
);
|
|
437
437
|
};
|
|
438
438
|
|
|
439
|
+
const isSkillsEnforcementImplementationPath = (path: string): boolean => {
|
|
440
|
+
const normalized = toNormalizedPath(path).toLowerCase();
|
|
441
|
+
return (
|
|
442
|
+
normalized.endsWith('.feature') ||
|
|
443
|
+
normalized.startsWith('core/facts/') ||
|
|
444
|
+
normalized.startsWith('core/rules/presets/heuristics/') ||
|
|
445
|
+
normalized.startsWith('integrations/config/') ||
|
|
446
|
+
normalized === 'integrations/git/runplatformgate.ts' ||
|
|
447
|
+
normalized === 'integrations/git/__tests__/runplatformgate.test.ts' ||
|
|
448
|
+
isSkillsContractCarrierPath(normalized)
|
|
449
|
+
);
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const isSkillsEnforcementRemediationDiff = (
|
|
453
|
+
paths: ReadonlyArray<string>
|
|
454
|
+
): boolean => {
|
|
455
|
+
if (paths.length === 0) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const normalizedPaths = paths.map((path) => toNormalizedPath(path));
|
|
460
|
+
const touchesDetectorSurface = normalizedPaths.some((path) =>
|
|
461
|
+
path.startsWith('core/facts/') ||
|
|
462
|
+
path.startsWith('core/rules/presets/heuristics/') ||
|
|
463
|
+
path.startsWith('integrations/config/') ||
|
|
464
|
+
path === 'integrations/git/runplatformgate.ts' ||
|
|
465
|
+
path === 'integrations/git/__tests__/runplatformgate.test.ts'
|
|
466
|
+
);
|
|
467
|
+
const touchesLockOrScenario = normalizedPaths.some((path) =>
|
|
468
|
+
path === 'skills.lock.json' || path.endsWith('.feature')
|
|
469
|
+
);
|
|
470
|
+
return (
|
|
471
|
+
touchesDetectorSurface &&
|
|
472
|
+
touchesLockOrScenario &&
|
|
473
|
+
normalizedPaths.every((path) => isSkillsEnforcementImplementationPath(path))
|
|
474
|
+
);
|
|
475
|
+
};
|
|
476
|
+
|
|
439
477
|
const collectStagedPaths = (git: IGitService, repoRoot: string): ReadonlyArray<string> => {
|
|
440
478
|
try {
|
|
441
479
|
return git.runGit(['diff', '--cached', '--name-only'], repoRoot)
|
|
@@ -1433,7 +1471,12 @@ export async function runPlatformGate(params: {
|
|
|
1433
1471
|
].sort(),
|
|
1434
1472
|
})
|
|
1435
1473
|
: undefined;
|
|
1436
|
-
const
|
|
1474
|
+
const skillsEnforcementRemediationDiff = isSkillsEnforcementRemediationDiff(stagedPaths);
|
|
1475
|
+
const remediationProgressAllowsGlobalGap =
|
|
1476
|
+
remediationProgressFinding !== undefined ||
|
|
1477
|
+
(skillsEnforcementRemediationDiff &&
|
|
1478
|
+
!hasNativeBlockingFinding &&
|
|
1479
|
+
!hasTddBddBlockingFinding);
|
|
1437
1480
|
const effectiveTddBddFindings = remediationProgressAllowsGlobalGap
|
|
1438
1481
|
? tddBddEvaluation.findings.map((finding) =>
|
|
1439
1482
|
finding.code === 'TDD_BDD_EVIDENCE_STALE'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.155",
|
|
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-
|
|
4
|
+
"generatedAt": "2026-05-05T20:56:13.453Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -5298,7 +5298,7 @@
|
|
|
5298
5298
|
"name": "ios-guidelines",
|
|
5299
5299
|
"version": "1.0.0",
|
|
5300
5300
|
"source": "file:vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
5301
|
-
"hash": "
|
|
5301
|
+
"hash": "3c15fbec440154ff12f4ca998166a3faed9d943831bf34e8c80969bbcd0f94e5",
|
|
5302
5302
|
"rules": [
|
|
5303
5303
|
{
|
|
5304
5304
|
"id": "skills.ios.guideline.ios.accessibility-identifiers-para-localizar-elementos",
|
|
@@ -5384,18 +5384,6 @@
|
|
|
5384
5384
|
"evaluationMode": "DECLARATIVE",
|
|
5385
5385
|
"origin": "core"
|
|
5386
5386
|
},
|
|
5387
|
-
{
|
|
5388
|
-
"id": "skills.ios.guideline.ios.apiendpoint-como-struct-data-driven-ocp-endpoints-en-features-no-enum-",
|
|
5389
|
-
"description": "APIEndpoint como struct data-driven - OCP: endpoints en features, no enum central",
|
|
5390
|
-
"severity": "WARN",
|
|
5391
|
-
"platform": "ios",
|
|
5392
|
-
"sourceSkill": "ios-guidelines",
|
|
5393
|
-
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
5394
|
-
"confidence": "MEDIUM",
|
|
5395
|
-
"locked": true,
|
|
5396
|
-
"evaluationMode": "DECLARATIVE",
|
|
5397
|
-
"origin": "core"
|
|
5398
|
-
},
|
|
5399
5387
|
{
|
|
5400
5388
|
"id": "skills.ios.guideline.ios.app-transport-security-ats-https-por-defecto",
|
|
5401
5389
|
"description": "App Transport Security (ATS) - HTTPS por defecto",
|
|
@@ -7761,18 +7749,6 @@
|
|
|
7761
7749
|
"evaluationMode": "DECLARATIVE",
|
|
7762
7750
|
"origin": "core"
|
|
7763
7751
|
},
|
|
7764
|
-
{
|
|
7765
|
-
"id": "skills.ios.guideline.ios.verificar-que-no-viole-solid-srp-ocp-lsp-isp-dip",
|
|
7766
|
-
"description": "Verificar que NO viole SOLID (SRP, OCP, LSP, ISP, DIP)",
|
|
7767
|
-
"severity": "WARN",
|
|
7768
|
-
"platform": "ios",
|
|
7769
|
-
"sourceSkill": "ios-guidelines",
|
|
7770
|
-
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
7771
|
-
"confidence": "MEDIUM",
|
|
7772
|
-
"locked": true,
|
|
7773
|
-
"evaluationMode": "DECLARATIVE",
|
|
7774
|
-
"origin": "core"
|
|
7775
|
-
},
|
|
7776
7752
|
{
|
|
7777
7753
|
"id": "skills.ios.guideline.ios.viewmodels-por-pantalla-orderslistviewmodel-orderdetailviewmodel",
|
|
7778
7754
|
"description": "ViewModels por pantalla - OrdersListViewModel, OrderDetailViewModel",
|
|
@@ -8098,6 +8074,19 @@
|
|
|
8098
8074
|
"evaluationMode": "AUTO",
|
|
8099
8075
|
"origin": "core"
|
|
8100
8076
|
},
|
|
8077
|
+
{
|
|
8078
|
+
"id": "skills.ios.no-solid-violations",
|
|
8079
|
+
"description": "Verificar que NO viole SOLID (SRP, OCP, LSP, ISP, DIP)",
|
|
8080
|
+
"severity": "WARN",
|
|
8081
|
+
"platform": "ios",
|
|
8082
|
+
"sourceSkill": "ios-guidelines",
|
|
8083
|
+
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
8084
|
+
"stage": "PRE_WRITE",
|
|
8085
|
+
"confidence": "MEDIUM",
|
|
8086
|
+
"locked": true,
|
|
8087
|
+
"evaluationMode": "AUTO",
|
|
8088
|
+
"origin": "core"
|
|
8089
|
+
},
|
|
8101
8090
|
{
|
|
8102
8091
|
"id": "skills.ios.no-string-format",
|
|
8103
8092
|
"description": "String(localized:) + Text format en lugar de String(format:)",
|
|
@@ -8299,7 +8288,7 @@
|
|
|
8299
8288
|
"name": "ios-swiftui-expert-guidelines",
|
|
8300
8289
|
"version": "1.0.0",
|
|
8301
8290
|
"source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8302
|
-
"hash": "
|
|
8291
|
+
"hash": "d73de37053af4a98ae690284715bb6d4b73d10a64d137670633e3b0a98705012",
|
|
8303
8292
|
"rules": [
|
|
8304
8293
|
{
|
|
8305
8294
|
"id": "skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear",
|
|
@@ -8316,6 +8305,19 @@
|
|
|
8316
8305
|
},
|
|
8317
8306
|
{
|
|
8318
8307
|
"id": "skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache",
|
|
8308
|
+
"description": "Avoid inline filtering in ForEach; prefilter and cache before rendering.",
|
|
8309
|
+
"severity": "ERROR",
|
|
8310
|
+
"platform": "ios",
|
|
8311
|
+
"confidence": "HIGH",
|
|
8312
|
+
"stage": "PRE_PUSH",
|
|
8313
|
+
"locked": true,
|
|
8314
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8315
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8316
|
+
"evaluationMode": "AUTO",
|
|
8317
|
+
"origin": "core"
|
|
8318
|
+
},
|
|
8319
|
+
{
|
|
8320
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache-2",
|
|
8319
8321
|
"description": "Avoid inline filtering in ForEach (prefilter and cache)",
|
|
8320
8322
|
"severity": "ERROR",
|
|
8321
8323
|
"platform": "ios",
|
|
@@ -8376,6 +8378,19 @@
|
|
|
8376
8378
|
},
|
|
8377
8379
|
{
|
|
8378
8380
|
"id": "skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue",
|
|
8381
|
+
"description": "Prefer SwiftUI contextual static member lookup such as .blue instead of Color.blue.",
|
|
8382
|
+
"severity": "WARN",
|
|
8383
|
+
"platform": "ios",
|
|
8384
|
+
"confidence": "HIGH",
|
|
8385
|
+
"stage": "PRE_PUSH",
|
|
8386
|
+
"locked": true,
|
|
8387
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8388
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8389
|
+
"evaluationMode": "AUTO",
|
|
8390
|
+
"origin": "core"
|
|
8391
|
+
},
|
|
8392
|
+
{
|
|
8393
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue-2",
|
|
8379
8394
|
"description": "Prefer static member lookup (.blue vs Color.blue)",
|
|
8380
8395
|
"severity": "WARN",
|
|
8381
8396
|
"platform": "ios",
|