pumuki 6.3.177 → 6.3.179

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/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.177
1
+ v6.3.179
@@ -7,6 +7,7 @@ import {
7
7
  findKotlinOpenClosedWhenMatch,
8
8
  findKotlinPresentationSrpMatch,
9
9
  hasKotlinGlobalScopeUsage,
10
+ hasKotlinLiveDataStateExposureUsage,
10
11
  hasKotlinRunBlockingUsage,
11
12
  hasKotlinThreadSleepCall,
12
13
  } from './android';
@@ -91,6 +92,28 @@ fun main() {
91
92
  assert.equal(hasKotlinRunBlockingUsage(partialSource), false);
92
93
  });
93
94
 
95
+ test('hasKotlinLiveDataStateExposureUsage detecta LiveData y MutableLiveData como estado observable legacy', () => {
96
+ const source = `
97
+ class OrdersViewModel : ViewModel() {
98
+ private val mutableState = MutableLiveData<OrdersUiState>()
99
+ val state: LiveData<OrdersUiState> = mutableState
100
+ }
101
+ `;
102
+ assert.equal(hasKotlinLiveDataStateExposureUsage(source), true);
103
+ });
104
+
105
+ test('hasKotlinLiveDataStateExposureUsage ignora imports, comentarios y strings', () => {
106
+ const source = `
107
+ import androidx.lifecycle.LiveData
108
+ // val state = MutableLiveData<OrdersUiState>()
109
+ val sample = "LiveData<OrdersUiState>"
110
+ class OrdersViewModel : ViewModel() {
111
+ val state: StateFlow<OrdersUiState> = MutableStateFlow(OrdersUiState())
112
+ }
113
+ `;
114
+ assert.equal(hasKotlinLiveDataStateExposureUsage(source), false);
115
+ });
116
+
94
117
  test('findKotlinPresentationSrpMatch devuelve payload semantico para SRP-Android en presentation', () => {
95
118
  const source = `
96
119
  import android.content.SharedPreferences
@@ -292,6 +292,13 @@ export const hasKotlinRunBlockingUsage = (source: string): boolean => {
292
292
  });
293
293
  };
294
294
 
295
+ export const hasKotlinLiveDataStateExposureUsage = (source: string): boolean => {
296
+ return collectKotlinRegexLines(
297
+ source,
298
+ /\b(?:MutableLiveData|LiveData)\s*(?:<|\(|\.)/
299
+ ).length > 0;
300
+ };
301
+
295
302
  export const findKotlinPresentationSrpMatch = (
296
303
  source: string
297
304
  ): KotlinPresentationSrpMatch | undefined => {
@@ -637,6 +637,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
637
637
  { platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinThreadSleepCall, ruleId: 'heuristics.android.thread-sleep.ast', code: 'HEURISTICS_ANDROID_THREAD_SLEEP_AST', message: 'AST heuristic detected Thread.sleep usage in production Kotlin code.' },
638
638
  { platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinGlobalScopeUsage, ruleId: 'heuristics.android.globalscope.ast', code: 'HEURISTICS_ANDROID_GLOBAL_SCOPE_AST', message: 'AST heuristic detected GlobalScope coroutine usage in production Kotlin code.' },
639
639
  { platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinRunBlockingUsage, ruleId: 'heuristics.android.run-blocking.ast', code: 'HEURISTICS_ANDROID_RUN_BLOCKING_AST', message: 'AST heuristic detected runBlocking usage in production Kotlin code.' },
640
+ { platform: 'android', pathCheck: isAndroidPresentationPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinLiveDataStateExposureUsage, ruleId: 'heuristics.android.flow.livedata-state-exposure.ast', code: 'HEURISTICS_ANDROID_FLOW_LIVEDATA_STATE_EXPOSURE_AST', message: 'AST heuristic detected LiveData state exposure in Android presentation code where StateFlow or SharedFlow should be preferred.' },
640
641
  ];
641
642
 
642
643
  const extractWorkflowHeuristicFacts = (
@@ -3,13 +3,14 @@ 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, 8);
6
+ assert.equal(androidRules.length, 9);
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.flow.livedata-state-exposure.ast',
13
14
  'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
14
15
  'heuristics.android.solid.ocp.discriminator-branching.ast',
15
16
  'heuristics.android.solid.dip.concrete-framework-dependency.ast',
@@ -18,6 +19,16 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
18
19
  ]);
19
20
 
20
21
  const byId = new Map(androidRules.map((rule) => [rule.id, rule]));
22
+ const coroutineRuleIds = [
23
+ 'heuristics.android.globalscope.ast',
24
+ 'heuristics.android.run-blocking.ast',
25
+ ];
26
+
27
+ for (const ruleId of coroutineRuleIds) {
28
+ assert.equal(byId.get(ruleId)?.platform, 'android');
29
+ assert.equal(byId.get(ruleId)?.locked, true);
30
+ }
31
+
21
32
  assert.equal(
22
33
  byId.get('heuristics.android.thread-sleep.ast')?.then.code,
23
34
  'HEURISTICS_ANDROID_THREAD_SLEEP_AST'
@@ -30,6 +41,10 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
30
41
  byId.get('heuristics.android.run-blocking.ast')?.then.code,
31
42
  'HEURISTICS_ANDROID_RUN_BLOCKING_AST'
32
43
  );
44
+ assert.equal(
45
+ byId.get('heuristics.android.flow.livedata-state-exposure.ast')?.then.code,
46
+ 'HEURISTICS_ANDROID_FLOW_LIVEDATA_STATE_EXPOSURE_AST'
47
+ );
33
48
  assert.equal(
34
49
  byId.get('heuristics.android.solid.srp.presentation-mixed-responsibilities.ast')?.then.code,
35
50
  'HEURISTICS_ANDROID_SOLID_SRP_PRESENTATION_MIXED_RESPONSIBILITIES_AST'
@@ -55,6 +55,26 @@ export const androidRules: RuleSet = [
55
55
  code: 'HEURISTICS_ANDROID_RUN_BLOCKING_AST',
56
56
  },
57
57
  },
58
+ {
59
+ id: 'heuristics.android.flow.livedata-state-exposure.ast',
60
+ description:
61
+ 'Detects LiveData state exposure in Android presentation code where StateFlow or SharedFlow should be preferred.',
62
+ severity: 'WARN',
63
+ platform: 'android',
64
+ locked: true,
65
+ when: {
66
+ kind: 'Heuristic',
67
+ where: {
68
+ ruleId: 'heuristics.android.flow.livedata-state-exposure.ast',
69
+ },
70
+ },
71
+ then: {
72
+ kind: 'Finding',
73
+ message:
74
+ 'AST heuristic detected LiveData state exposure in Android presentation code.',
75
+ code: 'HEURISTICS_ANDROID_FLOW_LIVEDATA_STATE_EXPOSURE_AST',
76
+ },
77
+ },
58
78
  {
59
79
  id: 'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
60
80
  description:
@@ -122,6 +122,16 @@ app/
122
122
  ✅ El baseline Android debe cubrir al menos SRP en presentation, OCP por branching de discriminadores, DIP por dependencias concretas de framework, ISP por interfaces demasiado anchas y LSP por precondiciones estrechadas.
123
123
  ✅ Estos detectores son paridad parcial Android: no sustituyen una auditoría completa de arquitectura, pero sí evitan que reglas SOLID críticas queden como doctrina declarativa sin señal ejecutable.
124
124
 
125
+ ### Enforcement AST inicial de coroutines Android
126
+ ✅ `skills.android.guideline.android.coroutines-async-await-no-callbacks` debe mapear a señales ejecutables existentes de uso inseguro de coroutines, empezando por `GlobalScope` y `runBlocking`.
127
+ ✅ El baseline inicial de coroutines es parcial: cubre APIs bloqueantes o scopes no estructurados, pero no declara todavía cobertura completa de `Flow`, `viewModelScope`, `supervisorScope`, dispatchers ni cancelación cooperativa.
128
+ ✅ Si se amplía esta cobertura, cada nueva regla debe aterrizar como detector AST/textual semántico con test dirigido antes de declararse cubierta en el registry.
129
+
130
+ ### Enforcement AST inicial de Flow/StateFlow Android
131
+ ✅ Las reglas de `StateFlow` / `StateFlow-SharedFlow` deben mapear a señales ejecutables de estado observable legacy, empezando por exposición de `LiveData` / `MutableLiveData` en presentation.
132
+ ✅ Este baseline no obliga a que todo ViewModel use Flow; solo detecta una alternativa legacy concreta cuando aparece en código de presentación Android.
133
+ ✅ `Flow`, `collectAsState`, `stateIn`, Room observable queries y SharedFlow events quedan fuera hasta que tengan detectores propios y regresiones dirigidas.
134
+
125
135
  ### Dependency Injection (Hilt):
126
136
  ✅ **Hilt** - DI framework (NO manual factories)
127
137
  ✅ **@HiltAndroidApp** - Application class
@@ -213,6 +213,25 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
213
213
  'skills.android.no-runblocking': heuristicDetector('android.run-blocking', [
214
214
  'heuristics.android.run-blocking.ast',
215
215
  ]),
216
+ 'skills.android.guideline.android.coroutines-async-await-no-callbacks': heuristicDetector(
217
+ 'android.coroutines.baseline',
218
+ [
219
+ 'heuristics.android.globalscope.ast',
220
+ 'heuristics.android.run-blocking.ast',
221
+ ]
222
+ ),
223
+ 'skills.android.guideline.android.stateflow-estado-mutable-observable': heuristicDetector(
224
+ 'android.flow.state-exposure',
225
+ ['heuristics.android.flow.livedata-state-exposure.ast']
226
+ ),
227
+ 'skills.android.guideline.android.stateflow-hot-stream-siempre-tiene-valor-para-estado': heuristicDetector(
228
+ 'android.flow.state-exposure',
229
+ ['heuristics.android.flow.livedata-state-exposure.ast']
230
+ ),
231
+ 'skills.android.guideline.android.stateflow-sharedflow-para-exponer-estado-del-viewmodel': heuristicDetector(
232
+ 'android.flow.state-exposure',
233
+ ['heuristics.android.flow.livedata-state-exposure.ast']
234
+ ),
216
235
  'skills.android.no-solid-violations': heuristicDetector('android.solid', [
217
236
  'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
218
237
  'heuristics.android.solid.ocp.discriminator-branching.ast',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.177",
3
+ "version": "6.3.179",
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": {