pumuki 6.3.181 → 6.3.183
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 +60 -0
- package/core/facts/detectors/text/android.ts +8 -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 +3 -2
- package/integrations/config/skillsDetectorRegistry.ts +8 -1
- package/package.json +1 -1
- package/skills.lock.json +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v6.3.
|
|
1
|
+
v6.3.183
|
|
@@ -8,9 +8,11 @@ import {
|
|
|
8
8
|
findKotlinPresentationSrpMatch,
|
|
9
9
|
hasKotlinDispatcherMainBoundaryLeakUsage,
|
|
10
10
|
hasKotlinGlobalScopeUsage,
|
|
11
|
+
hasKotlinHardcodedBackgroundDispatcherUsage,
|
|
11
12
|
hasKotlinLiveDataStateExposureUsage,
|
|
12
13
|
hasKotlinManualCoroutineScopeInViewModelUsage,
|
|
13
14
|
hasKotlinRunBlockingUsage,
|
|
15
|
+
hasKotlinSupervisorScopeUsage,
|
|
14
16
|
hasKotlinThreadSleepCall,
|
|
15
17
|
} from './android';
|
|
16
18
|
|
|
@@ -163,6 +165,64 @@ class SyncOrdersUseCase {
|
|
|
163
165
|
assert.equal(hasKotlinDispatcherMainBoundaryLeakUsage(source), false);
|
|
164
166
|
});
|
|
165
167
|
|
|
168
|
+
test('hasKotlinHardcodedBackgroundDispatcherUsage detecta Dispatchers.IO y Dispatchers.Default', () => {
|
|
169
|
+
const ioSource = `
|
|
170
|
+
class SyncOrdersUseCase {
|
|
171
|
+
suspend fun execute() = withContext(Dispatchers.IO) { }
|
|
172
|
+
}
|
|
173
|
+
`;
|
|
174
|
+
const defaultSource = `
|
|
175
|
+
class BuildCatalogIndexUseCase {
|
|
176
|
+
suspend fun execute() = withContext(Dispatchers.Default) { }
|
|
177
|
+
}
|
|
178
|
+
`;
|
|
179
|
+
assert.equal(hasKotlinHardcodedBackgroundDispatcherUsage(ioSource), true);
|
|
180
|
+
assert.equal(hasKotlinHardcodedBackgroundDispatcherUsage(defaultSource), true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('hasKotlinHardcodedBackgroundDispatcherUsage ignora imports, comentarios, strings y Main', () => {
|
|
184
|
+
const source = `
|
|
185
|
+
import kotlinx.coroutines.Dispatchers
|
|
186
|
+
// withContext(Dispatchers.IO) { }
|
|
187
|
+
val sample = "Dispatchers.Default"
|
|
188
|
+
class SyncOrdersUseCase {
|
|
189
|
+
suspend fun execute() = withContext(Dispatchers.Main) { }
|
|
190
|
+
}
|
|
191
|
+
`;
|
|
192
|
+
assert.equal(hasKotlinHardcodedBackgroundDispatcherUsage(source), false);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('hasKotlinSupervisorScopeUsage detecta supervisorScope con parentesis y llaves', () => {
|
|
196
|
+
const parenthesesSource = `
|
|
197
|
+
class SyncOrdersUseCase {
|
|
198
|
+
suspend fun execute() = supervisorScope {
|
|
199
|
+
launch { syncRemote() }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
`;
|
|
203
|
+
const genericSource = `
|
|
204
|
+
class SyncOrdersUseCase {
|
|
205
|
+
suspend fun execute() = supervisorScope<Unit> {
|
|
206
|
+
launch { syncRemote() }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
assert.equal(hasKotlinSupervisorScopeUsage(parenthesesSource), true);
|
|
211
|
+
assert.equal(hasKotlinSupervisorScopeUsage(genericSource), true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('hasKotlinSupervisorScopeUsage ignora imports, comentarios, strings y nombres parciales', () => {
|
|
215
|
+
const source = `
|
|
216
|
+
import kotlinx.coroutines.supervisorScope
|
|
217
|
+
// supervisorScope { launch { } }
|
|
218
|
+
val sample = "supervisorScope { launch { } }"
|
|
219
|
+
class SyncOrdersUseCase {
|
|
220
|
+
suspend fun execute() = customSupervisorScope { }
|
|
221
|
+
}
|
|
222
|
+
`;
|
|
223
|
+
assert.equal(hasKotlinSupervisorScopeUsage(source), false);
|
|
224
|
+
});
|
|
225
|
+
|
|
166
226
|
test('findKotlinPresentationSrpMatch devuelve payload semantico para SRP-Android en presentation', () => {
|
|
167
227
|
const source = `
|
|
168
228
|
import android.content.SharedPreferences
|
|
@@ -255,6 +255,14 @@ const parseKotlinTypeDeclarations = (source: string): readonly KotlinTypeDeclara
|
|
|
255
255
|
return declarations;
|
|
256
256
|
};
|
|
257
257
|
|
|
258
|
+
export const hasKotlinHardcodedBackgroundDispatcherUsage = (source: string): boolean => {
|
|
259
|
+
return collectKotlinRegexLines(source, /\bDispatchers\s*\.\s*(?:IO|Default)\b/).length > 0;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export const hasKotlinSupervisorScopeUsage = (source: string): boolean => {
|
|
263
|
+
return collectKotlinRegexLines(source, /\bsupervisorScope\s*(?:<[^>\n]+>\s*)?(?:\(|\{)/).length > 0;
|
|
264
|
+
};
|
|
265
|
+
|
|
258
266
|
export const hasKotlinThreadSleepCall = (source: string): boolean => {
|
|
259
267
|
return scanCodeLikeSource(source, ({ source: kotlinSource, index, current }) => {
|
|
260
268
|
if (current !== 'T') {
|
|
@@ -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 isAndroidDomainOrApplicationPath = (path: string): boolean => {
|
|
103
|
+
return isAndroidKotlinPath(path) && (path.includes('/domain/') || path.includes('/application/'));
|
|
104
|
+
};
|
|
105
|
+
|
|
102
106
|
const isAndroidNonPresentationKotlinPath = (path: string): boolean => {
|
|
103
107
|
return isAndroidKotlinPath(path) && !path.includes('/presentation/');
|
|
104
108
|
};
|
|
@@ -644,6 +648,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
644
648
|
{ 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.' },
|
|
645
649
|
{ 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
650
|
{ 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
|
+
{ 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
|
+
{ 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.' },
|
|
647
653
|
];
|
|
648
654
|
|
|
649
655
|
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, 13);
|
|
7
7
|
|
|
8
8
|
const ids = androidRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -13,6 +13,8 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
13
13
|
'heuristics.android.flow.livedata-state-exposure.ast',
|
|
14
14
|
'heuristics.android.coroutines.manual-scope-in-viewmodel.ast',
|
|
15
15
|
'heuristics.android.coroutines.dispatchers-main-boundary-leak.ast',
|
|
16
|
+
'heuristics.android.coroutines.hardcoded-background-dispatcher.ast',
|
|
17
|
+
'heuristics.android.coroutines.supervisor-scope.ast',
|
|
16
18
|
'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
|
|
17
19
|
'heuristics.android.solid.ocp.discriminator-branching.ast',
|
|
18
20
|
'heuristics.android.solid.dip.concrete-framework-dependency.ast',
|
|
@@ -55,6 +57,14 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
55
57
|
byId.get('heuristics.android.coroutines.dispatchers-main-boundary-leak.ast')?.then.code,
|
|
56
58
|
'HEURISTICS_ANDROID_COROUTINES_DISPATCHERS_MAIN_BOUNDARY_LEAK_AST'
|
|
57
59
|
);
|
|
60
|
+
assert.equal(
|
|
61
|
+
byId.get('heuristics.android.coroutines.hardcoded-background-dispatcher.ast')?.then.code,
|
|
62
|
+
'HEURISTICS_ANDROID_COROUTINES_HARDCODED_BACKGROUND_DISPATCHER_AST'
|
|
63
|
+
);
|
|
64
|
+
assert.equal(
|
|
65
|
+
byId.get('heuristics.android.coroutines.supervisor-scope.ast')?.then.code,
|
|
66
|
+
'HEURISTICS_ANDROID_COROUTINES_SUPERVISOR_SCOPE_AST'
|
|
67
|
+
);
|
|
58
68
|
assert.equal(
|
|
59
69
|
byId.get('heuristics.android.solid.srp.presentation-mixed-responsibilities.ast')?.then.code,
|
|
60
70
|
'HEURISTICS_ANDROID_SOLID_SRP_PRESENTATION_MIXED_RESPONSIBILITIES_AST'
|
|
@@ -115,6 +115,46 @@ export const androidRules: RuleSet = [
|
|
|
115
115
|
code: 'HEURISTICS_ANDROID_COROUTINES_DISPATCHERS_MAIN_BOUNDARY_LEAK_AST',
|
|
116
116
|
},
|
|
117
117
|
},
|
|
118
|
+
{
|
|
119
|
+
id: 'heuristics.android.coroutines.hardcoded-background-dispatcher.ast',
|
|
120
|
+
description:
|
|
121
|
+
'Detects hard-coded Dispatchers.IO or Dispatchers.Default usage in Android domain/application code.',
|
|
122
|
+
severity: 'WARN',
|
|
123
|
+
platform: 'android',
|
|
124
|
+
locked: true,
|
|
125
|
+
when: {
|
|
126
|
+
kind: 'Heuristic',
|
|
127
|
+
where: {
|
|
128
|
+
ruleId: 'heuristics.android.coroutines.hardcoded-background-dispatcher.ast',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
then: {
|
|
132
|
+
kind: 'Finding',
|
|
133
|
+
message:
|
|
134
|
+
'AST heuristic detected hard-coded background dispatcher in Android domain/application code.',
|
|
135
|
+
code: 'HEURISTICS_ANDROID_COROUTINES_HARDCODED_BACKGROUND_DISPATCHER_AST',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'heuristics.android.coroutines.supervisor-scope.ast',
|
|
140
|
+
description:
|
|
141
|
+
'Detects supervisorScope usage in Android domain/application code.',
|
|
142
|
+
severity: 'WARN',
|
|
143
|
+
platform: 'android',
|
|
144
|
+
locked: true,
|
|
145
|
+
when: {
|
|
146
|
+
kind: 'Heuristic',
|
|
147
|
+
where: {
|
|
148
|
+
ruleId: 'heuristics.android.coroutines.supervisor-scope.ast',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
then: {
|
|
152
|
+
kind: 'Finding',
|
|
153
|
+
message:
|
|
154
|
+
'AST heuristic detected supervisorScope usage in Android domain/application code.',
|
|
155
|
+
code: 'HEURISTICS_ANDROID_COROUTINES_SUPERVISOR_SCOPE_AST',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
118
158
|
{
|
|
119
159
|
id: 'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
|
|
120
160
|
description:
|
|
@@ -125,8 +125,9 @@ 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
|
-
✅ `skills.android.guideline.android.dispatchers-main-ui-io-network-disk-default-cpu` debe mapear a señales ejecutables de
|
|
129
|
-
✅
|
|
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
|
+
✅ `skills.android.guideline.android.supervisorscope-errores-no-cancelan-otros-jobs` debe mapear a señal ejecutable de `supervisorScope` en `domain` o `application`.
|
|
130
|
+
✅ 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 y `supervisorScope`, pero no declara todavía cobertura completa de `Flow`, dispatchers, try-catch ni cancelación cooperativa.
|
|
130
131
|
✅ 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.
|
|
131
132
|
|
|
132
133
|
### Enforcement AST inicial de Flow/StateFlow Android
|
|
@@ -226,7 +226,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
226
226
|
),
|
|
227
227
|
'skills.android.guideline.android.dispatchers-main-ui-io-network-disk-default-cpu': heuristicDetector(
|
|
228
228
|
'android.coroutines.dispatchers',
|
|
229
|
-
[
|
|
229
|
+
[
|
|
230
|
+
'heuristics.android.coroutines.dispatchers-main-boundary-leak.ast',
|
|
231
|
+
'heuristics.android.coroutines.hardcoded-background-dispatcher.ast',
|
|
232
|
+
]
|
|
233
|
+
),
|
|
234
|
+
'skills.android.guideline.android.supervisorscope-errores-no-cancelan-otros-jobs': heuristicDetector(
|
|
235
|
+
'android.coroutines.supervisor-scope',
|
|
236
|
+
['heuristics.android.coroutines.supervisor-scope.ast']
|
|
230
237
|
),
|
|
231
238
|
'skills.android.guideline.android.stateflow-estado-mutable-observable': heuristicDetector(
|
|
232
239
|
'android.flow.state-exposure',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.183",
|
|
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": {
|