@momo-kits/native-kits 0.157.1-skill.3-debug → 0.157.1-skill.6-debug

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.
Files changed (109) hide show
  1. package/.claude/skills/momo-native-kits-skills/README.md +52 -0
  2. package/.claude/skills/momo-native-kits-skills/SKILL.md +32 -0
  3. package/.claude/skills/momo-native-kits-skills/data-display/SKILL.md +42 -0
  4. package/.claude/skills/momo-native-kits-skills/feedback/SKILL.md +43 -0
  5. package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/feedback}/references/popup.md +3 -10
  6. package/.claude/skills/momo-native-kits-skills/form-controls/SKILL.md +42 -0
  7. package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/form-controls}/references/button.md +2 -3
  8. package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/form-controls}/references/input.md +3 -7
  9. package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/form-controls}/references/radio.md +9 -9
  10. package/.claude/skills/momo-native-kits-skills/layout/SKILL.md +39 -0
  11. package/.claude/skills/momo-native-kits-skills/navigation/SKILL.md +38 -0
  12. package/.claude/skills/momo-native-kits-skills/output/test-report.json +12 -0
  13. package/.claude/skills/momo-native-kits-skills/output/validation-result.json +108 -0
  14. package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills}/references/constants.md +81 -6
  15. package/.claude/skills/momo-native-kits-skills/references/design-guidelines.md +130 -0
  16. package/.claude/skills/momo-native-kits-skills/references/developer/android-compose.md +29 -0
  17. package/.claude/skills/momo-native-kits-skills/references/developer/code-convention.md +118 -0
  18. package/.claude/skills/momo-native-kits-skills/references/developer/compose-multiplatform.md +29 -0
  19. package/.claude/skills/momo-native-kits-skills/scripts/logger.js +63 -0
  20. package/.claude/skills/momo-native-kits-skills/scripts/rules/badge.js +32 -0
  21. package/.claude/skills/momo-native-kits-skills/scripts/rules/bottomtab.js +19 -0
  22. package/.claude/skills/momo-native-kits-skills/scripts/rules/button.js +32 -0
  23. package/.claude/skills/momo-native-kits-skills/scripts/rules/card.js +32 -0
  24. package/.claude/skills/momo-native-kits-skills/scripts/rules/checkbox.js +32 -0
  25. package/.claude/skills/momo-native-kits-skills/scripts/rules/chip.js +59 -0
  26. package/.claude/skills/momo-native-kits-skills/scripts/rules/divider.js +19 -0
  27. package/.claude/skills/momo-native-kits-skills/scripts/rules/global.js +61 -0
  28. package/.claude/skills/momo-native-kits-skills/scripts/rules/index.js +54 -0
  29. package/.claude/skills/momo-native-kits-skills/scripts/rules/input.js +32 -0
  30. package/.claude/skills/momo-native-kits-skills/scripts/rules/navigation.js +48 -0
  31. package/.claude/skills/momo-native-kits-skills/scripts/rules/popup.js +45 -0
  32. package/.claude/skills/momo-native-kits-skills/scripts/rules/radio.js +105 -0
  33. package/.claude/skills/momo-native-kits-skills/scripts/rules/skeleton.js +20 -0
  34. package/.claude/skills/momo-native-kits-skills/scripts/rules/snackbar.js +32 -0
  35. package/.claude/skills/momo-native-kits-skills/scripts/rules/state.js +21 -0
  36. package/.claude/skills/momo-native-kits-skills/scripts/rules/switch.js +45 -0
  37. package/.claude/skills/momo-native-kits-skills/scripts/rules/text.js +19 -0
  38. package/.claude/skills/momo-native-kits-skills/scripts/rules/tooltip.js +45 -0
  39. package/.claude/skills/momo-native-kits-skills/scripts/rules/trustbanner.js +22 -0
  40. package/.claude/skills/momo-native-kits-skills/scripts/test-on-device.sh +117 -0
  41. package/.claude/skills/momo-native-kits-skills/scripts/test-runner.js +167 -0
  42. package/.claude/skills/momo-native-kits-skills/scripts/validate-kits.js +131 -0
  43. package/.momo-agent.json +13 -0
  44. package/compose/build.gradle.kts +1 -1
  45. package/gradle.properties +1 -1
  46. package/package.json +1 -1
  47. package/.claude/momo-native-kits-skill/SKILL.md +0 -51
  48. package/.claude/momo-native-kits-skill/evals/evals.json +0 -95
  49. package/.claude/momo-native-kits-skill/workspace/iteration-1/benchmark.json +0 -20
  50. package/.claude/momo-native-kits-skill/workspace/iteration-1/benchmark.md +0 -13
  51. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-0-button/eval_metadata.json +0 -6
  52. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-0-button/with_skill/outputs/ButtonExample.kt +0 -55
  53. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-0-button/without_skill/outputs/ButtonExample.kt +0 -45
  54. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-1-input/eval_metadata.json +0 -6
  55. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-1-input/with_skill/outputs/InputPhoneExample.kt +0 -40
  56. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-1-input/without_skill/outputs/InputPhoneExample.kt +0 -42
  57. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-2-bottomtab/eval_metadata.json +0 -6
  58. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-2-bottomtab/with_skill/outputs/BottomTabExample.kt +0 -236
  59. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-2-bottomtab/without_skill/outputs/BottomTabExample.kt +0 -152
  60. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-3-checkbox/eval_metadata.json +0 -6
  61. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-3-checkbox/with_skill/outputs/CheckBoxExample.kt +0 -49
  62. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-3-checkbox/without_skill/outputs/CheckBoxExample.kt +0 -123
  63. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-4-datetimepicker/eval_metadata.json +0 -6
  64. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-4-datetimepicker/with_skill/outputs/DateTimePickerExample.kt +0 -318
  65. package/.claude/momo-native-kits-skill/workspace/iteration-1/eval-4-datetimepicker/without_skill/outputs/DateTimePickerExample.kt +0 -330
  66. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-card/with_skill/outputs/CardExample.kt +0 -124
  67. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-card/without_skill/outputs/CardExample.kt +0 -71
  68. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-loginform/with_skill/outputs/LoginFormExample.kt +0 -134
  69. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-loginform/without_skill/outputs/LoginFormExample.kt +0 -199
  70. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-navcontainer/with_skill/outputs/NavigationContainerExample.kt +0 -224
  71. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-navcontainer/without_skill/outputs/NavigationContainerExample.kt +0 -225
  72. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-popup/with_skill/outputs/PopupExample.kt +0 -79
  73. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-popup/without_skill/outputs/PopupExample.kt +0 -169
  74. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-setoptions/eval_metadata.json +0 -6
  75. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-setoptions/with_skill/outputs/SetOptionsExample.kt +0 -255
  76. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-setoptions/without_skill/outputs/SetOptionsExample.kt +0 -212
  77. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-skeleton/with_skill/outputs/SkeletonExample.kt +0 -199
  78. package/.claude/momo-native-kits-skill/workspace/iteration-2/eval-skeleton/without_skill/outputs/SkeletonExample.kt +0 -229
  79. package/.claude/momo-native-kits-skill/workspace/iteration-3/benchmark.json +0 -20
  80. package/.claude/momo-native-kits-skill/workspace/iteration-3/benchmark.md +0 -13
  81. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-button/eval_metadata.json +0 -22
  82. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-button/with_skill/outputs/PrimaryButtonExample.kt +0 -38
  83. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-button/with_skill/timing.json +0 -5
  84. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-button/without_skill/outputs/PrimaryButtonExample.kt +0 -83
  85. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-button/without_skill/timing.json +0 -5
  86. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-navcontainer/eval_metadata.json +0 -22
  87. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-navcontainer/with_skill/outputs/NavigationContainerExample.kt +0 -547
  88. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-navcontainer/with_skill/timing.json +0 -5
  89. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-navcontainer/without_skill/outputs/MoMoNavigationContainer.kt +0 -519
  90. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-navcontainer/without_skill/timing.json +0 -5
  91. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-setoptions/eval_metadata.json +0 -27
  92. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-setoptions/with_skill/outputs/SetOptionsExample.kt +0 -429
  93. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-setoptions/with_skill/timing.json +0 -5
  94. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-setoptions/without_skill/outputs/SetOptionsExample.kt +0 -353
  95. package/.claude/momo-native-kits-skill/workspace/iteration-3/eval-setoptions/without_skill/timing.json +0 -5
  96. package/.claude/settings.local.json +0 -41
  97. package/local.properties +0 -8
  98. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/data-display}/references/badge.md +0 -0
  99. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/data-display}/references/chip.md +0 -0
  100. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/data-display}/references/skeleton.md +0 -0
  101. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/data-display}/references/text.md +0 -0
  102. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/feedback}/references/snackbar.md +0 -0
  103. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/feedback}/references/tooltip.md +0 -0
  104. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/feedback}/references/trustbanner.md +0 -0
  105. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/form-controls}/references/checkbox.md +0 -0
  106. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/form-controls}/references/switch.md +0 -0
  107. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/layout}/references/card.md +0 -0
  108. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/layout}/references/divider.md +0 -0
  109. /package/.claude/{momo-native-kits-skill → skills/momo-native-kits-skills/navigation}/references/navigation.md +0 -0
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Chip validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'chip-no-text',
8
+ category: 'Chip',
9
+ name: 'Chip uses "label" (not "text")',
10
+ severity: 'error',
11
+ applies: (content) => /\bChip\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
12
+ validate(content) {
13
+ if (/Chip\s*\([^)]*\btext\s*=/s.test(content)) {
14
+ return { passed: false, evidence: 'Chip has no "text" — use "label" instead' };
15
+ }
16
+ return { passed: true, evidence: 'Chip uses correct "label" param' };
17
+ }
18
+ },
19
+ {
20
+ id: 'chip-no-isSelected',
21
+ category: 'Chip',
22
+ name: 'Chip uses "selected" (not "isSelected")',
23
+ severity: 'error',
24
+ applies: (content) => /\bChip\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
25
+ validate(content) {
26
+ if (/Chip\s*\([^)]*\bisSelected\s*=/s.test(content)) {
27
+ return { passed: false, evidence: 'Chip has no "isSelected" — use "selected" instead' };
28
+ }
29
+ return { passed: true, evidence: 'Chip uses correct "selected" param' };
30
+ }
31
+ },
32
+ {
33
+ id: 'chip-no-leadingIcon',
34
+ category: 'Chip',
35
+ name: 'Chip uses "iconLeft" (not "leadingIcon")',
36
+ severity: 'error',
37
+ applies: (content) => /\bChip\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
38
+ validate(content) {
39
+ if (/Chip\s*\([^)]*\bleadingIcon\s*=/s.test(content)) {
40
+ return { passed: false, evidence: 'Chip has no "leadingIcon" — use "iconLeft" instead' };
41
+ }
42
+ return { passed: true, evidence: 'Chip uses correct icon param names' };
43
+ }
44
+ },
45
+ {
46
+ id: 'chip-size-enum',
47
+ category: 'Chip',
48
+ name: 'Chip uses ChipSize enum',
49
+ severity: 'error',
50
+ applies: (content) => /\bChip\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
51
+ validate(content) {
52
+ // Check for wrong Size enum (Button's enum) used on Chip
53
+ if (/Chip\s*\([^)]*\bsize\s*=\s*Size\./s.test(content)) {
54
+ return { passed: false, evidence: 'Chip uses Size.X — should use ChipSize.SMALL or ChipSize.LARGE' };
55
+ }
56
+ return { passed: true, evidence: 'Chip size enum looks correct' };
57
+ }
58
+ },
59
+ ];
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Divider validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'divider-no-params',
8
+ category: 'Divider',
9
+ name: 'Divider() takes no parameters',
10
+ severity: 'warning',
11
+ applies: (content) => /\bDivider\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
12
+ validate(content) {
13
+ if (/Divider\s*\([^)]+\)/s.test(content)) {
14
+ return { passed: false, evidence: 'Divider() takes no parameters' };
15
+ }
16
+ return { passed: true, evidence: 'Divider() called correctly' };
17
+ }
18
+ },
19
+ ];
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Global validation rules
3
+ * - No Material Design imports
4
+ * - No Material components used
5
+ * - Uses vn.momo.kits imports
6
+ */
7
+
8
+ module.exports = [
9
+ {
10
+ id: 'no-material-import',
11
+ category: 'Global',
12
+ name: 'No Material Design imports',
13
+ severity: 'error',
14
+ validate(content) {
15
+ const patterns = [
16
+ { regex: /import\s+androidx\.compose\.material\b/, label: 'androidx.compose.material' },
17
+ { regex: /import\s+androidx\.compose\.material3\b/, label: 'androidx.compose.material3' },
18
+ ];
19
+ for (const p of patterns) {
20
+ if (p.regex.test(content)) {
21
+ return { passed: false, evidence: `Found forbidden import: ${p.label}` };
22
+ }
23
+ }
24
+ return { passed: true, evidence: 'No Material imports found' };
25
+ }
26
+ },
27
+ {
28
+ id: 'no-material-components',
29
+ category: 'Global',
30
+ name: 'No Material components used',
31
+ severity: 'error',
32
+ validate(content) {
33
+ // Only flag Material-specific components that don't exist in MoMo
34
+ // Note: Switch, CheckBox exist in both Material and MoMo - handled by component-specific rules
35
+ const materialComponents = [
36
+ 'RadioButton', 'MaterialTheme', 'TextField', 'OutlinedTextField',
37
+ 'AlertDialog', 'FloatingActionButton', 'TopAppBar', 'BottomNavigation',
38
+ 'NavigationBar', 'Scaffold',
39
+ ];
40
+ for (const comp of materialComponents) {
41
+ const regex = new RegExp(`\\b${comp}\\s*\\(`);
42
+ if (regex.test(content)) {
43
+ return { passed: false, evidence: `Found Material component: ${comp}()` };
44
+ }
45
+ }
46
+ return { passed: true, evidence: 'No Material components used' };
47
+ }
48
+ },
49
+ {
50
+ id: 'momo-kits-import',
51
+ category: 'Global',
52
+ name: 'Uses vn.momo.kits imports',
53
+ severity: 'warning',
54
+ validate(content) {
55
+ if (/import\s+vn\.momo\.kits\./.test(content)) {
56
+ return { passed: true, evidence: 'Found vn.momo.kits imports' };
57
+ }
58
+ return { passed: false, evidence: 'No vn.momo.kits imports found' };
59
+ }
60
+ },
61
+ ];
@@ -0,0 +1,54 @@
1
+ /**
2
+ * File: scripts/rules/index.js
3
+ * Created At: 2026-03-16 10:00:00 +07:00
4
+ * Created By: AI
5
+ * AI Agent: Claude
6
+ * Model: opus-4-6
7
+ *
8
+ * All validation rules
9
+ * Export all rule files for MoMo Native Kits validation
10
+ */
11
+
12
+ // AI-GENERATED START: export all validation rules
13
+ const global = require('./global');
14
+ const button = require('./button');
15
+ const checkbox = require('./checkbox');
16
+ const switch_ = require('./switch');
17
+ const chip = require('./chip');
18
+ const badge = require('./badge');
19
+ const skeleton = require('./skeleton');
20
+ const tooltip = require('./tooltip');
21
+ const snackbar = require('./snackbar');
22
+ const trustbanner = require('./trustbanner');
23
+ const divider = require('./divider');
24
+ const radio = require('./radio');
25
+ const popup = require('./popup');
26
+ const input = require('./input');
27
+ const bottomtab = require('./bottomtab');
28
+ const text = require('./text');
29
+ const state = require('./state');
30
+ const card = require('./card');
31
+ const navigation = require('./navigation');
32
+
33
+ module.exports = [
34
+ ...global,
35
+ ...button,
36
+ ...checkbox,
37
+ ...switch_,
38
+ ...chip,
39
+ ...badge,
40
+ ...skeleton,
41
+ ...tooltip,
42
+ ...snackbar,
43
+ ...trustbanner,
44
+ ...divider,
45
+ ...radio,
46
+ ...popup,
47
+ ...input,
48
+ ...bottomtab,
49
+ ...text,
50
+ ...state,
51
+ ...card,
52
+ ...navigation,
53
+ ];
54
+ // AI-GENERATED END: export all validation rules
@@ -0,0 +1,32 @@
1
+ /**
2
+ * InputPhoneNumber validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'phone-no-floatingValue',
8
+ category: 'InputPhoneNumber',
9
+ name: 'InputPhoneNumber has no floatingValue',
10
+ severity: 'error',
11
+ applies: (content) => /InputPhoneNumber\s*\(/.test(content),
12
+ validate(content) {
13
+ if (/InputPhoneNumber[^)]*floatingValue\s*=/.test(content)) {
14
+ return { passed: false, evidence: 'floatingValue does not exist on InputPhoneNumber' };
15
+ }
16
+ return { passed: true, evidence: 'No invalid floatingValue usage' };
17
+ }
18
+ },
19
+ {
20
+ id: 'phone-no-countryCode',
21
+ category: 'InputPhoneNumber',
22
+ name: 'InputPhoneNumber has no countryCode',
23
+ severity: 'error',
24
+ applies: (content) => /InputPhoneNumber\s*\(/.test(content),
25
+ validate(content) {
26
+ if (/InputPhoneNumber[^)]*countryCode\s*=/.test(content)) {
27
+ return { passed: false, evidence: 'countryCode does not exist — country code +84 is hardcoded' };
28
+ }
29
+ return { passed: true, evidence: 'No invalid countryCode usage' };
30
+ }
31
+ },
32
+ ];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Navigation validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'nav-single-container',
8
+ category: 'Navigation',
9
+ name: 'Only one NavigationContainer per file',
10
+ severity: 'warning',
11
+ applies: (content) => /NavigationContainer\s*\(/.test(content),
12
+ validate(content) {
13
+ const count = (content.match(/NavigationContainer\s*\(/g) || []).length;
14
+ if (count > 1) {
15
+ return { passed: false, evidence: `Found ${count} NavigationContainer — should be only 1 per Fragment` };
16
+ }
17
+ return { passed: true, evidence: 'Single NavigationContainer found' };
18
+ }
19
+ },
20
+ {
21
+ id: 'nav-setOptions-in-launched',
22
+ category: 'Navigation',
23
+ name: 'setOptions inside LaunchedEffect',
24
+ severity: 'warning',
25
+ applies: (content) => /setOptions\s*\(/.test(content),
26
+ validate(content) {
27
+ const hasLaunchedEffect = /LaunchedEffect[\s\S]*setOptions/.test(content);
28
+ const hasOnClick = /onClick[\s\S]{0,100}setOptions/.test(content);
29
+ if (!hasLaunchedEffect && !hasOnClick) {
30
+ return { passed: false, evidence: 'setOptions should be called inside LaunchedEffect(Unit) or event handler' };
31
+ }
32
+ return { passed: true, evidence: 'setOptions used in correct scope' };
33
+ }
34
+ },
35
+ {
36
+ id: 'nav-no-scaffold',
37
+ category: 'Navigation',
38
+ name: 'No Scaffold (use NavigationContainer)',
39
+ severity: 'error',
40
+ applies: (content) => /vn\.momo\.kits/.test(content),
41
+ validate(content) {
42
+ if (/\bScaffold\s*\(/.test(content)) {
43
+ return { passed: false, evidence: 'Scaffold is Material — use NavigationContainer instead' };
44
+ }
45
+ return { passed: true, evidence: 'No Scaffold usage' };
46
+ }
47
+ },
48
+ ];
@@ -0,0 +1,45 @@
1
+ /**
2
+ * PopupNotify validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'popup-no-message',
8
+ category: 'PopupNotify',
9
+ name: 'PopupNotify uses description (not message)',
10
+ severity: 'error',
11
+ applies: (content) => /PopupNotifyProps/.test(content),
12
+ validate(content) {
13
+ if (/PopupNotifyProps\s*\([^)]*\bmessage\s*=/s.test(content)) {
14
+ return { passed: false, evidence: 'PopupNotifyProps uses "message" — should be "description"' };
15
+ }
16
+ return { passed: true, evidence: 'Uses "description" correctly' };
17
+ }
18
+ },
19
+ {
20
+ id: 'popup-no-type',
21
+ category: 'PopupNotify',
22
+ name: 'No PopupNotifyType (does not exist)',
23
+ severity: 'error',
24
+ applies: (content) => /PopupNotify/.test(content),
25
+ validate(content) {
26
+ if (/PopupNotifyType/.test(content)) {
27
+ return { passed: false, evidence: 'PopupNotifyType does not exist — remove type param' };
28
+ }
29
+ return { passed: true, evidence: 'No invalid PopupNotifyType usage' };
30
+ }
31
+ },
32
+ {
33
+ id: 'popup-onIconClose',
34
+ category: 'PopupNotify',
35
+ name: 'PopupNotify has onIconClose (required)',
36
+ severity: 'error',
37
+ applies: (content) => /PopupNotifyProps\s*\(/.test(content),
38
+ validate(content) {
39
+ if (!/onIconClose\s*=/.test(content)) {
40
+ return { passed: false, evidence: 'Missing required onIconClose in PopupNotifyProps' };
41
+ }
42
+ return { passed: true, evidence: 'onIconClose provided' };
43
+ }
44
+ },
45
+ ];
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Radio validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'radio-onChange',
8
+ category: 'Radio',
9
+ name: 'Radio onChange is () -> Unit',
10
+ severity: 'error',
11
+ applies: (content) => /\bRadio\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
12
+ validate(content) {
13
+ if (/onChange\s*=\s*\{[^}]*=\s*it\s*\}/.test(content)) {
14
+ return { passed: false, evidence: 'onChange uses "it" — Radio.onChange is () -> Unit, not (T) -> Unit' };
15
+ }
16
+ return { passed: true, evidence: 'Radio onChange signature correct' };
17
+ }
18
+ },
19
+ {
20
+ id: 'radio-no-onSelect',
21
+ category: 'Radio',
22
+ name: 'Radio uses "onChange" (not "onSelect")',
23
+ severity: 'error',
24
+ applies: (content) => /\bRadio\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
25
+ validate(content) {
26
+ if (/Radio\s*\([^)]*\bonSelect\s*=/s.test(content)) {
27
+ return { passed: false, evidence: 'Radio has no "onSelect" — use "onChange" instead' };
28
+ }
29
+ return { passed: true, evidence: 'Radio uses correct callback name' };
30
+ }
31
+ },
32
+ {
33
+ id: 'radio-no-generic-type',
34
+ category: 'Radio',
35
+ name: 'No generic type parameter on Radio',
36
+ severity: 'error',
37
+ applies: (content) => /\bRadio\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
38
+ validate(content) {
39
+ const genericPattern = /Radio\s*<[A-Za-z]+>\s*\(/;
40
+ if (genericPattern.test(content)) {
41
+ return { passed: false, evidence: 'Radio is not generic — remove type parameter (e.g., Radio<String>(...) → Radio(...))' };
42
+ }
43
+ return { passed: true, evidence: 'No generic type parameter found' };
44
+ }
45
+ },
46
+ {
47
+ id: 'radio-value-groupValue',
48
+ category: 'Radio',
49
+ name: 'Radio has both value and groupValue',
50
+ severity: 'error',
51
+ applies: (content) => /\bRadio\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
52
+ validate(content) {
53
+ const radioCallRegex = /Radio\s*\(([\s\S]*?)\n\s*\)/g;
54
+ let match;
55
+ let callIndex = 0;
56
+ let allPassed = true;
57
+
58
+ while ((match = radioCallRegex.exec(content)) !== null) {
59
+ callIndex++;
60
+ const args = match[1];
61
+
62
+ const hasValue = /\bvalue\s*=/.test(args);
63
+ const hasGroupValue = /\bgroupValue\s*=/.test(args);
64
+
65
+ if (!hasValue || !hasGroupValue) {
66
+ const missing = [];
67
+ if (!hasValue) missing.push('value');
68
+ if (!hasGroupValue) missing.push('groupValue');
69
+ allPassed = false;
70
+ return { passed: false, evidence: `Radio #${callIndex} missing: ${missing.join(', ')}` };
71
+ }
72
+ }
73
+
74
+ if (callIndex === 0) {
75
+ return { passed: true, evidence: 'No Radio calls found' };
76
+ }
77
+ return { passed: allPassed, evidence: `All ${callIndex} Radio call(s) have value and groupValue` };
78
+ }
79
+ },
80
+ {
81
+ id: 'radio-group-pattern',
82
+ category: 'Radio',
83
+ name: 'Radio used in group (2+ Radio with shared groupValue)',
84
+ severity: 'warning',
85
+ applies: (content) => /\bRadio\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
86
+ validate(content) {
87
+ const radioCount = (content.match(/Radio\s*\(/g) || []).length;
88
+
89
+ if (radioCount === 0) {
90
+ return { passed: true, evidence: 'No Radio usage' };
91
+ } else if (radioCount === 1) {
92
+ // Check if it's inside a forEach/for loop (dynamic group)
93
+ const hasDynamicGroup = /\.forEach\s*\{[\s\S]*Radio\s*\(/.test(content)
94
+ || /for\s*\([\s\S]*Radio\s*\(/.test(content)
95
+ || /entries\.forEach/.test(content);
96
+
97
+ if (hasDynamicGroup) {
98
+ return { passed: true, evidence: 'Radio inside loop — dynamic group detected' };
99
+ }
100
+ return { passed: false, evidence: 'Only 1 Radio found — should be in groups of 2+' };
101
+ }
102
+ return { passed: true, evidence: `${radioCount} Radio calls found — group pattern OK` };
103
+ }
104
+ },
105
+ ];
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Skeleton validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'skeleton-no-params',
8
+ category: 'Skeleton',
9
+ name: 'Skeleton() takes no parameters',
10
+ severity: 'error',
11
+ applies: (content) => /\bSkeleton\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
12
+ validate(content) {
13
+ // Skeleton() has no params — any param usage is wrong
14
+ if (/Skeleton\s*\([^)]+\)/s.test(content)) {
15
+ return { passed: false, evidence: 'Skeleton() takes no parameters — use Box with modifier for sizing' };
16
+ }
17
+ return { passed: true, evidence: 'Skeleton() called correctly with no params' };
18
+ }
19
+ },
20
+ ];
@@ -0,0 +1,32 @@
1
+ /**
2
+ * SnackBar validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'snackbar-no-message',
8
+ category: 'SnackBar',
9
+ name: 'SnackBar is sealed class (not function with message)',
10
+ severity: 'error',
11
+ applies: (content) => /\bSnackBar\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
12
+ validate(content) {
13
+ if (/SnackBar\s*\([^)]*\bmessage\s*=/s.test(content)) {
14
+ return { passed: false, evidence: 'SnackBar has no "message" param — use SnackBar.Custom(content = { ... }) or SnackBar.Toast()' };
15
+ }
16
+ return { passed: true, evidence: 'SnackBar API usage looks correct' };
17
+ }
18
+ },
19
+ {
20
+ id: 'snackbar-no-duration-enum',
21
+ category: 'SnackBar',
22
+ name: 'No SnackBarDuration enum (does not exist)',
23
+ severity: 'error',
24
+ applies: (content) => /SnackBar/.test(content) && /vn\.momo\.kits/.test(content),
25
+ validate(content) {
26
+ if (/SnackBarDuration/.test(content)) {
27
+ return { passed: false, evidence: 'SnackBarDuration does not exist — use duration: Long? (millis) directly' };
28
+ }
29
+ return { passed: true, evidence: 'No invalid SnackBarDuration usage' };
30
+ }
31
+ },
32
+ ];
@@ -0,0 +1,21 @@
1
+ /**
2
+ * State management validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'state-remember',
8
+ category: 'State',
9
+ name: 'MutableState uses remember',
10
+ severity: 'warning',
11
+ applies: (content) => /mutableStateOf/.test(content) && /vn\.momo\.kits/.test(content),
12
+ validate(content) {
13
+ const unremembered = /(?<!remember\s*\{\s*)mutableStateOf/.test(content);
14
+ const hasRemember = /remember\s*\{?\s*mutableStateOf/.test(content) || /by\s+remember/.test(content);
15
+ if (!hasRemember && unremembered) {
16
+ return { passed: false, evidence: 'mutableStateOf without remember — state will reset on recomposition' };
17
+ }
18
+ return { passed: true, evidence: 'State properly wrapped in remember' };
19
+ }
20
+ },
21
+ ];
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Switch validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'switch-no-checked',
8
+ category: 'Switch',
9
+ name: 'Switch uses "value" (not "checked")',
10
+ severity: 'error',
11
+ applies: (content) => /\bSwitch\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
12
+ validate(content) {
13
+ if (/Switch\s*\([^)]*\bchecked\s*=/s.test(content)) {
14
+ return { passed: false, evidence: 'Switch has no "checked" — use "value" instead' };
15
+ }
16
+ return { passed: true, evidence: 'Switch uses correct "value" param' };
17
+ }
18
+ },
19
+ {
20
+ id: 'switch-no-onCheckedChange',
21
+ category: 'Switch',
22
+ name: 'Switch uses "onChange" (not "onCheckedChange")',
23
+ severity: 'error',
24
+ applies: (content) => /\bSwitch\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
25
+ validate(content) {
26
+ if (/Switch\s*\([^)]*\bonCheckedChange\s*=/s.test(content)) {
27
+ return { passed: false, evidence: 'Switch has no "onCheckedChange" — use "onChange" instead' };
28
+ }
29
+ return { passed: true, evidence: 'Switch uses correct "onChange" param' };
30
+ }
31
+ },
32
+ {
33
+ id: 'switch-no-enabled',
34
+ category: 'Switch',
35
+ name: 'Switch uses "disabled" (not "enabled")',
36
+ severity: 'error',
37
+ applies: (content) => /\bSwitch\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
38
+ validate(content) {
39
+ if (/Switch\s*\([^)]*\benabled\s*=/s.test(content)) {
40
+ return { passed: false, evidence: 'Switch has no "enabled" — use "disabled = true" instead' };
41
+ }
42
+ return { passed: true, evidence: 'Switch uses correct "disabled" param' };
43
+ }
44
+ },
45
+ ];
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Text validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'text-no-fontSize',
8
+ category: 'Text',
9
+ name: 'Text uses "style" (not "fontSize")',
10
+ severity: 'warning',
11
+ applies: (content) => /\bText\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
12
+ validate(content) {
13
+ if (/Text\s*\([^)]*\bfontSize\s*=/s.test(content)) {
14
+ return { passed: false, evidence: 'Text has no "fontSize" — use "style = Typography.xxx" instead' };
15
+ }
16
+ return { passed: true, evidence: 'Text typography usage looks correct' };
17
+ }
18
+ },
19
+ ];
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Tooltip validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'tooltip-no-text',
8
+ category: 'Tooltip',
9
+ name: 'Tooltip uses "description" (not "text")',
10
+ severity: 'error',
11
+ applies: (content) => /\bTooltip\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
12
+ validate(content) {
13
+ if (/Tooltip\s*\([^)]*\btext\s*=/s.test(content)) {
14
+ return { passed: false, evidence: 'Tooltip has no "text" — use "description" instead' };
15
+ }
16
+ return { passed: true, evidence: 'Tooltip uses correct param names' };
17
+ }
18
+ },
19
+ {
20
+ id: 'tooltip-no-position',
21
+ category: 'Tooltip',
22
+ name: 'No TooltipPosition (use TooltipPlacement)',
23
+ severity: 'error',
24
+ applies: (content) => /\bTooltip\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
25
+ validate(content) {
26
+ if (/TooltipPosition/.test(content)) {
27
+ return { passed: false, evidence: 'TooltipPosition does not exist — use TooltipPlacement (TOP/BOTTOM/LEFT/RIGHT)' };
28
+ }
29
+ return { passed: true, evidence: 'No invalid TooltipPosition usage' };
30
+ }
31
+ },
32
+ {
33
+ id: 'tooltip-requires-state',
34
+ category: 'Tooltip',
35
+ name: 'Tooltip requires TooltipState',
36
+ severity: 'error',
37
+ applies: (content) => /\bTooltip\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
38
+ validate(content) {
39
+ if (!/rememberTooltipState|TooltipState/.test(content)) {
40
+ return { passed: false, evidence: 'Tooltip requires "state: TooltipState" — use rememberTooltipState()' };
41
+ }
42
+ return { passed: true, evidence: 'TooltipState is used' };
43
+ }
44
+ },
45
+ ];
@@ -0,0 +1,22 @@
1
+ /**
2
+ * TrustBanner validation rules
3
+ */
4
+
5
+ module.exports = [
6
+ {
7
+ id: 'trustbanner-no-title',
8
+ category: 'TrustBanner',
9
+ name: 'TrustBanner has no title/description/icon',
10
+ severity: 'error',
11
+ applies: (content) => /\bTrustBanner\s*\(/.test(content) && /vn\.momo\.kits/.test(content),
12
+ validate(content) {
13
+ const wrongParams = ['title', 'description', 'icon'];
14
+ for (const param of wrongParams) {
15
+ if (new RegExp(`TrustBanner\\s*\\([^)]*\\b${param}\\s*=`, 's').test(content)) {
16
+ return { passed: false, evidence: `TrustBanner has no "${param}" — use serviceName, screenName, onPress, trackEvent` };
17
+ }
18
+ }
19
+ return { passed: true, evidence: 'TrustBanner params look correct' };
20
+ }
21
+ },
22
+ ];