pumuki 6.3.185 → 6.3.187
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 +26 -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 +6 -1
- 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.187
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
hasKotlinGlobalScopeUsage,
|
|
12
12
|
hasKotlinHardcodedBackgroundDispatcherUsage,
|
|
13
13
|
hasKotlinLiveDataStateExposureUsage,
|
|
14
|
+
hasKotlinLifecycleScopeUsage,
|
|
14
15
|
hasKotlinWithContextUsage,
|
|
15
16
|
hasKotlinManualCoroutineScopeInViewModelUsage,
|
|
16
17
|
hasKotlinRunBlockingUsage,
|
|
@@ -221,6 +222,31 @@ class SyncOrdersUseCase {
|
|
|
221
222
|
assert.equal(hasKotlinWithContextUsage(source), false);
|
|
222
223
|
});
|
|
223
224
|
|
|
225
|
+
test('hasKotlinLifecycleScopeUsage detecta lifecycleScope con llamadas encadenadas', () => {
|
|
226
|
+
const source = `
|
|
227
|
+
class SyncOrdersUseCase {
|
|
228
|
+
fun execute() {
|
|
229
|
+
lifecycleScope.launch { syncRemote() }
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
233
|
+
assert.equal(hasKotlinLifecycleScopeUsage(source), true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('hasKotlinLifecycleScopeUsage ignora imports, comentarios, strings y nombres parciales', () => {
|
|
237
|
+
const source = `
|
|
238
|
+
import androidx.lifecycle.lifecycleScope
|
|
239
|
+
// lifecycleScope.launch { syncRemote() }
|
|
240
|
+
val sample = "lifecycleScope.launch { syncRemote() }"
|
|
241
|
+
class SyncOrdersUseCase {
|
|
242
|
+
fun execute() {
|
|
243
|
+
customLifecycleScope.launch { syncRemote() }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
`;
|
|
247
|
+
assert.equal(hasKotlinLifecycleScopeUsage(source), false);
|
|
248
|
+
});
|
|
249
|
+
|
|
224
250
|
test('hasKotlinSupervisorScopeUsage detecta supervisorScope con parentesis y llaves', () => {
|
|
225
251
|
const parenthesesSource = `
|
|
226
252
|
class SyncOrdersUseCase {
|
|
@@ -263,6 +263,10 @@ export const hasKotlinWithContextUsage = (source: string): boolean => {
|
|
|
263
263
|
return collectKotlinRegexLines(source, /\bwithContext\s*(?:<[^>\n]+>\s*)?\(/).length > 0;
|
|
264
264
|
};
|
|
265
265
|
|
|
266
|
+
export const hasKotlinLifecycleScopeUsage = (source: string): boolean => {
|
|
267
|
+
return collectKotlinRegexLines(source, /\blifecycleScope\s*\./).length > 0;
|
|
268
|
+
};
|
|
269
|
+
|
|
266
270
|
export const hasKotlinSupervisorScopeUsage = (source: string): boolean => {
|
|
267
271
|
return collectKotlinRegexLines(source, /\bsupervisorScope\s*(?:<[^>\n]+>\s*)?(?:\(|\{)/).length > 0;
|
|
268
272
|
};
|
|
@@ -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,11 +652,13 @@ 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.' },
|
|
648
656
|
{ 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
657
|
{ 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
658
|
{ 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.' },
|
|
651
659
|
{ platform: 'android', pathCheck: isAndroidDomainOrApplicationPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinHardcodedBackgroundDispatcherUsage, ruleId: 'heuristics.android.coroutines.hardcoded-background-dispatcher.ast', code: 'HEURISTICS_ANDROID_COROUTINES_HARDCODED_BACKGROUND_DISPATCHER_AST', message: 'AST heuristic detected hard-coded Dispatchers.IO or Dispatchers.Default in Android domain/application code.' },
|
|
652
660
|
{ platform: 'android', pathCheck: isAndroidDomainOrApplicationPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinWithContextUsage, ruleId: 'heuristics.android.coroutines.with-context.ast', code: 'HEURISTICS_ANDROID_COROUTINES_WITH_CONTEXT_AST', message: 'AST heuristic detected withContext usage in Android domain/application code.' },
|
|
661
|
+
{ platform: 'android', pathCheck: isAndroidDomainOrApplicationPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinLifecycleScopeUsage, ruleId: 'heuristics.android.coroutines.lifecycle-scope-boundary-leak.ast', code: 'HEURISTICS_ANDROID_COROUTINES_LIFECYCLE_SCOPE_BOUNDARY_LEAK_AST', message: 'AST heuristic detected lifecycleScope usage in Android domain/application code.' },
|
|
653
662
|
{ platform: 'android', pathCheck: isAndroidDomainOrApplicationPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinSupervisorScopeUsage, ruleId: 'heuristics.android.coroutines.supervisor-scope.ast', code: 'HEURISTICS_ANDROID_COROUTINES_SUPERVISOR_SCOPE_AST', message: 'AST heuristic detected supervisorScope usage in Android domain/application code.' },
|
|
654
663
|
{ platform: 'android', pathCheck: isAndroidDomainOrApplicationPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinCoroutineTryCatchUsage, ruleId: 'heuristics.android.coroutines.try-catch.ast', code: 'HEURISTICS_ANDROID_COROUTINES_TRY_CATCH_AST', message: 'AST heuristic detected try-catch handling in Android coroutine code.' },
|
|
655
664
|
];
|
|
@@ -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, 17);
|
|
7
7
|
|
|
8
8
|
const ids = androidRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -11,10 +11,12 @@ 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',
|
|
14
15
|
'heuristics.android.coroutines.manual-scope-in-viewmodel.ast',
|
|
15
16
|
'heuristics.android.coroutines.dispatchers-main-boundary-leak.ast',
|
|
16
17
|
'heuristics.android.coroutines.hardcoded-background-dispatcher.ast',
|
|
17
18
|
'heuristics.android.coroutines.with-context.ast',
|
|
19
|
+
'heuristics.android.coroutines.lifecycle-scope-boundary-leak.ast',
|
|
18
20
|
'heuristics.android.coroutines.supervisor-scope.ast',
|
|
19
21
|
'heuristics.android.coroutines.try-catch.ast',
|
|
20
22
|
'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
|
|
@@ -51,6 +53,10 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
51
53
|
byId.get('heuristics.android.flow.livedata-state-exposure.ast')?.then.code,
|
|
52
54
|
'HEURISTICS_ANDROID_FLOW_LIVEDATA_STATE_EXPOSURE_AST'
|
|
53
55
|
);
|
|
56
|
+
assert.equal(
|
|
57
|
+
byId.get('heuristics.android.security.local-properties-tracked.ast')?.then.code,
|
|
58
|
+
'HEURISTICS_ANDROID_SECURITY_LOCAL_PROPERTIES_TRACKED_AST'
|
|
59
|
+
);
|
|
54
60
|
assert.equal(
|
|
55
61
|
byId.get('heuristics.android.coroutines.manual-scope-in-viewmodel.ast')?.then.code,
|
|
56
62
|
'HEURISTICS_ANDROID_COROUTINES_MANUAL_SCOPE_IN_VIEWMODEL_AST'
|
|
@@ -67,6 +73,10 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
67
73
|
byId.get('heuristics.android.coroutines.with-context.ast')?.then.code,
|
|
68
74
|
'HEURISTICS_ANDROID_COROUTINES_WITH_CONTEXT_AST'
|
|
69
75
|
);
|
|
76
|
+
assert.equal(
|
|
77
|
+
byId.get('heuristics.android.coroutines.lifecycle-scope-boundary-leak.ast')?.then.code,
|
|
78
|
+
'HEURISTICS_ANDROID_COROUTINES_LIFECYCLE_SCOPE_BOUNDARY_LEAK_AST'
|
|
79
|
+
);
|
|
70
80
|
assert.equal(
|
|
71
81
|
byId.get('heuristics.android.coroutines.supervisor-scope.ast')?.then.code,
|
|
72
82
|
'HEURISTICS_ANDROID_COROUTINES_SUPERVISOR_SCOPE_AST'
|
|
@@ -75,6 +75,26 @@ 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
|
+
},
|
|
78
98
|
{
|
|
79
99
|
id: 'heuristics.android.coroutines.manual-scope-in-viewmodel.ast',
|
|
80
100
|
description:
|
|
@@ -155,6 +175,26 @@ export const androidRules: RuleSet = [
|
|
|
155
175
|
code: 'HEURISTICS_ANDROID_COROUTINES_WITH_CONTEXT_AST',
|
|
156
176
|
},
|
|
157
177
|
},
|
|
178
|
+
{
|
|
179
|
+
id: 'heuristics.android.coroutines.lifecycle-scope-boundary-leak.ast',
|
|
180
|
+
description:
|
|
181
|
+
'Detects lifecycleScope usage in Android domain/application code where lifecycle ownership belongs to UI lifecycle owners.',
|
|
182
|
+
severity: 'WARN',
|
|
183
|
+
platform: 'android',
|
|
184
|
+
locked: true,
|
|
185
|
+
when: {
|
|
186
|
+
kind: 'Heuristic',
|
|
187
|
+
where: {
|
|
188
|
+
ruleId: 'heuristics.android.coroutines.lifecycle-scope-boundary-leak.ast',
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
then: {
|
|
192
|
+
kind: 'Finding',
|
|
193
|
+
message:
|
|
194
|
+
'AST heuristic detected lifecycleScope usage in Android domain/application code.',
|
|
195
|
+
code: 'HEURISTICS_ANDROID_COROUTINES_LIFECYCLE_SCOPE_BOUNDARY_LEAK_AST',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
158
198
|
{
|
|
159
199
|
id: 'heuristics.android.coroutines.supervisor-scope.ast',
|
|
160
200
|
description:
|
|
@@ -127,11 +127,16 @@ app/
|
|
|
127
127
|
✅ `skills.android.guideline.android.viewmodelscope-scope-de-viewmodel-cancelado-automa-ticamente` debe mapear a señales ejecutables de scopes manuales dentro de `ViewModel`, empezando por `CoroutineScope(...)` construido en presentation.
|
|
128
128
|
✅ `skills.android.guideline.android.dispatchers-main-ui-io-network-disk-default-cpu` debe mapear a señales ejecutables de dispatchers mal ubicados: `Dispatchers.Main` fuera de `presentation` y `Dispatchers.IO` / `Dispatchers.Default` hardcodeados en `domain` o `application` en lugar de entrar por frontera inyectable.
|
|
129
129
|
✅ `skills.android.guideline.android.withcontext-cambiar-dispatcher` debe mapear a señal ejecutable de `withContext` en `domain` o `application`.
|
|
130
|
+
✅ `skills.android.guideline.android.lifecyclescope-scope-de-activity-fragment` debe mapear a señal ejecutable de `lifecycleScope` fuera de la capa UI, empezando por `domain` o `application`.
|
|
130
131
|
✅ `skills.android.guideline.android.supervisorscope-errores-no-cancelan-otros-jobs` debe mapear a señal ejecutable de `supervisorScope` en `domain` o `application`.
|
|
131
132
|
✅ `skills.android.guideline.android.try-catch-manejo-de-errores-en-coroutines` debe mapear a señal ejecutable de `try/catch` dentro de código coroutine en `domain` o `application`.
|
|
132
|
-
✅ 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`, `supervisorScope` y `try/catch` en coroutines, pero no declara todavía cobertura completa de `Flow`, dispatchers ni cancelación cooperativa.
|
|
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.
|
|
133
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.
|
|
134
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
|
+
|
|
135
140
|
### Enforcement AST inicial de Flow/StateFlow Android
|
|
136
141
|
✅ 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.
|
|
137
142
|
✅ 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.
|
|
@@ -235,6 +235,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
235
235
|
'android.coroutines.with-context',
|
|
236
236
|
['heuristics.android.coroutines.with-context.ast']
|
|
237
237
|
),
|
|
238
|
+
'skills.android.guideline.android.lifecyclescope-scope-de-activity-fragment': heuristicDetector(
|
|
239
|
+
'android.coroutines.lifecycle-scope',
|
|
240
|
+
['heuristics.android.coroutines.lifecycle-scope-boundary-leak.ast']
|
|
241
|
+
),
|
|
238
242
|
'skills.android.guideline.android.supervisorscope-errores-no-cancelan-otros-jobs': heuristicDetector(
|
|
239
243
|
'android.coroutines.supervisor-scope',
|
|
240
244
|
['heuristics.android.coroutines.supervisor-scope.ast']
|
|
@@ -243,6 +247,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
243
247
|
'android.coroutines.try-catch',
|
|
244
248
|
['heuristics.android.coroutines.try-catch.ast']
|
|
245
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
|
+
),
|
|
246
254
|
'skills.android.guideline.android.stateflow-estado-mutable-observable': heuristicDetector(
|
|
247
255
|
'android.flow.state-exposure',
|
|
248
256
|
['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.187",
|
|
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": {
|