pumuki 6.3.187 → 6.3.189
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 +1 -1
- package/core/facts/detectors/text/android.test.ts +68 -0
- package/core/facts/detectors/text/android.ts +15 -0
- package/core/facts/extractHeuristicFacts.ts +6 -0
- package/core/rules/presets/heuristics/android.test.ts +11 -1
- package/core/rules/presets/heuristics/android.ts +40 -0
- package/docs/codex-skills/android-enterprise-rules.md +9 -0
- package/integrations/config/skillsDetectorRegistry.ts +8 -0
- package/package.json +1 -1
- package/skills.lock.json +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v6.3.
|
|
1
|
+
v6.3.189
|
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
hasKotlinDispatcherMainBoundaryLeakUsage,
|
|
11
11
|
hasKotlinGlobalScopeUsage,
|
|
12
12
|
hasKotlinHardcodedBackgroundDispatcherUsage,
|
|
13
|
+
hasKotlinJUnit4Usage,
|
|
13
14
|
hasKotlinLiveDataStateExposureUsage,
|
|
14
15
|
hasKotlinLifecycleScopeUsage,
|
|
16
|
+
hasKotlinSharedPreferencesUsage,
|
|
15
17
|
hasKotlinWithContextUsage,
|
|
16
18
|
hasKotlinManualCoroutineScopeInViewModelUsage,
|
|
17
19
|
hasKotlinRunBlockingUsage,
|
|
@@ -247,6 +249,72 @@ class SyncOrdersUseCase {
|
|
|
247
249
|
assert.equal(hasKotlinLifecycleScopeUsage(source), false);
|
|
248
250
|
});
|
|
249
251
|
|
|
252
|
+
test('hasKotlinSharedPreferencesUsage detecta SharedPreferences y getSharedPreferences', () => {
|
|
253
|
+
const typeSource = `
|
|
254
|
+
class PreferencesStore(private val preferences: SharedPreferences) {
|
|
255
|
+
fun read() = preferences.getString("token", null)
|
|
256
|
+
}
|
|
257
|
+
`;
|
|
258
|
+
const callSource = `
|
|
259
|
+
class PreferencesStore(private val context: Context) {
|
|
260
|
+
fun read() = context.getSharedPreferences("user", Context.MODE_PRIVATE)
|
|
261
|
+
}
|
|
262
|
+
`;
|
|
263
|
+
assert.equal(hasKotlinSharedPreferencesUsage(typeSource), true);
|
|
264
|
+
assert.equal(hasKotlinSharedPreferencesUsage(callSource), true);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('hasKotlinSharedPreferencesUsage ignora imports, comentarios, strings y nombres parciales', () => {
|
|
268
|
+
const source = `
|
|
269
|
+
import android.content.SharedPreferences
|
|
270
|
+
// val preferences: SharedPreferences = legacy()
|
|
271
|
+
val sample = "getSharedPreferences(\"user\")"
|
|
272
|
+
class PreferencesStore {
|
|
273
|
+
fun read() = customSharedPreferencesProvider()
|
|
274
|
+
}
|
|
275
|
+
`;
|
|
276
|
+
assert.equal(hasKotlinSharedPreferencesUsage(source), false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('hasKotlinJUnit4Usage detecta imports y anotaciones JUnit4 en tests Kotlin Android', () => {
|
|
280
|
+
const importSource = `
|
|
281
|
+
import org.junit.Test
|
|
282
|
+
import org.junit.Assert
|
|
283
|
+
|
|
284
|
+
class OrdersViewModelTest {
|
|
285
|
+
@Test
|
|
286
|
+
fun loadsOrders() {
|
|
287
|
+
Assert.assertEquals(1, 1)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
`;
|
|
291
|
+
const runnerSource = `
|
|
292
|
+
import org.junit.runner.RunWith
|
|
293
|
+
|
|
294
|
+
@RunWith(AndroidJUnit4::class)
|
|
295
|
+
class OrdersInstrumentedTest
|
|
296
|
+
`;
|
|
297
|
+
|
|
298
|
+
assert.equal(hasKotlinJUnit4Usage(importSource), true);
|
|
299
|
+
assert.equal(hasKotlinJUnit4Usage(runnerSource), true);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('hasKotlinJUnit4Usage ignora JUnit5, comentarios y strings', () => {
|
|
303
|
+
const source = `
|
|
304
|
+
import org.junit.jupiter.api.Test
|
|
305
|
+
// import org.junit.Test
|
|
306
|
+
val sample = "Assert.assertEquals(1, 1)"
|
|
307
|
+
|
|
308
|
+
class OrdersViewModelTest {
|
|
309
|
+
@Test
|
|
310
|
+
fun loadsOrders() {
|
|
311
|
+
assertEquals(1, 1)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
`;
|
|
315
|
+
assert.equal(hasKotlinJUnit4Usage(source), false);
|
|
316
|
+
});
|
|
317
|
+
|
|
250
318
|
test('hasKotlinSupervisorScopeUsage detecta supervisorScope con parentesis y llaves', () => {
|
|
251
319
|
const parenthesesSource = `
|
|
252
320
|
class SyncOrdersUseCase {
|
|
@@ -267,6 +267,21 @@ export const hasKotlinLifecycleScopeUsage = (source: string): boolean => {
|
|
|
267
267
|
return collectKotlinRegexLines(source, /\blifecycleScope\s*\./).length > 0;
|
|
268
268
|
};
|
|
269
269
|
|
|
270
|
+
export const hasKotlinSharedPreferencesUsage = (source: string): boolean => {
|
|
271
|
+
return collectKotlinRegexLines(source, /\b(?:SharedPreferences|PreferenceManager\s*\.|getSharedPreferences\s*\()/).length > 0;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export const hasKotlinJUnit4Usage = (source: string): boolean => {
|
|
275
|
+
return source.split(/\r?\n/).some((line) => {
|
|
276
|
+
const sanitized = stripKotlinLineForSemanticScan(line);
|
|
277
|
+
return (
|
|
278
|
+
/\bimport\s+org\.junit\.(?:Test|Before|After|BeforeClass|AfterClass|Assert|Rule|ClassRule|runner\.RunWith|rules\.)\b/.test(sanitized) ||
|
|
279
|
+
/@(?:RunWith|Rule|ClassRule)\b/.test(sanitized) ||
|
|
280
|
+
/\bAssert\s*\./.test(sanitized)
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
|
|
270
285
|
export const hasKotlinSupervisorScopeUsage = (source: string): boolean => {
|
|
271
286
|
return collectKotlinRegexLines(source, /\bsupervisorScope\s*(?:<[^>\n]+>\s*)?(?:\(|\{)/).length > 0;
|
|
272
287
|
};
|
|
@@ -161,6 +161,10 @@ const isKotlinTestPath = (path: string): boolean => {
|
|
|
161
161
|
);
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
+
const isAndroidKotlinTestPath = (path: string): boolean => {
|
|
165
|
+
return isAndroidKotlinPath(path) && isKotlinTestPath(path);
|
|
166
|
+
};
|
|
167
|
+
|
|
164
168
|
const isExcludedProjectScanPath = (path: string): boolean => {
|
|
165
169
|
const normalized = path.replace(/\\/g, '/').toLowerCase();
|
|
166
170
|
return (
|
|
@@ -653,6 +657,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
653
657
|
{ 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.' },
|
|
654
658
|
{ 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.' },
|
|
655
659
|
{ platform: 'android', pathCheck: isAndroidLocalPropertiesPath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.android.security.local-properties-tracked.ast', code: 'HEURISTICS_ANDROID_SECURITY_LOCAL_PROPERTIES_TRACKED_AST', message: 'AST heuristic detected tracked Android local.properties file.' },
|
|
660
|
+
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinSharedPreferencesUsage, ruleId: 'heuristics.android.persistence.shared-preferences-usage.ast', code: 'HEURISTICS_ANDROID_PERSISTENCE_SHARED_PREFERENCES_USAGE_AST', message: 'AST heuristic detected SharedPreferences usage in Android production Kotlin code.' },
|
|
661
|
+
{ platform: 'android', pathCheck: isAndroidKotlinTestPath, excludePaths: [], detect: TextAndroid.hasKotlinJUnit4Usage, ruleId: 'heuristics.android.testing.junit4-usage.ast', code: 'HEURISTICS_ANDROID_TESTING_JUNIT4_USAGE_AST', message: 'AST heuristic detected JUnit4 usage in Android Kotlin tests where JUnit5 is preferred.' },
|
|
656
662
|
{ 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.' },
|
|
657
663
|
{ platform: 'android', pathCheck: isAndroidPresentationPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinManualCoroutineScopeInViewModelUsage, ruleId: 'heuristics.android.coroutines.manual-scope-in-viewmodel.ast', code: 'HEURISTICS_ANDROID_COROUTINES_MANUAL_SCOPE_IN_VIEWMODEL_AST', message: 'AST heuristic detected manual CoroutineScope inside an Android ViewModel where viewModelScope should be preferred.' },
|
|
658
664
|
{ platform: 'android', pathCheck: isAndroidNonPresentationKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinDispatcherMainBoundaryLeakUsage, ruleId: 'heuristics.android.coroutines.dispatchers-main-boundary-leak.ast', code: 'HEURISTICS_ANDROID_COROUTINES_DISPATCHERS_MAIN_BOUNDARY_LEAK_AST', message: 'AST heuristic detected Dispatchers.Main outside Android presentation code.' },
|
|
@@ -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, 19);
|
|
7
7
|
|
|
8
8
|
const ids = androidRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -12,6 +12,8 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
12
12
|
'heuristics.android.run-blocking.ast',
|
|
13
13
|
'heuristics.android.flow.livedata-state-exposure.ast',
|
|
14
14
|
'heuristics.android.security.local-properties-tracked.ast',
|
|
15
|
+
'heuristics.android.persistence.shared-preferences-usage.ast',
|
|
16
|
+
'heuristics.android.testing.junit4-usage.ast',
|
|
15
17
|
'heuristics.android.coroutines.manual-scope-in-viewmodel.ast',
|
|
16
18
|
'heuristics.android.coroutines.dispatchers-main-boundary-leak.ast',
|
|
17
19
|
'heuristics.android.coroutines.hardcoded-background-dispatcher.ast',
|
|
@@ -57,6 +59,14 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
57
59
|
byId.get('heuristics.android.security.local-properties-tracked.ast')?.then.code,
|
|
58
60
|
'HEURISTICS_ANDROID_SECURITY_LOCAL_PROPERTIES_TRACKED_AST'
|
|
59
61
|
);
|
|
62
|
+
assert.equal(
|
|
63
|
+
byId.get('heuristics.android.persistence.shared-preferences-usage.ast')?.then.code,
|
|
64
|
+
'HEURISTICS_ANDROID_PERSISTENCE_SHARED_PREFERENCES_USAGE_AST'
|
|
65
|
+
);
|
|
66
|
+
assert.equal(
|
|
67
|
+
byId.get('heuristics.android.testing.junit4-usage.ast')?.then.code,
|
|
68
|
+
'HEURISTICS_ANDROID_TESTING_JUNIT4_USAGE_AST'
|
|
69
|
+
);
|
|
60
70
|
assert.equal(
|
|
61
71
|
byId.get('heuristics.android.coroutines.manual-scope-in-viewmodel.ast')?.then.code,
|
|
62
72
|
'HEURISTICS_ANDROID_COROUTINES_MANUAL_SCOPE_IN_VIEWMODEL_AST'
|
|
@@ -95,6 +95,46 @@ export const androidRules: RuleSet = [
|
|
|
95
95
|
code: 'HEURISTICS_ANDROID_SECURITY_LOCAL_PROPERTIES_TRACKED_AST',
|
|
96
96
|
},
|
|
97
97
|
},
|
|
98
|
+
{
|
|
99
|
+
id: 'heuristics.android.persistence.shared-preferences-usage.ast',
|
|
100
|
+
description:
|
|
101
|
+
'Detects SharedPreferences usage in Android production Kotlin code where DataStore may be preferred.',
|
|
102
|
+
severity: 'WARN',
|
|
103
|
+
platform: 'android',
|
|
104
|
+
locked: true,
|
|
105
|
+
when: {
|
|
106
|
+
kind: 'Heuristic',
|
|
107
|
+
where: {
|
|
108
|
+
ruleId: 'heuristics.android.persistence.shared-preferences-usage.ast',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
then: {
|
|
112
|
+
kind: 'Finding',
|
|
113
|
+
message:
|
|
114
|
+
'AST heuristic detected SharedPreferences usage in Android production Kotlin code.',
|
|
115
|
+
code: 'HEURISTICS_ANDROID_PERSISTENCE_SHARED_PREFERENCES_USAGE_AST',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'heuristics.android.testing.junit4-usage.ast',
|
|
120
|
+
description:
|
|
121
|
+
'Detects JUnit4 usage in Android Kotlin tests where JUnit5 is preferred.',
|
|
122
|
+
severity: 'WARN',
|
|
123
|
+
platform: 'android',
|
|
124
|
+
locked: true,
|
|
125
|
+
when: {
|
|
126
|
+
kind: 'Heuristic',
|
|
127
|
+
where: {
|
|
128
|
+
ruleId: 'heuristics.android.testing.junit4-usage.ast',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
then: {
|
|
132
|
+
kind: 'Finding',
|
|
133
|
+
message:
|
|
134
|
+
'AST heuristic detected JUnit4 usage in Android Kotlin tests.',
|
|
135
|
+
code: 'HEURISTICS_ANDROID_TESTING_JUNIT4_USAGE_AST',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
98
138
|
{
|
|
99
139
|
id: 'heuristics.android.coroutines.manual-scope-in-viewmodel.ast',
|
|
100
140
|
description:
|
|
@@ -137,11 +137,20 @@ app/
|
|
|
137
137
|
✅ `skills.android.guideline.android.local-properties-api-keys-no-subir-a-git` debe mapear a señal ejecutable de `local.properties` versionado bajo `apps/android/`.
|
|
138
138
|
✅ Este baseline no imprime ni analiza valores secretos; solo reporta la presencia del archivo versionado y exige retirarlo del control de versiones.
|
|
139
139
|
|
|
140
|
+
### Enforcement AST inicial de persistencia Android
|
|
141
|
+
✅ `skills.android.guideline.android.datastore-androidx-datastore-datastore-preferences-reemplazo-de-shared` debe mapear a señal ejecutable de uso legacy de `SharedPreferences` en Kotlin Android production.
|
|
142
|
+
✅ Este baseline no prohíbe `EncryptedSharedPreferences` todavía ni declara cobertura completa de migración a DataStore; solo detecta el uso legacy explícito de `SharedPreferences` / `getSharedPreferences(...)`.
|
|
143
|
+
|
|
140
144
|
### Enforcement AST inicial de Flow/StateFlow Android
|
|
141
145
|
✅ 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.
|
|
142
146
|
✅ 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.
|
|
143
147
|
✅ `Flow`, `collectAsState`, `stateIn`, Room observable queries y SharedFlow events quedan fuera hasta que tengan detectores propios y regresiones dirigidas.
|
|
144
148
|
|
|
149
|
+
### Enforcement AST inicial de testing Android
|
|
150
|
+
✅ `skills.android.guideline.android.junit5-framework-de-testing-preferido-sobre-junit4` debe mapear a señal ejecutable de uso JUnit4 en tests Kotlin Android.
|
|
151
|
+
✅ Este baseline detecta imports/anotaciones JUnit4 (`org.junit.Test`, `org.junit.Assert`, `@RunWith`, reglas JUnit4) bajo `apps/android/**/test/**` y `apps/android/**/androidTest/**`.
|
|
152
|
+
✅ JUnit5/Jupiter queda preservado como caso limpio; migración completa de runner, Gradle y APIs de assertions queda fuera hasta tener detectores propios.
|
|
153
|
+
|
|
145
154
|
### Dependency Injection (Hilt):
|
|
146
155
|
✅ **Hilt** - DI framework (NO manual factories)
|
|
147
156
|
✅ **@HiltAndroidApp** - Application class
|
|
@@ -251,6 +251,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
251
251
|
'android.security.local-properties',
|
|
252
252
|
['heuristics.android.security.local-properties-tracked.ast']
|
|
253
253
|
),
|
|
254
|
+
'skills.android.guideline.android.datastore-androidx-datastore-datastore-preferences-reemplazo-de-shared': heuristicDetector(
|
|
255
|
+
'android.persistence.shared-preferences',
|
|
256
|
+
['heuristics.android.persistence.shared-preferences-usage.ast']
|
|
257
|
+
),
|
|
258
|
+
'skills.android.guideline.android.junit5-framework-de-testing-preferido-sobre-junit4': heuristicDetector(
|
|
259
|
+
'android.testing.junit4-usage',
|
|
260
|
+
['heuristics.android.testing.junit4-usage.ast']
|
|
261
|
+
),
|
|
254
262
|
'skills.android.guideline.android.stateflow-estado-mutable-observable': heuristicDetector(
|
|
255
263
|
'android.flow.state-exposure',
|
|
256
264
|
['heuristics.android.flow.livedata-state-exposure.ast']
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.189",
|
|
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": {
|