pumuki 6.3.128 → 6.3.129
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/CHANGELOG.md +8 -0
- package/core/facts/detectors/text/android.test.ts +268 -0
- package/core/facts/detectors/text/android.ts +340 -0
- package/core/facts/extractHeuristicFacts.ts +29 -0
- package/core/rules/presets/heuristics/android.test.ts +56 -1
- package/core/rules/presets/heuristics/android.ts +200 -0
- package/docs/operations/RELEASE_NOTES.md +6 -0
- package/docs/product/CONFIGURATION.md +8 -0
- package/integrations/config/skillsCompilerTemplates.test.ts +14 -0
- package/integrations/config/skillsCompilerTemplates.ts +27 -0
- package/integrations/config/skillsDetectorRegistry.ts +36 -0
- package/integrations/config/skillsMarkdownRules.ts +177 -7
- package/integrations/config/skillsRuleSet.ts +6 -0
- package/integrations/evidence/rulesCoverage.ts +13 -0
- package/integrations/evidence/schema.ts +2 -0
- package/integrations/git/runPlatformGate.ts +31 -4
- package/integrations/policy/gitAtomicityEnforcement.ts +2 -2
- package/integrations/policy/heuristicsEnforcement.ts +2 -2
- package/integrations/policy/sddCompletenessEnforcement.ts +2 -2
- package/integrations/policy/skillsEnforcement.ts +2 -2
- package/integrations/policy/tddBddEnforcement.ts +2 -2
- package/package.json +1 -1
- package/skills.lock.json +146 -527
|
@@ -3,13 +3,24 @@ 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, 14);
|
|
7
7
|
|
|
8
8
|
const ids = androidRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
10
10
|
'heuristics.android.thread-sleep.ast',
|
|
11
11
|
'heuristics.android.globalscope.ast',
|
|
12
12
|
'heuristics.android.run-blocking.ast',
|
|
13
|
+
'heuristics.android.force-unwrap.ast',
|
|
14
|
+
'heuristics.android.java-source.ast',
|
|
15
|
+
'heuristics.android.asynctask-deprecated.ast',
|
|
16
|
+
'heuristics.android.findviewbyid.ast',
|
|
17
|
+
'heuristics.android.rxjava-new-code.ast',
|
|
18
|
+
'heuristics.android.dispatchers-main-ui-io-network-disk-default-cpu.ast',
|
|
19
|
+
'heuristics.android.withcontext-change-dispatcher.ast',
|
|
20
|
+
'heuristics.android.no-console-log.ast',
|
|
21
|
+
'heuristics.android.hardcoded-strings.ast',
|
|
22
|
+
'heuristics.android.no-singleton.ast',
|
|
23
|
+
'heuristics.android.try-catch-manejo-de-errores-en-coroutines.ast',
|
|
13
24
|
]);
|
|
14
25
|
|
|
15
26
|
const byId = new Map(androidRules.map((rule) => [rule.id, rule]));
|
|
@@ -25,6 +36,50 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
25
36
|
byId.get('heuristics.android.run-blocking.ast')?.then.code,
|
|
26
37
|
'HEURISTICS_ANDROID_RUN_BLOCKING_AST'
|
|
27
38
|
);
|
|
39
|
+
assert.equal(
|
|
40
|
+
byId.get('heuristics.android.force-unwrap.ast')?.then.code,
|
|
41
|
+
'HEURISTICS_ANDROID_FORCE_UNWRAP_AST'
|
|
42
|
+
);
|
|
43
|
+
assert.equal(
|
|
44
|
+
byId.get('heuristics.android.java-source.ast')?.then.code,
|
|
45
|
+
'HEURISTICS_ANDROID_JAVA_SOURCE_AST'
|
|
46
|
+
);
|
|
47
|
+
assert.equal(
|
|
48
|
+
byId.get('heuristics.android.asynctask-deprecated.ast')?.then.code,
|
|
49
|
+
'HEURISTICS_ANDROID_ASYNCTASK_DEPRECATED_AST'
|
|
50
|
+
);
|
|
51
|
+
assert.equal(
|
|
52
|
+
byId.get('heuristics.android.findviewbyid.ast')?.then.code,
|
|
53
|
+
'HEURISTICS_ANDROID_FINDVIEWBYID_AST'
|
|
54
|
+
);
|
|
55
|
+
assert.equal(
|
|
56
|
+
byId.get('heuristics.android.rxjava-new-code.ast')?.then.code,
|
|
57
|
+
'HEURISTICS_ANDROID_RXJAVA_NEW_CODE_AST'
|
|
58
|
+
);
|
|
59
|
+
assert.equal(
|
|
60
|
+
byId.get('heuristics.android.dispatchers-main-ui-io-network-disk-default-cpu.ast')?.then.code,
|
|
61
|
+
'HEURISTICS_ANDROID_DISPATCHERS_MAIN_UI_IO_NETWORK_DISK_DEFAULT_CPU_AST'
|
|
62
|
+
);
|
|
63
|
+
assert.equal(
|
|
64
|
+
byId.get('heuristics.android.withcontext-change-dispatcher.ast')?.then.code,
|
|
65
|
+
'HEURISTICS_ANDROID_WITHCONTEXT_CHANGE_DISPATCHER_AST'
|
|
66
|
+
);
|
|
67
|
+
assert.equal(
|
|
68
|
+
byId.get('heuristics.android.no-console-log.ast')?.then.code,
|
|
69
|
+
'HEURISTICS_ANDROID_NO_CONSOLE_LOG_AST'
|
|
70
|
+
);
|
|
71
|
+
assert.equal(
|
|
72
|
+
byId.get('heuristics.android.hardcoded-strings.ast')?.then.code,
|
|
73
|
+
'HEURISTICS_ANDROID_HARDCODED_STRINGS_AST'
|
|
74
|
+
);
|
|
75
|
+
assert.equal(
|
|
76
|
+
byId.get('heuristics.android.no-singleton.ast')?.then.code,
|
|
77
|
+
'HEURISTICS_ANDROID_NO_SINGLETON_AST'
|
|
78
|
+
);
|
|
79
|
+
assert.equal(
|
|
80
|
+
byId.get('heuristics.android.try-catch-manejo-de-errores-en-coroutines.ast')?.then.code,
|
|
81
|
+
'HEURISTICS_ANDROID_TRY_CATCH_MANEJO_DE_ERRORES_EN_COROUTINES_AST'
|
|
82
|
+
);
|
|
28
83
|
|
|
29
84
|
for (const rule of androidRules) {
|
|
30
85
|
assert.equal(rule.platform, 'android');
|
|
@@ -55,4 +55,204 @@ export const androidRules: RuleSet = [
|
|
|
55
55
|
code: 'HEURISTICS_ANDROID_RUN_BLOCKING_AST',
|
|
56
56
|
},
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
id: 'heuristics.android.force-unwrap.ast',
|
|
60
|
+
description: 'Detects Kotlin force unwrap (!!) usage in Android production files.',
|
|
61
|
+
severity: 'WARN',
|
|
62
|
+
platform: 'android',
|
|
63
|
+
locked: true,
|
|
64
|
+
when: {
|
|
65
|
+
kind: 'Heuristic',
|
|
66
|
+
where: {
|
|
67
|
+
ruleId: 'heuristics.android.force-unwrap.ast',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
then: {
|
|
71
|
+
kind: 'Finding',
|
|
72
|
+
message: 'AST heuristic detected Kotlin force unwrap (!!) usage in production code.',
|
|
73
|
+
code: 'HEURISTICS_ANDROID_FORCE_UNWRAP_AST',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'heuristics.android.java-source.ast',
|
|
78
|
+
description: 'Detects Java source in Android production code where Kotlin is required.',
|
|
79
|
+
severity: 'WARN',
|
|
80
|
+
platform: 'android',
|
|
81
|
+
locked: true,
|
|
82
|
+
when: {
|
|
83
|
+
kind: 'Heuristic',
|
|
84
|
+
where: {
|
|
85
|
+
ruleId: 'heuristics.android.java-source.ast',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
then: {
|
|
89
|
+
kind: 'Finding',
|
|
90
|
+
message: 'AST heuristic detected Java source in Android production code where Kotlin is required for new code.',
|
|
91
|
+
code: 'HEURISTICS_ANDROID_JAVA_SOURCE_AST',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'heuristics.android.asynctask-deprecated.ast',
|
|
96
|
+
description: 'Detects AsyncTask usage in Android production code where Coroutines are required.',
|
|
97
|
+
severity: 'WARN',
|
|
98
|
+
platform: 'android',
|
|
99
|
+
locked: true,
|
|
100
|
+
when: {
|
|
101
|
+
kind: 'Heuristic',
|
|
102
|
+
where: {
|
|
103
|
+
ruleId: 'heuristics.android.asynctask-deprecated.ast',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
then: {
|
|
107
|
+
kind: 'Finding',
|
|
108
|
+
message: 'AST heuristic detected AsyncTask usage in Android production code where Coroutines are required.',
|
|
109
|
+
code: 'HEURISTICS_ANDROID_ASYNCTASK_DEPRECATED_AST',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'heuristics.android.findviewbyid.ast',
|
|
114
|
+
description: 'Detects findViewById usage in Android production code where View Binding or Compose is required.',
|
|
115
|
+
severity: 'WARN',
|
|
116
|
+
platform: 'android',
|
|
117
|
+
locked: true,
|
|
118
|
+
when: {
|
|
119
|
+
kind: 'Heuristic',
|
|
120
|
+
where: {
|
|
121
|
+
ruleId: 'heuristics.android.findviewbyid.ast',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
then: {
|
|
125
|
+
kind: 'Finding',
|
|
126
|
+
message: 'AST heuristic detected findViewById usage in Android production code where View Binding or Compose is required.',
|
|
127
|
+
code: 'HEURISTICS_ANDROID_FINDVIEWBYID_AST',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'heuristics.android.rxjava-new-code.ast',
|
|
132
|
+
description: 'Detects RxJava usage in Android production code where Flow is required for new code.',
|
|
133
|
+
severity: 'WARN',
|
|
134
|
+
platform: 'android',
|
|
135
|
+
locked: true,
|
|
136
|
+
when: {
|
|
137
|
+
kind: 'Heuristic',
|
|
138
|
+
where: {
|
|
139
|
+
ruleId: 'heuristics.android.rxjava-new-code.ast',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
then: {
|
|
143
|
+
kind: 'Finding',
|
|
144
|
+
message: 'AST heuristic detected RxJava usage in Android production code where Flow is required for new code.',
|
|
145
|
+
code: 'HEURISTICS_ANDROID_RXJAVA_NEW_CODE_AST',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: 'heuristics.android.dispatchers-main-ui-io-network-disk-default-cpu.ast',
|
|
150
|
+
description:
|
|
151
|
+
'Detects explicit Dispatchers.Main/IO/Default usage in Android production code where dispatcher selection must remain intentional.',
|
|
152
|
+
severity: 'WARN',
|
|
153
|
+
platform: 'android',
|
|
154
|
+
locked: true,
|
|
155
|
+
when: {
|
|
156
|
+
kind: 'Heuristic',
|
|
157
|
+
where: {
|
|
158
|
+
ruleId: 'heuristics.android.dispatchers-main-ui-io-network-disk-default-cpu.ast',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
then: {
|
|
162
|
+
kind: 'Finding',
|
|
163
|
+
message:
|
|
164
|
+
'AST heuristic detected explicit Dispatchers.Main/IO/Default usage in Android production code where dispatcher selection must remain intentional.',
|
|
165
|
+
code: 'HEURISTICS_ANDROID_DISPATCHERS_MAIN_UI_IO_NETWORK_DISK_DEFAULT_CPU_AST',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'heuristics.android.withcontext-change-dispatcher.ast',
|
|
170
|
+
description: 'Detects withContext usage in Android production code where dispatcher switching is intentional.',
|
|
171
|
+
severity: 'WARN',
|
|
172
|
+
platform: 'android',
|
|
173
|
+
locked: true,
|
|
174
|
+
when: {
|
|
175
|
+
kind: 'Heuristic',
|
|
176
|
+
where: {
|
|
177
|
+
ruleId: 'heuristics.android.withcontext-change-dispatcher.ast',
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
then: {
|
|
181
|
+
kind: 'Finding',
|
|
182
|
+
message: 'AST heuristic detected withContext usage in Android production code where dispatcher switching is intentional.',
|
|
183
|
+
code: 'HEURISTICS_ANDROID_WITHCONTEXT_CHANGE_DISPATCHER_AST',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'heuristics.android.no-console-log.ast',
|
|
188
|
+
description: 'Detects Android logging usage in production code without a debug-only guard.',
|
|
189
|
+
severity: 'WARN',
|
|
190
|
+
platform: 'android',
|
|
191
|
+
locked: true,
|
|
192
|
+
when: {
|
|
193
|
+
kind: 'Heuristic',
|
|
194
|
+
where: {
|
|
195
|
+
ruleId: 'heuristics.android.no-console-log.ast',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
then: {
|
|
199
|
+
kind: 'Finding',
|
|
200
|
+
message: 'AST heuristic detected Android logging usage in production code without a debug-only guard.',
|
|
201
|
+
code: 'HEURISTICS_ANDROID_NO_CONSOLE_LOG_AST',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: 'heuristics.android.hardcoded-strings.ast',
|
|
206
|
+
description: 'Detects hardcoded string literal usage in Android production code where strings.xml should be used.',
|
|
207
|
+
severity: 'WARN',
|
|
208
|
+
platform: 'android',
|
|
209
|
+
locked: true,
|
|
210
|
+
when: {
|
|
211
|
+
kind: 'Heuristic',
|
|
212
|
+
where: {
|
|
213
|
+
ruleId: 'heuristics.android.hardcoded-strings.ast',
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
then: {
|
|
217
|
+
kind: 'Finding',
|
|
218
|
+
message: 'AST heuristic detected hardcoded string literal usage in Android production code where strings.xml should be used.',
|
|
219
|
+
code: 'HEURISTICS_ANDROID_HARDCODED_STRINGS_AST',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'heuristics.android.no-singleton.ast',
|
|
224
|
+
description: 'Detects Kotlin singleton object or companion singleton holder usage in Android production code where Hilt or Dagger DI should be used.',
|
|
225
|
+
severity: 'WARN',
|
|
226
|
+
platform: 'android',
|
|
227
|
+
locked: true,
|
|
228
|
+
when: {
|
|
229
|
+
kind: 'Heuristic',
|
|
230
|
+
where: {
|
|
231
|
+
ruleId: 'heuristics.android.no-singleton.ast',
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
then: {
|
|
235
|
+
kind: 'Finding',
|
|
236
|
+
message: 'AST heuristic detected Kotlin singleton object or companion singleton holder usage in Android production code where Hilt or Dagger DI should be used.',
|
|
237
|
+
code: 'HEURISTICS_ANDROID_NO_SINGLETON_AST',
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'heuristics.android.try-catch-manejo-de-errores-en-coroutines.ast',
|
|
242
|
+
description: 'Detects try/catch usage in Android coroutine code where error handling must remain explicit.',
|
|
243
|
+
severity: 'WARN',
|
|
244
|
+
platform: 'android',
|
|
245
|
+
locked: true,
|
|
246
|
+
when: {
|
|
247
|
+
kind: 'Heuristic',
|
|
248
|
+
where: {
|
|
249
|
+
ruleId: 'heuristics.android.try-catch-manejo-de-errores-en-coroutines.ast',
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
then: {
|
|
253
|
+
kind: 'Finding',
|
|
254
|
+
message: 'AST heuristic detected try/catch usage in Android coroutine code where error handling must remain explicit.',
|
|
255
|
+
code: 'HEURISTICS_ANDROID_TRY_CATCH_MANEJO_DE_ERRORES_EN_COROUTINES_AST',
|
|
256
|
+
},
|
|
257
|
+
},
|
|
58
258
|
];
|
|
@@ -4,6 +4,12 @@ This file tracks the active deterministic framework line used in this repository
|
|
|
4
4
|
Canonical release chronology lives in `CHANGELOG.md`.
|
|
5
5
|
This file keeps only the operational highlights and rollout notes that matter while running the framework.
|
|
6
6
|
|
|
7
|
+
### 2026-04-29 (v6.3.129)
|
|
8
|
+
|
|
9
|
+
- **Singletons Android con detector AST real:** `skills.android.no-singleton-usar-inyeccio-n-de-dependencias-hilt-dagger` queda vinculada a un detector que distingue singleton real de `@Module` / `@InstallIn` / `@EntryPoint`.
|
|
10
|
+
- **Regresión cerrada en la suite Android:** el caso de objetos anónimos y companion objects inocuos queda cubierto y la compilación del lock vuelve a producir el binding canónico.
|
|
11
|
+
- **Rollout recomendado:** publicar `pumuki@6.3.129`, repin inmediato en `RuralGo` y revalidar `status` / `doctor` / `audit --stage=PRE_WRITE --json` sobre el consumer.
|
|
12
|
+
|
|
7
13
|
## 2026-04 (CLI stability and macOS notifications)
|
|
8
14
|
|
|
9
15
|
### 2026-04-25 (v6.3.116)
|
|
@@ -66,6 +66,14 @@ Current enforcement scope:
|
|
|
66
66
|
- curated template compilation (`skills.sources.json` -> `skills.lock.json`)
|
|
67
67
|
- stage-aware policy resolution via `resolvePolicyForStage`
|
|
68
68
|
- additive skills-derived rules merged through the shared gate runner
|
|
69
|
+
- strict skills enforcement by default; `PUMUKI_SKILLS_ENFORCEMENT=advisory`
|
|
70
|
+
is an explicit opt-in escape hatch, not the product baseline
|
|
71
|
+
- strict heuristics, TDD/BDD evidence, SDD completeness, and Git atomicity by
|
|
72
|
+
default; their `advisory`/`0` env values are explicit legacy overrides, not
|
|
73
|
+
the product baseline
|
|
74
|
+
- declarative skill rules that represent auditable norms must be mapped to an
|
|
75
|
+
intelligent AST detector; missing detector bindings are reported as
|
|
76
|
+
`unsupported_detector_rule_ids` and block strict gate execution
|
|
69
77
|
- evidence traceability for active skills bundles and policy source/hash in `.ai_evidence.json`
|
|
70
78
|
|
|
71
79
|
Ownership model:
|
|
@@ -67,3 +67,17 @@ test('skillsCompilerTemplates mantiene bundles iOS enterprise esperados', () =>
|
|
|
67
67
|
assert.equal(bundle.rules.every((rule) => rule.platform === 'ios'), true);
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
|
+
|
|
71
|
+
test('skillsCompilerTemplates mantiene el bundle Android baseline esperado', () => {
|
|
72
|
+
const bundle = skillsCompilerTemplates['android-guidelines'];
|
|
73
|
+
assert.ok(bundle, 'Missing Android bundle: android-guidelines');
|
|
74
|
+
assert.deepEqual(bundle.rules.map((rule) => rule.id), [
|
|
75
|
+
'skills.android.no-thread-sleep',
|
|
76
|
+
'skills.android.no-globalscope',
|
|
77
|
+
'skills.android.no-console-log',
|
|
78
|
+
'skills.android.hardcoded-strings-usar-strings-xml',
|
|
79
|
+
'skills.android.no-singleton-usar-inyeccio-n-de-dependencias-hilt-dagger',
|
|
80
|
+
'skills.android.no-runblocking',
|
|
81
|
+
]);
|
|
82
|
+
assert.equal(bundle.rules.every((rule) => rule.platform === 'android'), true);
|
|
83
|
+
});
|
|
@@ -565,6 +565,33 @@ export const skillsCompilerTemplates: Record<string, SkillsCompilerTemplate> = {
|
|
|
565
565
|
stage: 'PRE_PUSH',
|
|
566
566
|
locked: true,
|
|
567
567
|
},
|
|
568
|
+
{
|
|
569
|
+
id: 'skills.android.no-console-log',
|
|
570
|
+
description: 'Disallow logs in Android production code.',
|
|
571
|
+
severity: 'ERROR',
|
|
572
|
+
platform: 'android',
|
|
573
|
+
confidence: 'HIGH',
|
|
574
|
+
stage: 'PRE_PUSH',
|
|
575
|
+
locked: true,
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: 'skills.android.hardcoded-strings-usar-strings-xml',
|
|
579
|
+
description: 'Disallow hardcoded strings in Android production code.',
|
|
580
|
+
severity: 'ERROR',
|
|
581
|
+
platform: 'android',
|
|
582
|
+
confidence: 'HIGH',
|
|
583
|
+
stage: 'PRE_PUSH',
|
|
584
|
+
locked: true,
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
id: 'skills.android.no-singleton-usar-inyeccio-n-de-dependencias-hilt-dagger',
|
|
588
|
+
description: 'Disallow singleton object patterns in Android production code; use Hilt or Dagger DI.',
|
|
589
|
+
severity: 'ERROR',
|
|
590
|
+
platform: 'android',
|
|
591
|
+
confidence: 'HIGH',
|
|
592
|
+
stage: 'PRE_PUSH',
|
|
593
|
+
locked: true,
|
|
594
|
+
},
|
|
568
595
|
{
|
|
569
596
|
id: 'skills.android.no-runblocking',
|
|
570
597
|
description: 'Disallow runBlocking in Android production code.',
|
|
@@ -210,6 +210,42 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
210
210
|
'skills.android.no-runblocking': heuristicDetector('android.run-blocking', [
|
|
211
211
|
'heuristics.android.run-blocking.ast',
|
|
212
212
|
]),
|
|
213
|
+
'skills.android.no-force-unwrap': heuristicDetector('android.force-unwrap', [
|
|
214
|
+
'heuristics.android.force-unwrap.ast',
|
|
215
|
+
]),
|
|
216
|
+
'skills.android.no-java-new-code': heuristicDetector('android.java-source', [
|
|
217
|
+
'heuristics.android.java-source.ast',
|
|
218
|
+
]),
|
|
219
|
+
'skills.android.asynctask-deprecated-usar-coroutines': heuristicDetector('android.asynctask', [
|
|
220
|
+
'heuristics.android.asynctask-deprecated.ast',
|
|
221
|
+
]),
|
|
222
|
+
'skills.android.findviewbyid-view-binding-o-compose': heuristicDetector('android.findviewbyid', [
|
|
223
|
+
'heuristics.android.findviewbyid.ast',
|
|
224
|
+
]),
|
|
225
|
+
'skills.android.rxjava-new-code': heuristicDetector('android.rxjava-new-code', [
|
|
226
|
+
'heuristics.android.rxjava-new-code.ast',
|
|
227
|
+
]),
|
|
228
|
+
'skills.android.dispatchers-main-ui-io-network-disk-default-cpu': heuristicDetector(
|
|
229
|
+
'android.dispatchers-main-ui-io-network-disk-default-cpu',
|
|
230
|
+
['heuristics.android.dispatchers-main-ui-io-network-disk-default-cpu.ast']
|
|
231
|
+
),
|
|
232
|
+
'skills.android.withcontext-change-dispatcher': heuristicDetector('android.withcontext-change-dispatcher', [
|
|
233
|
+
'heuristics.android.withcontext-change-dispatcher.ast',
|
|
234
|
+
]),
|
|
235
|
+
'skills.android.no-console-log': heuristicDetector('android.no-console-log', [
|
|
236
|
+
'heuristics.android.no-console-log.ast',
|
|
237
|
+
]),
|
|
238
|
+
'skills.android.hardcoded-strings-usar-strings-xml': heuristicDetector('android.hardcoded-strings', [
|
|
239
|
+
'heuristics.android.hardcoded-strings.ast',
|
|
240
|
+
]),
|
|
241
|
+
'skills.android.no-singleton-usar-inyeccio-n-de-dependencias-hilt-dagger': heuristicDetector(
|
|
242
|
+
'android.no-singleton',
|
|
243
|
+
['heuristics.android.no-singleton.ast']
|
|
244
|
+
),
|
|
245
|
+
'skills.android.try-catch-manejo-de-errores-en-coroutines': heuristicDetector(
|
|
246
|
+
'android.try-catch-manejo-de-errores-en-coroutines',
|
|
247
|
+
['heuristics.android.try-catch-manejo-de-errores-en-coroutines.ast']
|
|
248
|
+
),
|
|
213
249
|
};
|
|
214
250
|
|
|
215
251
|
export const listSkillsDetectorBindings = (): ReadonlyArray<{
|
|
@@ -192,7 +192,12 @@ const normalizeKnownRuleTarget = (
|
|
|
192
192
|
const includes = (needle: string): boolean => normalizedDescription.includes(needle);
|
|
193
193
|
|
|
194
194
|
if (platform === 'ios') {
|
|
195
|
-
if (
|
|
195
|
+
if (
|
|
196
|
+
includes('force unwrap') ||
|
|
197
|
+
includes('force unwrapping') ||
|
|
198
|
+
includes('no force unwrap') ||
|
|
199
|
+
includes('no force unwrapping')
|
|
200
|
+
) {
|
|
196
201
|
return 'skills.ios.no-force-unwrap';
|
|
197
202
|
}
|
|
198
203
|
if (includes('force try')) {
|
|
@@ -354,8 +359,10 @@ const normalizeKnownRuleTarget = (
|
|
|
354
359
|
includes('prefer import testing') ||
|
|
355
360
|
includes('prefer test functions over test methods') ||
|
|
356
361
|
includes('test functions over test methods') ||
|
|
362
|
+
includes('new xctest only unit tests') ||
|
|
357
363
|
includes('xctest-only unit tests') ||
|
|
358
364
|
includes('new xctest-only unit tests') ||
|
|
365
|
+
includes('xctest solo para proyectos legacy') ||
|
|
359
366
|
includes('xctest only for ui') ||
|
|
360
367
|
includes('xctest only for ui performance')
|
|
361
368
|
) {
|
|
@@ -426,12 +433,111 @@ const normalizeKnownRuleTarget = (
|
|
|
426
433
|
}
|
|
427
434
|
|
|
428
435
|
if (platform === 'android') {
|
|
436
|
+
if (
|
|
437
|
+
includes('asynctask') ||
|
|
438
|
+
includes('deprecated, usar coroutines') ||
|
|
439
|
+
includes('usar coroutines')
|
|
440
|
+
) {
|
|
441
|
+
return 'skills.android.asynctask-deprecated-usar-coroutines';
|
|
442
|
+
}
|
|
443
|
+
if (
|
|
444
|
+
includes('java en codigo nuevo') ||
|
|
445
|
+
includes('java en co digo nuevo') ||
|
|
446
|
+
includes('no java en codigo nuevo') ||
|
|
447
|
+
includes('no java en co digo nuevo') ||
|
|
448
|
+
includes('solo kotlin') ||
|
|
449
|
+
includes('kotlin para todo')
|
|
450
|
+
) {
|
|
451
|
+
return 'skills.android.no-java-new-code';
|
|
452
|
+
}
|
|
453
|
+
if (
|
|
454
|
+
includes('rxjava') ||
|
|
455
|
+
includes('usar flow') ||
|
|
456
|
+
includes('usar flows') ||
|
|
457
|
+
includes('flow en nuevo codigo') ||
|
|
458
|
+
includes('flow en nuevo co digo')
|
|
459
|
+
) {
|
|
460
|
+
return 'skills.android.rxjava-new-code';
|
|
461
|
+
}
|
|
462
|
+
if (
|
|
463
|
+
includes('dispatchers') ||
|
|
464
|
+
includes('dispatcher') ||
|
|
465
|
+
includes('main ui') ||
|
|
466
|
+
includes('io network') ||
|
|
467
|
+
includes('default cpu') ||
|
|
468
|
+
includes('withcontext')
|
|
469
|
+
) {
|
|
470
|
+
if (includes('withcontext') || includes('cambiar dispatcher')) {
|
|
471
|
+
return 'skills.android.withcontext-change-dispatcher';
|
|
472
|
+
}
|
|
473
|
+
return 'skills.android.dispatchers-main-ui-io-network-disk-default-cpu';
|
|
474
|
+
}
|
|
475
|
+
if (
|
|
476
|
+
includes('try-catch') ||
|
|
477
|
+
includes('manejo de errores en coroutines') ||
|
|
478
|
+
includes('errores en coroutines') ||
|
|
479
|
+
includes('error handling in coroutines')
|
|
480
|
+
) {
|
|
481
|
+
return 'skills.android.try-catch-manejo-de-errores-en-coroutines';
|
|
482
|
+
}
|
|
483
|
+
if (
|
|
484
|
+
includes('findviewbyid') ||
|
|
485
|
+
includes('view binding') ||
|
|
486
|
+
includes('viewbinding') ||
|
|
487
|
+
includes('compose') ||
|
|
488
|
+
includes('no xml layouts') ||
|
|
489
|
+
includes('xml layouts')
|
|
490
|
+
) {
|
|
491
|
+
return 'skills.android.findviewbyid-view-binding-o-compose';
|
|
492
|
+
}
|
|
493
|
+
if (
|
|
494
|
+
includes('force unwrap') ||
|
|
495
|
+
includes('force unwrapping') ||
|
|
496
|
+
includes('no force unwrap') ||
|
|
497
|
+
includes('no force unwrapping')
|
|
498
|
+
) {
|
|
499
|
+
return 'skills.android.no-force-unwrap';
|
|
500
|
+
}
|
|
429
501
|
if (includes('thread sleep') || includes('thread.sleep')) {
|
|
430
502
|
return 'skills.android.no-thread-sleep';
|
|
431
503
|
}
|
|
432
504
|
if (includes('globalscope') || includes('global scope')) {
|
|
433
505
|
return 'skills.android.no-globalscope';
|
|
434
506
|
}
|
|
507
|
+
if (
|
|
508
|
+
includes('console log') ||
|
|
509
|
+
includes('console.log') ||
|
|
510
|
+
includes('logs ad-hoc') ||
|
|
511
|
+
includes('no logs en produccion') ||
|
|
512
|
+
includes('no logs en producción') ||
|
|
513
|
+
includes('no logs en produccio n') ||
|
|
514
|
+
includes('timber.d') ||
|
|
515
|
+
includes('buildconfig.debug') ||
|
|
516
|
+
includes('android.util.log')
|
|
517
|
+
) {
|
|
518
|
+
return 'skills.android.no-console-log';
|
|
519
|
+
}
|
|
520
|
+
if (
|
|
521
|
+
includes('no singleton') ||
|
|
522
|
+
includes('singleton') ||
|
|
523
|
+
includes('singletons everywhere') ||
|
|
524
|
+
includes('hilt dagger') ||
|
|
525
|
+
includes('hilt/dagger') ||
|
|
526
|
+
includes('inyeccion de dependencias') ||
|
|
527
|
+
includes('inyecion de dependencias') ||
|
|
528
|
+
includes('usar inyeccion de dependencias') ||
|
|
529
|
+
includes('usar inyecion de dependencias')
|
|
530
|
+
) {
|
|
531
|
+
return 'skills.android.no-singleton-usar-inyeccio-n-de-dependencias-hilt-dagger';
|
|
532
|
+
}
|
|
533
|
+
if (
|
|
534
|
+
includes('hardcoded strings') ||
|
|
535
|
+
includes('hardcoded string') ||
|
|
536
|
+
includes('usar strings.xml') ||
|
|
537
|
+
includes('usar strings xml')
|
|
538
|
+
) {
|
|
539
|
+
return 'skills.android.hardcoded-strings-usar-strings-xml';
|
|
540
|
+
}
|
|
435
541
|
if (includes('runblocking') || includes('run blocking')) {
|
|
436
542
|
return 'skills.android.no-runblocking';
|
|
437
543
|
}
|
|
@@ -440,22 +546,86 @@ const normalizeKnownRuleTarget = (
|
|
|
440
546
|
|
|
441
547
|
if (platform === 'backend' || platform === 'frontend') {
|
|
442
548
|
const prefix = platform === 'backend' ? 'skills.backend' : 'skills.frontend';
|
|
443
|
-
if (
|
|
549
|
+
if (
|
|
550
|
+
includes('solid') ||
|
|
551
|
+
includes('single responsibility') ||
|
|
552
|
+
includes('srp') ||
|
|
553
|
+
includes('controllers delgados') ||
|
|
554
|
+
includes('logic in controllers') ||
|
|
555
|
+
includes('lo gica en controllers') ||
|
|
556
|
+
includes('lógica en controllers') ||
|
|
557
|
+
includes('mover a servicios') ||
|
|
558
|
+
includes('componentes pequeños') ||
|
|
559
|
+
includes('componentes pequen os') ||
|
|
560
|
+
includes('custom hooks para logica reutilizable') ||
|
|
561
|
+
includes('custom hooks para lógica reutilizable') ||
|
|
562
|
+
includes('prop drilling excesivo') ||
|
|
563
|
+
includes('anemic domain models') ||
|
|
564
|
+
includes('entidades solo con getters') ||
|
|
565
|
+
includes('no logica de negocio en repositorios') ||
|
|
566
|
+
includes('no lógica de negocio en repositorios')
|
|
567
|
+
) {
|
|
444
568
|
return `${prefix}.no-solid-violations`;
|
|
445
569
|
}
|
|
446
|
-
if (
|
|
570
|
+
if (
|
|
571
|
+
includes('clean architecture') ||
|
|
572
|
+
includes('clean code') ||
|
|
573
|
+
includes('clean por feature') ||
|
|
574
|
+
includes('domain application infrastructure presentation') ||
|
|
575
|
+
includes('presentation application domain infrastructure') ||
|
|
576
|
+
includes('dependencias hacia adentro') ||
|
|
577
|
+
includes('capas presentacion') ||
|
|
578
|
+
includes('capas presentación') ||
|
|
579
|
+
includes('infrastructure domain') ||
|
|
580
|
+
includes('feature first') ||
|
|
581
|
+
includes('bounded context') ||
|
|
582
|
+
includes('shared kernel')
|
|
583
|
+
) {
|
|
447
584
|
return `${prefix}.enforce-clean-architecture`;
|
|
448
585
|
}
|
|
449
|
-
if (
|
|
586
|
+
if (
|
|
587
|
+
includes('god classes') ||
|
|
588
|
+
includes('god class') ||
|
|
589
|
+
includes('servicios que mezclan responsabilidades') ||
|
|
590
|
+
includes('mezclan responsabilidades') ||
|
|
591
|
+
includes('modulos cohesivos') ||
|
|
592
|
+
includes('módulos cohesivos') ||
|
|
593
|
+
includes('mo dulos cohesivos') ||
|
|
594
|
+
includes('un modulo por feature') ||
|
|
595
|
+
includes('un módulo por feature') ||
|
|
596
|
+
includes('un mo dulo por feature')
|
|
597
|
+
) {
|
|
450
598
|
return `${prefix}.no-god-classes`;
|
|
451
599
|
}
|
|
452
|
-
if (
|
|
600
|
+
if (
|
|
601
|
+
includes('empty catch') ||
|
|
602
|
+
includes('catch vacios') ||
|
|
603
|
+
includes('catch vacíos') ||
|
|
604
|
+
includes('try-catch silenciosos') ||
|
|
605
|
+
includes('silenciar errores') ||
|
|
606
|
+
includes('siempre loggear o propagar')
|
|
607
|
+
) {
|
|
453
608
|
return `${prefix}.no-empty-catch`;
|
|
454
609
|
}
|
|
455
|
-
if (
|
|
610
|
+
if (
|
|
611
|
+
includes('console log') ||
|
|
612
|
+
includes('console.log') ||
|
|
613
|
+
includes('logs ad-hoc') ||
|
|
614
|
+
includes('no logs en produccion') ||
|
|
615
|
+
includes('no logs en producción') ||
|
|
616
|
+
includes('no logs en produccio n')
|
|
617
|
+
) {
|
|
456
618
|
return `${prefix}.no-console-log`;
|
|
457
619
|
}
|
|
458
|
-
if (
|
|
620
|
+
if (
|
|
621
|
+
includes('explicit any') ||
|
|
622
|
+
includes(' no any') ||
|
|
623
|
+
includes('avoid any') ||
|
|
624
|
+
includes('usar unknown') ||
|
|
625
|
+
includes('type guard') ||
|
|
626
|
+
includes('tipos explicitos') ||
|
|
627
|
+
includes('tipos explícitos')
|
|
628
|
+
) {
|
|
459
629
|
return `${prefix}.avoid-explicit-any`;
|
|
460
630
|
}
|
|
461
631
|
return null;
|
|
@@ -21,6 +21,7 @@ export type SkillsRuleSetLoadResult = {
|
|
|
21
21
|
mappedHeuristicRuleIds: ReadonlySet<string>;
|
|
22
22
|
requiresHeuristicFacts: boolean;
|
|
23
23
|
unsupportedAutoRuleIds?: ReadonlyArray<string>;
|
|
24
|
+
unsupportedDetectorRuleIds?: ReadonlyArray<string>;
|
|
24
25
|
registryCoverage?: {
|
|
25
26
|
contract: 'AUTO_RUNTIME_RULES_FOR_STAGE';
|
|
26
27
|
stage: Exclude<GateStage, 'STAGED'>;
|
|
@@ -502,6 +503,7 @@ const emptyResult = (): SkillsRuleSetLoadResult => {
|
|
|
502
503
|
mappedHeuristicRuleIds: new Set<string>(),
|
|
503
504
|
requiresHeuristicFacts: false,
|
|
504
505
|
unsupportedAutoRuleIds: [],
|
|
506
|
+
unsupportedDetectorRuleIds: [],
|
|
505
507
|
};
|
|
506
508
|
};
|
|
507
509
|
|
|
@@ -534,6 +536,7 @@ export const loadSkillsRuleSetForStage = (
|
|
|
534
536
|
const rulesById = new Map<string, RuleDefinition>();
|
|
535
537
|
const mappedHeuristicRuleIds = new Set<string>();
|
|
536
538
|
const unsupportedAutoRuleIds = new Set<string>();
|
|
539
|
+
const unsupportedDetectorRuleIds = new Set<string>();
|
|
537
540
|
const registryRuleIds = new Set<string>();
|
|
538
541
|
const registryAutoRuleIds = new Set<string>();
|
|
539
542
|
const registryDeclarativeRuleIds = new Set<string>();
|
|
@@ -565,11 +568,13 @@ export const loadSkillsRuleSetForStage = (
|
|
|
565
568
|
continue;
|
|
566
569
|
}
|
|
567
570
|
if (evaluationMode !== 'AUTO') {
|
|
571
|
+
unsupportedDetectorRuleIds.add(compiledRule.id);
|
|
568
572
|
continue;
|
|
569
573
|
}
|
|
570
574
|
stageApplicableAutoRuleIds.add(compiledRule.id);
|
|
571
575
|
if (evaluationMode === 'AUTO' && mappedRuleIds.length === 0) {
|
|
572
576
|
unsupportedAutoRuleIds.add(compiledRule.id);
|
|
577
|
+
unsupportedDetectorRuleIds.add(compiledRule.id);
|
|
573
578
|
continue;
|
|
574
579
|
}
|
|
575
580
|
|
|
@@ -602,6 +607,7 @@ export const loadSkillsRuleSetForStage = (
|
|
|
602
607
|
mappedHeuristicRuleIds,
|
|
603
608
|
requiresHeuristicFacts: mappedHeuristicRuleIds.size > 0,
|
|
604
609
|
unsupportedAutoRuleIds: [...unsupportedAutoRuleIds].sort(),
|
|
610
|
+
unsupportedDetectorRuleIds: [...unsupportedDetectorRuleIds].sort(),
|
|
605
611
|
registryCoverage: {
|
|
606
612
|
contract: 'AUTO_RUNTIME_RULES_FOR_STAGE',
|
|
607
613
|
stage,
|