pumuki 6.3.180 → 6.3.181
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 +22 -0
- package/core/facts/detectors/text/android.ts +4 -0
- package/core/facts/extractHeuristicFacts.ts +5 -0
- package/core/rules/presets/heuristics/android.test.ts +6 -1
- package/core/rules/presets/heuristics/android.ts +20 -0
- package/docs/codex-skills/android-enterprise-rules.md +2 -1
- package/integrations/config/skillsDetectorRegistry.ts +4 -0
- package/package.json +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v6.3.
|
|
1
|
+
v6.3.181
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
findKotlinLiskovSubstitutionMatch,
|
|
7
7
|
findKotlinOpenClosedWhenMatch,
|
|
8
8
|
findKotlinPresentationSrpMatch,
|
|
9
|
+
hasKotlinDispatcherMainBoundaryLeakUsage,
|
|
9
10
|
hasKotlinGlobalScopeUsage,
|
|
10
11
|
hasKotlinLiveDataStateExposureUsage,
|
|
11
12
|
hasKotlinManualCoroutineScopeInViewModelUsage,
|
|
@@ -141,6 +142,27 @@ class OrdersViewModel : ViewModel() {
|
|
|
141
142
|
assert.equal(hasKotlinManualCoroutineScopeInViewModelUsage(source), false);
|
|
142
143
|
});
|
|
143
144
|
|
|
145
|
+
test('hasKotlinDispatcherMainBoundaryLeakUsage detecta Dispatchers.Main como dispatcher UI explícito', () => {
|
|
146
|
+
const source = `
|
|
147
|
+
class SyncOrdersUseCase {
|
|
148
|
+
suspend fun execute() = withContext(Dispatchers.Main) { }
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
assert.equal(hasKotlinDispatcherMainBoundaryLeakUsage(source), true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('hasKotlinDispatcherMainBoundaryLeakUsage ignora imports, comentarios y strings', () => {
|
|
155
|
+
const source = `
|
|
156
|
+
import kotlinx.coroutines.Dispatchers
|
|
157
|
+
// withContext(Dispatchers.Main) { }
|
|
158
|
+
val sample = "Dispatchers.Main"
|
|
159
|
+
class SyncOrdersUseCase {
|
|
160
|
+
suspend fun execute() = withContext(Dispatchers.IO) { }
|
|
161
|
+
}
|
|
162
|
+
`;
|
|
163
|
+
assert.equal(hasKotlinDispatcherMainBoundaryLeakUsage(source), false);
|
|
164
|
+
});
|
|
165
|
+
|
|
144
166
|
test('findKotlinPresentationSrpMatch devuelve payload semantico para SRP-Android en presentation', () => {
|
|
145
167
|
const source = `
|
|
146
168
|
import android.content.SharedPreferences
|
|
@@ -331,6 +331,10 @@ export const hasKotlinManualCoroutineScopeInViewModelUsage = (source: string): b
|
|
|
331
331
|
return false;
|
|
332
332
|
};
|
|
333
333
|
|
|
334
|
+
export const hasKotlinDispatcherMainBoundaryLeakUsage = (source: string): boolean => {
|
|
335
|
+
return collectKotlinRegexLines(source, /\bDispatchers\s*\.\s*Main\b/).length > 0;
|
|
336
|
+
};
|
|
337
|
+
|
|
334
338
|
export const findKotlinPresentationSrpMatch = (
|
|
335
339
|
source: string
|
|
336
340
|
): KotlinPresentationSrpMatch | undefined => {
|
|
@@ -99,6 +99,10 @@ const isAndroidApplicationOrPresentationPath = (path: string): boolean => {
|
|
|
99
99
|
return isAndroidKotlinPath(path) && (path.includes('/application/') || path.includes('/presentation/'));
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
+
const isAndroidNonPresentationKotlinPath = (path: string): boolean => {
|
|
103
|
+
return isAndroidKotlinPath(path) && !path.includes('/presentation/');
|
|
104
|
+
};
|
|
105
|
+
|
|
102
106
|
const isApprovedIOSBridgePath = (path: string): boolean => {
|
|
103
107
|
const normalized = path.toLowerCase();
|
|
104
108
|
return (
|
|
@@ -639,6 +643,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
639
643
|
{ 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
644
|
{ 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.' },
|
|
641
645
|
{ 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.' },
|
|
646
|
+
{ 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.' },
|
|
642
647
|
];
|
|
643
648
|
|
|
644
649
|
const extractWorkflowHeuristicFacts = (
|
|
@@ -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, 11);
|
|
7
7
|
|
|
8
8
|
const ids = androidRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -12,6 +12,7 @@ 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.coroutines.manual-scope-in-viewmodel.ast',
|
|
15
|
+
'heuristics.android.coroutines.dispatchers-main-boundary-leak.ast',
|
|
15
16
|
'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
|
|
16
17
|
'heuristics.android.solid.ocp.discriminator-branching.ast',
|
|
17
18
|
'heuristics.android.solid.dip.concrete-framework-dependency.ast',
|
|
@@ -50,6 +51,10 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
50
51
|
byId.get('heuristics.android.coroutines.manual-scope-in-viewmodel.ast')?.then.code,
|
|
51
52
|
'HEURISTICS_ANDROID_COROUTINES_MANUAL_SCOPE_IN_VIEWMODEL_AST'
|
|
52
53
|
);
|
|
54
|
+
assert.equal(
|
|
55
|
+
byId.get('heuristics.android.coroutines.dispatchers-main-boundary-leak.ast')?.then.code,
|
|
56
|
+
'HEURISTICS_ANDROID_COROUTINES_DISPATCHERS_MAIN_BOUNDARY_LEAK_AST'
|
|
57
|
+
);
|
|
53
58
|
assert.equal(
|
|
54
59
|
byId.get('heuristics.android.solid.srp.presentation-mixed-responsibilities.ast')?.then.code,
|
|
55
60
|
'HEURISTICS_ANDROID_SOLID_SRP_PRESENTATION_MIXED_RESPONSIBILITIES_AST'
|
|
@@ -95,6 +95,26 @@ export const androidRules: RuleSet = [
|
|
|
95
95
|
code: 'HEURISTICS_ANDROID_COROUTINES_MANUAL_SCOPE_IN_VIEWMODEL_AST',
|
|
96
96
|
},
|
|
97
97
|
},
|
|
98
|
+
{
|
|
99
|
+
id: 'heuristics.android.coroutines.dispatchers-main-boundary-leak.ast',
|
|
100
|
+
description:
|
|
101
|
+
'Detects Dispatchers.Main usage outside Android presentation code.',
|
|
102
|
+
severity: 'WARN',
|
|
103
|
+
platform: 'android',
|
|
104
|
+
locked: true,
|
|
105
|
+
when: {
|
|
106
|
+
kind: 'Heuristic',
|
|
107
|
+
where: {
|
|
108
|
+
ruleId: 'heuristics.android.coroutines.dispatchers-main-boundary-leak.ast',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
then: {
|
|
112
|
+
kind: 'Finding',
|
|
113
|
+
message:
|
|
114
|
+
'AST heuristic detected Dispatchers.Main outside Android presentation code.',
|
|
115
|
+
code: 'HEURISTICS_ANDROID_COROUTINES_DISPATCHERS_MAIN_BOUNDARY_LEAK_AST',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
98
118
|
{
|
|
99
119
|
id: 'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
|
|
100
120
|
description:
|
|
@@ -125,7 +125,8 @@ app/
|
|
|
125
125
|
### Enforcement AST inicial de coroutines Android
|
|
126
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
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 dispatcher UI filtrado fuera de presentation, empezando por `Dispatchers.Main` en application/data.
|
|
129
|
+
✅ El baseline inicial de coroutines es parcial: cubre APIs bloqueantes, scopes no estructurados, scopes manuales en `ViewModel` y filtraciones de `Dispatchers.Main`, pero no declara todavía cobertura completa de `Flow`, `supervisorScope`, dispatchers ni cancelación cooperativa.
|
|
129
130
|
✅ 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.
|
|
130
131
|
|
|
131
132
|
### Enforcement AST inicial de Flow/StateFlow Android
|
|
@@ -224,6 +224,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
224
224
|
'android.coroutines.viewmodel-scope',
|
|
225
225
|
['heuristics.android.coroutines.manual-scope-in-viewmodel.ast']
|
|
226
226
|
),
|
|
227
|
+
'skills.android.guideline.android.dispatchers-main-ui-io-network-disk-default-cpu': heuristicDetector(
|
|
228
|
+
'android.coroutines.dispatchers',
|
|
229
|
+
['heuristics.android.coroutines.dispatchers-main-boundary-leak.ast']
|
|
230
|
+
),
|
|
227
231
|
'skills.android.guideline.android.stateflow-estado-mutable-observable': heuristicDetector(
|
|
228
232
|
'android.flow.state-exposure',
|
|
229
233
|
['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.181",
|
|
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": {
|