pumuki 6.3.186 → 6.3.188
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 +28 -0
- package/core/facts/detectors/text/android.ts +4 -0
- package/core/facts/extractHeuristicFacts.ts +9 -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 +8 -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.188
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
hasKotlinHardcodedBackgroundDispatcherUsage,
|
|
13
13
|
hasKotlinLiveDataStateExposureUsage,
|
|
14
14
|
hasKotlinLifecycleScopeUsage,
|
|
15
|
+
hasKotlinSharedPreferencesUsage,
|
|
15
16
|
hasKotlinWithContextUsage,
|
|
16
17
|
hasKotlinManualCoroutineScopeInViewModelUsage,
|
|
17
18
|
hasKotlinRunBlockingUsage,
|
|
@@ -247,6 +248,33 @@ class SyncOrdersUseCase {
|
|
|
247
248
|
assert.equal(hasKotlinLifecycleScopeUsage(source), false);
|
|
248
249
|
});
|
|
249
250
|
|
|
251
|
+
test('hasKotlinSharedPreferencesUsage detecta SharedPreferences y getSharedPreferences', () => {
|
|
252
|
+
const typeSource = `
|
|
253
|
+
class PreferencesStore(private val preferences: SharedPreferences) {
|
|
254
|
+
fun read() = preferences.getString("token", null)
|
|
255
|
+
}
|
|
256
|
+
`;
|
|
257
|
+
const callSource = `
|
|
258
|
+
class PreferencesStore(private val context: Context) {
|
|
259
|
+
fun read() = context.getSharedPreferences("user", Context.MODE_PRIVATE)
|
|
260
|
+
}
|
|
261
|
+
`;
|
|
262
|
+
assert.equal(hasKotlinSharedPreferencesUsage(typeSource), true);
|
|
263
|
+
assert.equal(hasKotlinSharedPreferencesUsage(callSource), true);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('hasKotlinSharedPreferencesUsage ignora imports, comentarios, strings y nombres parciales', () => {
|
|
267
|
+
const source = `
|
|
268
|
+
import android.content.SharedPreferences
|
|
269
|
+
// val preferences: SharedPreferences = legacy()
|
|
270
|
+
val sample = "getSharedPreferences(\"user\")"
|
|
271
|
+
class PreferencesStore {
|
|
272
|
+
fun read() = customSharedPreferencesProvider()
|
|
273
|
+
}
|
|
274
|
+
`;
|
|
275
|
+
assert.equal(hasKotlinSharedPreferencesUsage(source), false);
|
|
276
|
+
});
|
|
277
|
+
|
|
250
278
|
test('hasKotlinSupervisorScopeUsage detecta supervisorScope con parentesis y llaves', () => {
|
|
251
279
|
const parenthesesSource = `
|
|
252
280
|
class SyncOrdersUseCase {
|
|
@@ -267,6 +267,10 @@ 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
|
+
|
|
270
274
|
export const hasKotlinSupervisorScopeUsage = (source: string): boolean => {
|
|
271
275
|
return collectKotlinRegexLines(source, /\bsupervisorScope\s*(?:<[^>\n]+>\s*)?(?:\(|\{)/).length > 0;
|
|
272
276
|
};
|
|
@@ -91,6 +91,13 @@ const isAndroidKotlinPath = (path: string): boolean => {
|
|
|
91
91
|
return (path.endsWith('.kt') || path.endsWith('.kts')) && path.startsWith('apps/android/');
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
+
const isAndroidLocalPropertiesPath = (path: string): boolean => {
|
|
95
|
+
const normalized = path.replace(/\\/g, '/').toLowerCase();
|
|
96
|
+
return normalized.startsWith('apps/android/') && normalized.endsWith('/local.properties');
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const detectsTrackedFilePresence = (_source: string): boolean => true;
|
|
100
|
+
|
|
94
101
|
const isAndroidPresentationPath = (path: string): boolean => {
|
|
95
102
|
return isAndroidKotlinPath(path) && path.includes('/presentation/');
|
|
96
103
|
};
|
|
@@ -645,6 +652,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
645
652
|
{ 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.' },
|
|
646
653
|
{ 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.' },
|
|
647
654
|
{ 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
|
+
{ 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.' },
|
|
656
|
+
{ 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.' },
|
|
648
657
|
{ 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.' },
|
|
649
658
|
{ 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.' },
|
|
650
659
|
{ 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, 18);
|
|
7
7
|
|
|
8
8
|
const ids = androidRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -11,6 +11,8 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
11
11
|
'heuristics.android.globalscope.ast',
|
|
12
12
|
'heuristics.android.run-blocking.ast',
|
|
13
13
|
'heuristics.android.flow.livedata-state-exposure.ast',
|
|
14
|
+
'heuristics.android.security.local-properties-tracked.ast',
|
|
15
|
+
'heuristics.android.persistence.shared-preferences-usage.ast',
|
|
14
16
|
'heuristics.android.coroutines.manual-scope-in-viewmodel.ast',
|
|
15
17
|
'heuristics.android.coroutines.dispatchers-main-boundary-leak.ast',
|
|
16
18
|
'heuristics.android.coroutines.hardcoded-background-dispatcher.ast',
|
|
@@ -52,6 +54,14 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
52
54
|
byId.get('heuristics.android.flow.livedata-state-exposure.ast')?.then.code,
|
|
53
55
|
'HEURISTICS_ANDROID_FLOW_LIVEDATA_STATE_EXPOSURE_AST'
|
|
54
56
|
);
|
|
57
|
+
assert.equal(
|
|
58
|
+
byId.get('heuristics.android.security.local-properties-tracked.ast')?.then.code,
|
|
59
|
+
'HEURISTICS_ANDROID_SECURITY_LOCAL_PROPERTIES_TRACKED_AST'
|
|
60
|
+
);
|
|
61
|
+
assert.equal(
|
|
62
|
+
byId.get('heuristics.android.persistence.shared-preferences-usage.ast')?.then.code,
|
|
63
|
+
'HEURISTICS_ANDROID_PERSISTENCE_SHARED_PREFERENCES_USAGE_AST'
|
|
64
|
+
);
|
|
55
65
|
assert.equal(
|
|
56
66
|
byId.get('heuristics.android.coroutines.manual-scope-in-viewmodel.ast')?.then.code,
|
|
57
67
|
'HEURISTICS_ANDROID_COROUTINES_MANUAL_SCOPE_IN_VIEWMODEL_AST'
|
|
@@ -75,6 +75,46 @@ export const androidRules: RuleSet = [
|
|
|
75
75
|
code: 'HEURISTICS_ANDROID_FLOW_LIVEDATA_STATE_EXPOSURE_AST',
|
|
76
76
|
},
|
|
77
77
|
},
|
|
78
|
+
{
|
|
79
|
+
id: 'heuristics.android.security.local-properties-tracked.ast',
|
|
80
|
+
description:
|
|
81
|
+
'Detects tracked Android local.properties files that may expose machine-local SDK paths or secrets.',
|
|
82
|
+
severity: 'WARN',
|
|
83
|
+
platform: 'android',
|
|
84
|
+
locked: true,
|
|
85
|
+
when: {
|
|
86
|
+
kind: 'Heuristic',
|
|
87
|
+
where: {
|
|
88
|
+
ruleId: 'heuristics.android.security.local-properties-tracked.ast',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
then: {
|
|
92
|
+
kind: 'Finding',
|
|
93
|
+
message:
|
|
94
|
+
'AST heuristic detected tracked Android local.properties file.',
|
|
95
|
+
code: 'HEURISTICS_ANDROID_SECURITY_LOCAL_PROPERTIES_TRACKED_AST',
|
|
96
|
+
},
|
|
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
|
+
},
|
|
78
118
|
{
|
|
79
119
|
id: 'heuristics.android.coroutines.manual-scope-in-viewmodel.ast',
|
|
80
120
|
description:
|
|
@@ -133,6 +133,14 @@ app/
|
|
|
133
133
|
✅ El baseline inicial de coroutines es parcial: cubre APIs bloqueantes, scopes no estructurados, scopes manuales en `ViewModel`, filtraciones de `Dispatchers.Main`, hardcode de dispatchers de background en dominio/aplicación, `withContext`, fuga de `lifecycleScope`, `supervisorScope` y `try/catch` en coroutines, pero no declara todavía cobertura completa de `Flow`, dispatchers ni cancelación cooperativa.
|
|
134
134
|
✅ 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.
|
|
135
135
|
|
|
136
|
+
### Enforcement AST inicial de seguridad Android
|
|
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
|
+
✅ Este baseline no imprime ni analiza valores secretos; solo reporta la presencia del archivo versionado y exige retirarlo del control de versiones.
|
|
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
|
+
|
|
136
144
|
### Enforcement AST inicial de Flow/StateFlow Android
|
|
137
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.
|
|
138
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.
|
|
@@ -247,6 +247,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
247
247
|
'android.coroutines.try-catch',
|
|
248
248
|
['heuristics.android.coroutines.try-catch.ast']
|
|
249
249
|
),
|
|
250
|
+
'skills.android.guideline.android.local-properties-api-keys-no-subir-a-git': heuristicDetector(
|
|
251
|
+
'android.security.local-properties',
|
|
252
|
+
['heuristics.android.security.local-properties-tracked.ast']
|
|
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
|
+
),
|
|
250
258
|
'skills.android.guideline.android.stateflow-estado-mutable-observable': heuristicDetector(
|
|
251
259
|
'android.flow.state-exposure',
|
|
252
260
|
['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.188",
|
|
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": {
|