pumuki 6.3.128 → 6.3.129

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.
@@ -3,13 +3,24 @@ 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, 3);
6
+ assert.equal(androidRules.length, 14);
7
7
 
8
8
  const ids = androidRules.map((rule) => rule.id);
9
9
  assert.deepEqual(ids, [
10
10
  'heuristics.android.thread-sleep.ast',
11
11
  'heuristics.android.globalscope.ast',
12
12
  'heuristics.android.run-blocking.ast',
13
+ 'heuristics.android.force-unwrap.ast',
14
+ 'heuristics.android.java-source.ast',
15
+ 'heuristics.android.asynctask-deprecated.ast',
16
+ 'heuristics.android.findviewbyid.ast',
17
+ 'heuristics.android.rxjava-new-code.ast',
18
+ 'heuristics.android.dispatchers-main-ui-io-network-disk-default-cpu.ast',
19
+ 'heuristics.android.withcontext-change-dispatcher.ast',
20
+ 'heuristics.android.no-console-log.ast',
21
+ 'heuristics.android.hardcoded-strings.ast',
22
+ 'heuristics.android.no-singleton.ast',
23
+ 'heuristics.android.try-catch-manejo-de-errores-en-coroutines.ast',
13
24
  ]);
14
25
 
15
26
  const byId = new Map(androidRules.map((rule) => [rule.id, rule]));
@@ -25,6 +36,50 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
25
36
  byId.get('heuristics.android.run-blocking.ast')?.then.code,
26
37
  'HEURISTICS_ANDROID_RUN_BLOCKING_AST'
27
38
  );
39
+ assert.equal(
40
+ byId.get('heuristics.android.force-unwrap.ast')?.then.code,
41
+ 'HEURISTICS_ANDROID_FORCE_UNWRAP_AST'
42
+ );
43
+ assert.equal(
44
+ byId.get('heuristics.android.java-source.ast')?.then.code,
45
+ 'HEURISTICS_ANDROID_JAVA_SOURCE_AST'
46
+ );
47
+ assert.equal(
48
+ byId.get('heuristics.android.asynctask-deprecated.ast')?.then.code,
49
+ 'HEURISTICS_ANDROID_ASYNCTASK_DEPRECATED_AST'
50
+ );
51
+ assert.equal(
52
+ byId.get('heuristics.android.findviewbyid.ast')?.then.code,
53
+ 'HEURISTICS_ANDROID_FINDVIEWBYID_AST'
54
+ );
55
+ assert.equal(
56
+ byId.get('heuristics.android.rxjava-new-code.ast')?.then.code,
57
+ 'HEURISTICS_ANDROID_RXJAVA_NEW_CODE_AST'
58
+ );
59
+ assert.equal(
60
+ byId.get('heuristics.android.dispatchers-main-ui-io-network-disk-default-cpu.ast')?.then.code,
61
+ 'HEURISTICS_ANDROID_DISPATCHERS_MAIN_UI_IO_NETWORK_DISK_DEFAULT_CPU_AST'
62
+ );
63
+ assert.equal(
64
+ byId.get('heuristics.android.withcontext-change-dispatcher.ast')?.then.code,
65
+ 'HEURISTICS_ANDROID_WITHCONTEXT_CHANGE_DISPATCHER_AST'
66
+ );
67
+ assert.equal(
68
+ byId.get('heuristics.android.no-console-log.ast')?.then.code,
69
+ 'HEURISTICS_ANDROID_NO_CONSOLE_LOG_AST'
70
+ );
71
+ assert.equal(
72
+ byId.get('heuristics.android.hardcoded-strings.ast')?.then.code,
73
+ 'HEURISTICS_ANDROID_HARDCODED_STRINGS_AST'
74
+ );
75
+ assert.equal(
76
+ byId.get('heuristics.android.no-singleton.ast')?.then.code,
77
+ 'HEURISTICS_ANDROID_NO_SINGLETON_AST'
78
+ );
79
+ assert.equal(
80
+ byId.get('heuristics.android.try-catch-manejo-de-errores-en-coroutines.ast')?.then.code,
81
+ 'HEURISTICS_ANDROID_TRY_CATCH_MANEJO_DE_ERRORES_EN_COROUTINES_AST'
82
+ );
28
83
 
29
84
  for (const rule of androidRules) {
30
85
  assert.equal(rule.platform, 'android');
@@ -55,4 +55,204 @@ export const androidRules: RuleSet = [
55
55
  code: 'HEURISTICS_ANDROID_RUN_BLOCKING_AST',
56
56
  },
57
57
  },
58
+ {
59
+ id: 'heuristics.android.force-unwrap.ast',
60
+ description: 'Detects Kotlin force unwrap (!!) usage in Android production files.',
61
+ severity: 'WARN',
62
+ platform: 'android',
63
+ locked: true,
64
+ when: {
65
+ kind: 'Heuristic',
66
+ where: {
67
+ ruleId: 'heuristics.android.force-unwrap.ast',
68
+ },
69
+ },
70
+ then: {
71
+ kind: 'Finding',
72
+ message: 'AST heuristic detected Kotlin force unwrap (!!) usage in production code.',
73
+ code: 'HEURISTICS_ANDROID_FORCE_UNWRAP_AST',
74
+ },
75
+ },
76
+ {
77
+ id: 'heuristics.android.java-source.ast',
78
+ description: 'Detects Java source in Android production code where Kotlin is required.',
79
+ severity: 'WARN',
80
+ platform: 'android',
81
+ locked: true,
82
+ when: {
83
+ kind: 'Heuristic',
84
+ where: {
85
+ ruleId: 'heuristics.android.java-source.ast',
86
+ },
87
+ },
88
+ then: {
89
+ kind: 'Finding',
90
+ message: 'AST heuristic detected Java source in Android production code where Kotlin is required for new code.',
91
+ code: 'HEURISTICS_ANDROID_JAVA_SOURCE_AST',
92
+ },
93
+ },
94
+ {
95
+ id: 'heuristics.android.asynctask-deprecated.ast',
96
+ description: 'Detects AsyncTask usage in Android production code where Coroutines are required.',
97
+ severity: 'WARN',
98
+ platform: 'android',
99
+ locked: true,
100
+ when: {
101
+ kind: 'Heuristic',
102
+ where: {
103
+ ruleId: 'heuristics.android.asynctask-deprecated.ast',
104
+ },
105
+ },
106
+ then: {
107
+ kind: 'Finding',
108
+ message: 'AST heuristic detected AsyncTask usage in Android production code where Coroutines are required.',
109
+ code: 'HEURISTICS_ANDROID_ASYNCTASK_DEPRECATED_AST',
110
+ },
111
+ },
112
+ {
113
+ id: 'heuristics.android.findviewbyid.ast',
114
+ description: 'Detects findViewById usage in Android production code where View Binding or Compose is required.',
115
+ severity: 'WARN',
116
+ platform: 'android',
117
+ locked: true,
118
+ when: {
119
+ kind: 'Heuristic',
120
+ where: {
121
+ ruleId: 'heuristics.android.findviewbyid.ast',
122
+ },
123
+ },
124
+ then: {
125
+ kind: 'Finding',
126
+ message: 'AST heuristic detected findViewById usage in Android production code where View Binding or Compose is required.',
127
+ code: 'HEURISTICS_ANDROID_FINDVIEWBYID_AST',
128
+ },
129
+ },
130
+ {
131
+ id: 'heuristics.android.rxjava-new-code.ast',
132
+ description: 'Detects RxJava usage in Android production code where Flow is required for new code.',
133
+ severity: 'WARN',
134
+ platform: 'android',
135
+ locked: true,
136
+ when: {
137
+ kind: 'Heuristic',
138
+ where: {
139
+ ruleId: 'heuristics.android.rxjava-new-code.ast',
140
+ },
141
+ },
142
+ then: {
143
+ kind: 'Finding',
144
+ message: 'AST heuristic detected RxJava usage in Android production code where Flow is required for new code.',
145
+ code: 'HEURISTICS_ANDROID_RXJAVA_NEW_CODE_AST',
146
+ },
147
+ },
148
+ {
149
+ id: 'heuristics.android.dispatchers-main-ui-io-network-disk-default-cpu.ast',
150
+ description:
151
+ 'Detects explicit Dispatchers.Main/IO/Default usage in Android production code where dispatcher selection must remain intentional.',
152
+ severity: 'WARN',
153
+ platform: 'android',
154
+ locked: true,
155
+ when: {
156
+ kind: 'Heuristic',
157
+ where: {
158
+ ruleId: 'heuristics.android.dispatchers-main-ui-io-network-disk-default-cpu.ast',
159
+ },
160
+ },
161
+ then: {
162
+ kind: 'Finding',
163
+ message:
164
+ 'AST heuristic detected explicit Dispatchers.Main/IO/Default usage in Android production code where dispatcher selection must remain intentional.',
165
+ code: 'HEURISTICS_ANDROID_DISPATCHERS_MAIN_UI_IO_NETWORK_DISK_DEFAULT_CPU_AST',
166
+ },
167
+ },
168
+ {
169
+ id: 'heuristics.android.withcontext-change-dispatcher.ast',
170
+ description: 'Detects withContext usage in Android production code where dispatcher switching is intentional.',
171
+ severity: 'WARN',
172
+ platform: 'android',
173
+ locked: true,
174
+ when: {
175
+ kind: 'Heuristic',
176
+ where: {
177
+ ruleId: 'heuristics.android.withcontext-change-dispatcher.ast',
178
+ },
179
+ },
180
+ then: {
181
+ kind: 'Finding',
182
+ message: 'AST heuristic detected withContext usage in Android production code where dispatcher switching is intentional.',
183
+ code: 'HEURISTICS_ANDROID_WITHCONTEXT_CHANGE_DISPATCHER_AST',
184
+ },
185
+ },
186
+ {
187
+ id: 'heuristics.android.no-console-log.ast',
188
+ description: 'Detects Android logging usage in production code without a debug-only guard.',
189
+ severity: 'WARN',
190
+ platform: 'android',
191
+ locked: true,
192
+ when: {
193
+ kind: 'Heuristic',
194
+ where: {
195
+ ruleId: 'heuristics.android.no-console-log.ast',
196
+ },
197
+ },
198
+ then: {
199
+ kind: 'Finding',
200
+ message: 'AST heuristic detected Android logging usage in production code without a debug-only guard.',
201
+ code: 'HEURISTICS_ANDROID_NO_CONSOLE_LOG_AST',
202
+ },
203
+ },
204
+ {
205
+ id: 'heuristics.android.hardcoded-strings.ast',
206
+ description: 'Detects hardcoded string literal usage in Android production code where strings.xml should be used.',
207
+ severity: 'WARN',
208
+ platform: 'android',
209
+ locked: true,
210
+ when: {
211
+ kind: 'Heuristic',
212
+ where: {
213
+ ruleId: 'heuristics.android.hardcoded-strings.ast',
214
+ },
215
+ },
216
+ then: {
217
+ kind: 'Finding',
218
+ message: 'AST heuristic detected hardcoded string literal usage in Android production code where strings.xml should be used.',
219
+ code: 'HEURISTICS_ANDROID_HARDCODED_STRINGS_AST',
220
+ },
221
+ },
222
+ {
223
+ id: 'heuristics.android.no-singleton.ast',
224
+ description: 'Detects Kotlin singleton object or companion singleton holder usage in Android production code where Hilt or Dagger DI should be used.',
225
+ severity: 'WARN',
226
+ platform: 'android',
227
+ locked: true,
228
+ when: {
229
+ kind: 'Heuristic',
230
+ where: {
231
+ ruleId: 'heuristics.android.no-singleton.ast',
232
+ },
233
+ },
234
+ then: {
235
+ kind: 'Finding',
236
+ message: 'AST heuristic detected Kotlin singleton object or companion singleton holder usage in Android production code where Hilt or Dagger DI should be used.',
237
+ code: 'HEURISTICS_ANDROID_NO_SINGLETON_AST',
238
+ },
239
+ },
240
+ {
241
+ id: 'heuristics.android.try-catch-manejo-de-errores-en-coroutines.ast',
242
+ description: 'Detects try/catch usage in Android coroutine code where error handling must remain explicit.',
243
+ severity: 'WARN',
244
+ platform: 'android',
245
+ locked: true,
246
+ when: {
247
+ kind: 'Heuristic',
248
+ where: {
249
+ ruleId: 'heuristics.android.try-catch-manejo-de-errores-en-coroutines.ast',
250
+ },
251
+ },
252
+ then: {
253
+ kind: 'Finding',
254
+ message: 'AST heuristic detected try/catch usage in Android coroutine code where error handling must remain explicit.',
255
+ code: 'HEURISTICS_ANDROID_TRY_CATCH_MANEJO_DE_ERRORES_EN_COROUTINES_AST',
256
+ },
257
+ },
58
258
  ];
@@ -4,6 +4,12 @@ This file tracks the active deterministic framework line used in this repository
4
4
  Canonical release chronology lives in `CHANGELOG.md`.
5
5
  This file keeps only the operational highlights and rollout notes that matter while running the framework.
6
6
 
7
+ ### 2026-04-29 (v6.3.129)
8
+
9
+ - **Singletons Android con detector AST real:** `skills.android.no-singleton-usar-inyeccio-n-de-dependencias-hilt-dagger` queda vinculada a un detector que distingue singleton real de `@Module` / `@InstallIn` / `@EntryPoint`.
10
+ - **Regresión cerrada en la suite Android:** el caso de objetos anónimos y companion objects inocuos queda cubierto y la compilación del lock vuelve a producir el binding canónico.
11
+ - **Rollout recomendado:** publicar `pumuki@6.3.129`, repin inmediato en `RuralGo` y revalidar `status` / `doctor` / `audit --stage=PRE_WRITE --json` sobre el consumer.
12
+
7
13
  ## 2026-04 (CLI stability and macOS notifications)
8
14
 
9
15
  ### 2026-04-25 (v6.3.116)
@@ -66,6 +66,14 @@ Current enforcement scope:
66
66
  - curated template compilation (`skills.sources.json` -> `skills.lock.json`)
67
67
  - stage-aware policy resolution via `resolvePolicyForStage`
68
68
  - additive skills-derived rules merged through the shared gate runner
69
+ - strict skills enforcement by default; `PUMUKI_SKILLS_ENFORCEMENT=advisory`
70
+ is an explicit opt-in escape hatch, not the product baseline
71
+ - strict heuristics, TDD/BDD evidence, SDD completeness, and Git atomicity by
72
+ default; their `advisory`/`0` env values are explicit legacy overrides, not
73
+ the product baseline
74
+ - declarative skill rules that represent auditable norms must be mapped to an
75
+ intelligent AST detector; missing detector bindings are reported as
76
+ `unsupported_detector_rule_ids` and block strict gate execution
69
77
  - evidence traceability for active skills bundles and policy source/hash in `.ai_evidence.json`
70
78
 
71
79
  Ownership model:
@@ -67,3 +67,17 @@ test('skillsCompilerTemplates mantiene bundles iOS enterprise esperados', () =>
67
67
  assert.equal(bundle.rules.every((rule) => rule.platform === 'ios'), true);
68
68
  }
69
69
  });
70
+
71
+ test('skillsCompilerTemplates mantiene el bundle Android baseline esperado', () => {
72
+ const bundle = skillsCompilerTemplates['android-guidelines'];
73
+ assert.ok(bundle, 'Missing Android bundle: android-guidelines');
74
+ assert.deepEqual(bundle.rules.map((rule) => rule.id), [
75
+ 'skills.android.no-thread-sleep',
76
+ 'skills.android.no-globalscope',
77
+ 'skills.android.no-console-log',
78
+ 'skills.android.hardcoded-strings-usar-strings-xml',
79
+ 'skills.android.no-singleton-usar-inyeccio-n-de-dependencias-hilt-dagger',
80
+ 'skills.android.no-runblocking',
81
+ ]);
82
+ assert.equal(bundle.rules.every((rule) => rule.platform === 'android'), true);
83
+ });
@@ -565,6 +565,33 @@ export const skillsCompilerTemplates: Record<string, SkillsCompilerTemplate> = {
565
565
  stage: 'PRE_PUSH',
566
566
  locked: true,
567
567
  },
568
+ {
569
+ id: 'skills.android.no-console-log',
570
+ description: 'Disallow logs in Android production code.',
571
+ severity: 'ERROR',
572
+ platform: 'android',
573
+ confidence: 'HIGH',
574
+ stage: 'PRE_PUSH',
575
+ locked: true,
576
+ },
577
+ {
578
+ id: 'skills.android.hardcoded-strings-usar-strings-xml',
579
+ description: 'Disallow hardcoded strings in Android production code.',
580
+ severity: 'ERROR',
581
+ platform: 'android',
582
+ confidence: 'HIGH',
583
+ stage: 'PRE_PUSH',
584
+ locked: true,
585
+ },
586
+ {
587
+ id: 'skills.android.no-singleton-usar-inyeccio-n-de-dependencias-hilt-dagger',
588
+ description: 'Disallow singleton object patterns in Android production code; use Hilt or Dagger DI.',
589
+ severity: 'ERROR',
590
+ platform: 'android',
591
+ confidence: 'HIGH',
592
+ stage: 'PRE_PUSH',
593
+ locked: true,
594
+ },
568
595
  {
569
596
  id: 'skills.android.no-runblocking',
570
597
  description: 'Disallow runBlocking in Android production code.',
@@ -210,6 +210,42 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
210
210
  'skills.android.no-runblocking': heuristicDetector('android.run-blocking', [
211
211
  'heuristics.android.run-blocking.ast',
212
212
  ]),
213
+ 'skills.android.no-force-unwrap': heuristicDetector('android.force-unwrap', [
214
+ 'heuristics.android.force-unwrap.ast',
215
+ ]),
216
+ 'skills.android.no-java-new-code': heuristicDetector('android.java-source', [
217
+ 'heuristics.android.java-source.ast',
218
+ ]),
219
+ 'skills.android.asynctask-deprecated-usar-coroutines': heuristicDetector('android.asynctask', [
220
+ 'heuristics.android.asynctask-deprecated.ast',
221
+ ]),
222
+ 'skills.android.findviewbyid-view-binding-o-compose': heuristicDetector('android.findviewbyid', [
223
+ 'heuristics.android.findviewbyid.ast',
224
+ ]),
225
+ 'skills.android.rxjava-new-code': heuristicDetector('android.rxjava-new-code', [
226
+ 'heuristics.android.rxjava-new-code.ast',
227
+ ]),
228
+ 'skills.android.dispatchers-main-ui-io-network-disk-default-cpu': heuristicDetector(
229
+ 'android.dispatchers-main-ui-io-network-disk-default-cpu',
230
+ ['heuristics.android.dispatchers-main-ui-io-network-disk-default-cpu.ast']
231
+ ),
232
+ 'skills.android.withcontext-change-dispatcher': heuristicDetector('android.withcontext-change-dispatcher', [
233
+ 'heuristics.android.withcontext-change-dispatcher.ast',
234
+ ]),
235
+ 'skills.android.no-console-log': heuristicDetector('android.no-console-log', [
236
+ 'heuristics.android.no-console-log.ast',
237
+ ]),
238
+ 'skills.android.hardcoded-strings-usar-strings-xml': heuristicDetector('android.hardcoded-strings', [
239
+ 'heuristics.android.hardcoded-strings.ast',
240
+ ]),
241
+ 'skills.android.no-singleton-usar-inyeccio-n-de-dependencias-hilt-dagger': heuristicDetector(
242
+ 'android.no-singleton',
243
+ ['heuristics.android.no-singleton.ast']
244
+ ),
245
+ 'skills.android.try-catch-manejo-de-errores-en-coroutines': heuristicDetector(
246
+ 'android.try-catch-manejo-de-errores-en-coroutines',
247
+ ['heuristics.android.try-catch-manejo-de-errores-en-coroutines.ast']
248
+ ),
213
249
  };
214
250
 
215
251
  export const listSkillsDetectorBindings = (): ReadonlyArray<{
@@ -192,7 +192,12 @@ const normalizeKnownRuleTarget = (
192
192
  const includes = (needle: string): boolean => normalizedDescription.includes(needle);
193
193
 
194
194
  if (platform === 'ios') {
195
- if (includes('force unwrap')) {
195
+ if (
196
+ includes('force unwrap') ||
197
+ includes('force unwrapping') ||
198
+ includes('no force unwrap') ||
199
+ includes('no force unwrapping')
200
+ ) {
196
201
  return 'skills.ios.no-force-unwrap';
197
202
  }
198
203
  if (includes('force try')) {
@@ -354,8 +359,10 @@ const normalizeKnownRuleTarget = (
354
359
  includes('prefer import testing') ||
355
360
  includes('prefer test functions over test methods') ||
356
361
  includes('test functions over test methods') ||
362
+ includes('new xctest only unit tests') ||
357
363
  includes('xctest-only unit tests') ||
358
364
  includes('new xctest-only unit tests') ||
365
+ includes('xctest solo para proyectos legacy') ||
359
366
  includes('xctest only for ui') ||
360
367
  includes('xctest only for ui performance')
361
368
  ) {
@@ -426,12 +433,111 @@ const normalizeKnownRuleTarget = (
426
433
  }
427
434
 
428
435
  if (platform === 'android') {
436
+ if (
437
+ includes('asynctask') ||
438
+ includes('deprecated, usar coroutines') ||
439
+ includes('usar coroutines')
440
+ ) {
441
+ return 'skills.android.asynctask-deprecated-usar-coroutines';
442
+ }
443
+ if (
444
+ includes('java en codigo nuevo') ||
445
+ includes('java en co digo nuevo') ||
446
+ includes('no java en codigo nuevo') ||
447
+ includes('no java en co digo nuevo') ||
448
+ includes('solo kotlin') ||
449
+ includes('kotlin para todo')
450
+ ) {
451
+ return 'skills.android.no-java-new-code';
452
+ }
453
+ if (
454
+ includes('rxjava') ||
455
+ includes('usar flow') ||
456
+ includes('usar flows') ||
457
+ includes('flow en nuevo codigo') ||
458
+ includes('flow en nuevo co digo')
459
+ ) {
460
+ return 'skills.android.rxjava-new-code';
461
+ }
462
+ if (
463
+ includes('dispatchers') ||
464
+ includes('dispatcher') ||
465
+ includes('main ui') ||
466
+ includes('io network') ||
467
+ includes('default cpu') ||
468
+ includes('withcontext')
469
+ ) {
470
+ if (includes('withcontext') || includes('cambiar dispatcher')) {
471
+ return 'skills.android.withcontext-change-dispatcher';
472
+ }
473
+ return 'skills.android.dispatchers-main-ui-io-network-disk-default-cpu';
474
+ }
475
+ if (
476
+ includes('try-catch') ||
477
+ includes('manejo de errores en coroutines') ||
478
+ includes('errores en coroutines') ||
479
+ includes('error handling in coroutines')
480
+ ) {
481
+ return 'skills.android.try-catch-manejo-de-errores-en-coroutines';
482
+ }
483
+ if (
484
+ includes('findviewbyid') ||
485
+ includes('view binding') ||
486
+ includes('viewbinding') ||
487
+ includes('compose') ||
488
+ includes('no xml layouts') ||
489
+ includes('xml layouts')
490
+ ) {
491
+ return 'skills.android.findviewbyid-view-binding-o-compose';
492
+ }
493
+ if (
494
+ includes('force unwrap') ||
495
+ includes('force unwrapping') ||
496
+ includes('no force unwrap') ||
497
+ includes('no force unwrapping')
498
+ ) {
499
+ return 'skills.android.no-force-unwrap';
500
+ }
429
501
  if (includes('thread sleep') || includes('thread.sleep')) {
430
502
  return 'skills.android.no-thread-sleep';
431
503
  }
432
504
  if (includes('globalscope') || includes('global scope')) {
433
505
  return 'skills.android.no-globalscope';
434
506
  }
507
+ if (
508
+ includes('console log') ||
509
+ includes('console.log') ||
510
+ includes('logs ad-hoc') ||
511
+ includes('no logs en produccion') ||
512
+ includes('no logs en producción') ||
513
+ includes('no logs en produccio n') ||
514
+ includes('timber.d') ||
515
+ includes('buildconfig.debug') ||
516
+ includes('android.util.log')
517
+ ) {
518
+ return 'skills.android.no-console-log';
519
+ }
520
+ if (
521
+ includes('no singleton') ||
522
+ includes('singleton') ||
523
+ includes('singletons everywhere') ||
524
+ includes('hilt dagger') ||
525
+ includes('hilt/dagger') ||
526
+ includes('inyeccion de dependencias') ||
527
+ includes('inyecion de dependencias') ||
528
+ includes('usar inyeccion de dependencias') ||
529
+ includes('usar inyecion de dependencias')
530
+ ) {
531
+ return 'skills.android.no-singleton-usar-inyeccio-n-de-dependencias-hilt-dagger';
532
+ }
533
+ if (
534
+ includes('hardcoded strings') ||
535
+ includes('hardcoded string') ||
536
+ includes('usar strings.xml') ||
537
+ includes('usar strings xml')
538
+ ) {
539
+ return 'skills.android.hardcoded-strings-usar-strings-xml';
540
+ }
435
541
  if (includes('runblocking') || includes('run blocking')) {
436
542
  return 'skills.android.no-runblocking';
437
543
  }
@@ -440,22 +546,86 @@ const normalizeKnownRuleTarget = (
440
546
 
441
547
  if (platform === 'backend' || platform === 'frontend') {
442
548
  const prefix = platform === 'backend' ? 'skills.backend' : 'skills.frontend';
443
- if (includes('solid') || includes('single responsibility') || includes('srp')) {
549
+ if (
550
+ includes('solid') ||
551
+ includes('single responsibility') ||
552
+ includes('srp') ||
553
+ includes('controllers delgados') ||
554
+ includes('logic in controllers') ||
555
+ includes('lo gica en controllers') ||
556
+ includes('lógica en controllers') ||
557
+ includes('mover a servicios') ||
558
+ includes('componentes pequeños') ||
559
+ includes('componentes pequen os') ||
560
+ includes('custom hooks para logica reutilizable') ||
561
+ includes('custom hooks para lógica reutilizable') ||
562
+ includes('prop drilling excesivo') ||
563
+ includes('anemic domain models') ||
564
+ includes('entidades solo con getters') ||
565
+ includes('no logica de negocio en repositorios') ||
566
+ includes('no lógica de negocio en repositorios')
567
+ ) {
444
568
  return `${prefix}.no-solid-violations`;
445
569
  }
446
- if (includes('clean architecture')) {
570
+ if (
571
+ includes('clean architecture') ||
572
+ includes('clean code') ||
573
+ includes('clean por feature') ||
574
+ includes('domain application infrastructure presentation') ||
575
+ includes('presentation application domain infrastructure') ||
576
+ includes('dependencias hacia adentro') ||
577
+ includes('capas presentacion') ||
578
+ includes('capas presentación') ||
579
+ includes('infrastructure domain') ||
580
+ includes('feature first') ||
581
+ includes('bounded context') ||
582
+ includes('shared kernel')
583
+ ) {
447
584
  return `${prefix}.enforce-clean-architecture`;
448
585
  }
449
- if (includes('god classes') || includes('god class')) {
586
+ if (
587
+ includes('god classes') ||
588
+ includes('god class') ||
589
+ includes('servicios que mezclan responsabilidades') ||
590
+ includes('mezclan responsabilidades') ||
591
+ includes('modulos cohesivos') ||
592
+ includes('módulos cohesivos') ||
593
+ includes('mo dulos cohesivos') ||
594
+ includes('un modulo por feature') ||
595
+ includes('un módulo por feature') ||
596
+ includes('un mo dulo por feature')
597
+ ) {
450
598
  return `${prefix}.no-god-classes`;
451
599
  }
452
- if (includes('empty catch')) {
600
+ if (
601
+ includes('empty catch') ||
602
+ includes('catch vacios') ||
603
+ includes('catch vacíos') ||
604
+ includes('try-catch silenciosos') ||
605
+ includes('silenciar errores') ||
606
+ includes('siempre loggear o propagar')
607
+ ) {
453
608
  return `${prefix}.no-empty-catch`;
454
609
  }
455
- if (includes('console log') || includes('console.log')) {
610
+ if (
611
+ includes('console log') ||
612
+ includes('console.log') ||
613
+ includes('logs ad-hoc') ||
614
+ includes('no logs en produccion') ||
615
+ includes('no logs en producción') ||
616
+ includes('no logs en produccio n')
617
+ ) {
456
618
  return `${prefix}.no-console-log`;
457
619
  }
458
- if (includes('explicit any') || includes(' no any') || includes('avoid any')) {
620
+ if (
621
+ includes('explicit any') ||
622
+ includes(' no any') ||
623
+ includes('avoid any') ||
624
+ includes('usar unknown') ||
625
+ includes('type guard') ||
626
+ includes('tipos explicitos') ||
627
+ includes('tipos explícitos')
628
+ ) {
459
629
  return `${prefix}.avoid-explicit-any`;
460
630
  }
461
631
  return null;
@@ -21,6 +21,7 @@ export type SkillsRuleSetLoadResult = {
21
21
  mappedHeuristicRuleIds: ReadonlySet<string>;
22
22
  requiresHeuristicFacts: boolean;
23
23
  unsupportedAutoRuleIds?: ReadonlyArray<string>;
24
+ unsupportedDetectorRuleIds?: ReadonlyArray<string>;
24
25
  registryCoverage?: {
25
26
  contract: 'AUTO_RUNTIME_RULES_FOR_STAGE';
26
27
  stage: Exclude<GateStage, 'STAGED'>;
@@ -502,6 +503,7 @@ const emptyResult = (): SkillsRuleSetLoadResult => {
502
503
  mappedHeuristicRuleIds: new Set<string>(),
503
504
  requiresHeuristicFacts: false,
504
505
  unsupportedAutoRuleIds: [],
506
+ unsupportedDetectorRuleIds: [],
505
507
  };
506
508
  };
507
509
 
@@ -534,6 +536,7 @@ export const loadSkillsRuleSetForStage = (
534
536
  const rulesById = new Map<string, RuleDefinition>();
535
537
  const mappedHeuristicRuleIds = new Set<string>();
536
538
  const unsupportedAutoRuleIds = new Set<string>();
539
+ const unsupportedDetectorRuleIds = new Set<string>();
537
540
  const registryRuleIds = new Set<string>();
538
541
  const registryAutoRuleIds = new Set<string>();
539
542
  const registryDeclarativeRuleIds = new Set<string>();
@@ -565,11 +568,13 @@ export const loadSkillsRuleSetForStage = (
565
568
  continue;
566
569
  }
567
570
  if (evaluationMode !== 'AUTO') {
571
+ unsupportedDetectorRuleIds.add(compiledRule.id);
568
572
  continue;
569
573
  }
570
574
  stageApplicableAutoRuleIds.add(compiledRule.id);
571
575
  if (evaluationMode === 'AUTO' && mappedRuleIds.length === 0) {
572
576
  unsupportedAutoRuleIds.add(compiledRule.id);
577
+ unsupportedDetectorRuleIds.add(compiledRule.id);
573
578
  continue;
574
579
  }
575
580
 
@@ -602,6 +607,7 @@ export const loadSkillsRuleSetForStage = (
602
607
  mappedHeuristicRuleIds,
603
608
  requiresHeuristicFacts: mappedHeuristicRuleIds.size > 0,
604
609
  unsupportedAutoRuleIds: [...unsupportedAutoRuleIds].sort(),
610
+ unsupportedDetectorRuleIds: [...unsupportedDetectorRuleIds].sort(),
605
611
  registryCoverage: {
606
612
  contract: 'AUTO_RUNTIME_RULES_FOR_STAGE',
607
613
  stage,