pumuki-ast-hooks 5.3.17 → 5.3.19
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/docs/VIOLATIONS_RESOLUTION_PLAN.md +23 -25
- package/docs/alerting-system.md +51 -0
- package/docs/observability.md +36 -0
- package/docs/type-safety.md +8 -0
- package/package.json +2 -2
- package/scripts/hooks-system/.AI_TOKEN_STATUS.txt +1 -1
- package/scripts/hooks-system/.audit-reports/notifications.log +11 -0
- package/scripts/hooks-system/.audit-reports/token-monitor.log +72 -0
- package/scripts/hooks-system/application/services/GitTreeState.js +139 -13
- package/scripts/hooks-system/application/services/HookSystemScheduler.js +43 -0
- package/scripts/hooks-system/application/services/PlaybookRunner.js +1 -1
- package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +1 -1
- package/scripts/hooks-system/application/services/logging/AuditLogger.js +173 -0
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitor.js +1 -0
- package/scripts/hooks-system/bin/__tests__/evidence-update.spec.js +49 -0
- package/scripts/hooks-system/bin/cli.js +1 -15
- package/scripts/hooks-system/config/env.js +33 -0
- package/scripts/hooks-system/domain/events/__tests__/EventBus.spec.js +33 -0
- package/scripts/hooks-system/domain/events/index.js +6 -16
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSASTIntelligentAnalyzer.spec.js +66 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureRules.js +24 -86
|
@@ -7,8 +7,6 @@ class iOSArchitectureRules {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
runRules(files) {
|
|
10
|
-
console.log(`[iOS Architecture] Detected pattern: ${this.pattern}`);
|
|
11
|
-
|
|
12
10
|
switch (this.pattern) {
|
|
13
11
|
case 'FEATURE_FIRST_CLEAN_DDD':
|
|
14
12
|
this.checkFeatureFirstCleanDDDRules(files);
|
|
@@ -16,9 +14,6 @@ class iOSArchitectureRules {
|
|
|
16
14
|
case 'MVVM':
|
|
17
15
|
this.checkMVVMRules(files);
|
|
18
16
|
break;
|
|
19
|
-
case 'MVVM-C':
|
|
20
|
-
this.checkMVVMCRules(files);
|
|
21
|
-
break;
|
|
22
17
|
case 'MVP':
|
|
23
18
|
this.checkMVPRules(files);
|
|
24
19
|
break;
|
|
@@ -38,7 +33,7 @@ class iOSArchitectureRules {
|
|
|
38
33
|
this.checkMixedArchitectureRules(files);
|
|
39
34
|
break;
|
|
40
35
|
default:
|
|
41
|
-
|
|
36
|
+
break;
|
|
42
37
|
}
|
|
43
38
|
}
|
|
44
39
|
|
|
@@ -68,10 +63,10 @@ class iOSArchitectureRules {
|
|
|
68
63
|
});
|
|
69
64
|
|
|
70
65
|
if (file.includes('/domain/') && !file.includes('/entities/') &&
|
|
71
|
-
|
|
66
|
+
!file.includes('/value-objects/') && !file.includes('/interfaces/')) {
|
|
72
67
|
pushFinding(this.findings, {
|
|
73
68
|
ruleId: 'ios.clean.domain_structure',
|
|
74
|
-
severity: '
|
|
69
|
+
severity: 'critical',
|
|
75
70
|
message: 'Archivo en domain/ sin estructura correcta. Usar entities/, value-objects/, interfaces/',
|
|
76
71
|
filePath: file,
|
|
77
72
|
line: 1
|
|
@@ -86,7 +81,7 @@ class iOSArchitectureRules {
|
|
|
86
81
|
if (hasProperties > 3 && hasMethods === 0) {
|
|
87
82
|
pushFinding(this.findings, {
|
|
88
83
|
ruleId: 'ios.ddd.anemic_entity',
|
|
89
|
-
severity: '
|
|
84
|
+
severity: 'critical',
|
|
90
85
|
message: 'Entity anémica (solo properties, sin comportamiento). Añadir métodos de negocio.',
|
|
91
86
|
filePath: file,
|
|
92
87
|
line: 1,
|
|
@@ -99,7 +94,7 @@ class iOSArchitectureRules {
|
|
|
99
94
|
if (content.includes('var ') && !content.includes('private(set)')) {
|
|
100
95
|
pushFinding(this.findings, {
|
|
101
96
|
ruleId: 'ios.ddd.mutable_value_object',
|
|
102
|
-
severity: '
|
|
97
|
+
severity: 'critical',
|
|
103
98
|
message: 'Value Object con properties mutables. VOs deben ser inmutables (usar let).',
|
|
104
99
|
filePath: file,
|
|
105
100
|
line: 1,
|
|
@@ -110,7 +105,7 @@ class iOSArchitectureRules {
|
|
|
110
105
|
if (!content.includes('init(') || !content.includes('throw')) {
|
|
111
106
|
pushFinding(this.findings, {
|
|
112
107
|
ruleId: 'ios.ddd.value_object_no_validation',
|
|
113
|
-
severity: '
|
|
108
|
+
severity: 'critical',
|
|
114
109
|
message: 'Value Object sin validación en init(). VOs deben garantizar invariantes.',
|
|
115
110
|
filePath: file,
|
|
116
111
|
line: 1,
|
|
@@ -128,7 +123,7 @@ class iOSArchitectureRules {
|
|
|
128
123
|
if (!content.includes('func execute(')) {
|
|
129
124
|
pushFinding(this.findings, {
|
|
130
125
|
ruleId: 'ios.ddd.usecase_missing_execute',
|
|
131
|
-
severity: '
|
|
126
|
+
severity: 'critical',
|
|
132
127
|
message: 'Use Case sin método execute(). Convención: func execute(input: Input) async throws -> Output',
|
|
133
128
|
filePath: file,
|
|
134
129
|
line: 1
|
|
@@ -138,7 +133,7 @@ class iOSArchitectureRules {
|
|
|
138
133
|
if (content.includes('UIKit') || content.includes('SwiftUI')) {
|
|
139
134
|
pushFinding(this.findings, {
|
|
140
135
|
ruleId: 'ios.clean.usecase_ui_dependency',
|
|
141
|
-
severity: '
|
|
136
|
+
severity: 'critical',
|
|
142
137
|
message: 'Use Case depende de UI framework. Application layer debe ser UI-agnostic.',
|
|
143
138
|
filePath: file,
|
|
144
139
|
line: 1
|
|
@@ -150,7 +145,7 @@ class iOSArchitectureRules {
|
|
|
150
145
|
if (!file.includes('/infrastructure/')) {
|
|
151
146
|
pushFinding(this.findings, {
|
|
152
147
|
ruleId: 'ios.clean.repository_wrong_layer',
|
|
153
|
-
severity: '
|
|
148
|
+
severity: 'critical',
|
|
154
149
|
message: 'Repository implementation fuera de infrastructure/. Mover a infrastructure/repositories/',
|
|
155
150
|
filePath: file,
|
|
156
151
|
line: 1
|
|
@@ -162,7 +157,7 @@ class iOSArchitectureRules {
|
|
|
162
157
|
if (!file.includes('/domain/')) {
|
|
163
158
|
pushFinding(this.findings, {
|
|
164
159
|
ruleId: 'ios.clean.repository_interface_wrong_layer',
|
|
165
|
-
severity: '
|
|
160
|
+
severity: 'critical',
|
|
166
161
|
message: 'Repository protocol fuera de domain/. Mover a domain/interfaces/',
|
|
167
162
|
filePath: file,
|
|
168
163
|
line: 1
|
|
@@ -174,7 +169,7 @@ class iOSArchitectureRules {
|
|
|
174
169
|
if (!file.includes('/application/')) {
|
|
175
170
|
pushFinding(this.findings, {
|
|
176
171
|
ruleId: 'ios.clean.dto_wrong_layer',
|
|
177
|
-
severity: '
|
|
172
|
+
severity: 'critical',
|
|
178
173
|
message: 'DTO fuera de application/. Mover a application/dto/',
|
|
179
174
|
filePath: file,
|
|
180
175
|
line: 1
|
|
@@ -213,7 +208,7 @@ class iOSArchitectureRules {
|
|
|
213
208
|
if (complexMethods.length > 0) {
|
|
214
209
|
pushFinding(this.findings, {
|
|
215
210
|
ruleId: 'ios.clean.infrastructure_business_logic',
|
|
216
|
-
severity: '
|
|
211
|
+
severity: 'critical',
|
|
217
212
|
message: 'Infrastructure con lógica de negocio compleja. Mover a domain/ o application/',
|
|
218
213
|
filePath: file,
|
|
219
214
|
line: 1,
|
|
@@ -226,7 +221,7 @@ class iOSArchitectureRules {
|
|
|
226
221
|
if (content.includes('Entity') && !content.includes('DTO') && !content.includes('Dto')) {
|
|
227
222
|
pushFinding(this.findings, {
|
|
228
223
|
ruleId: 'ios.clean.presentation_uses_entity',
|
|
229
|
-
severity: '
|
|
224
|
+
severity: 'critical',
|
|
230
225
|
message: 'Presentation usando Entities de domain directamente. Usar DTOs para desacoplar.',
|
|
231
226
|
filePath: file,
|
|
232
227
|
line: 1,
|
|
@@ -245,7 +240,7 @@ class iOSArchitectureRules {
|
|
|
245
240
|
if (!content.includes('ObservableObject') && !content.includes('@Observable')) {
|
|
246
241
|
pushFinding(this.findings, {
|
|
247
242
|
ruleId: 'ios.mvvm.viewmodel_not_observable',
|
|
248
|
-
severity: '
|
|
243
|
+
severity: 'critical',
|
|
249
244
|
message: 'ViewModel debe conformar ObservableObject o usar @Observable macro (iOS 17+)',
|
|
250
245
|
filePath: file,
|
|
251
246
|
line: 1
|
|
@@ -255,7 +250,7 @@ class iOSArchitectureRules {
|
|
|
255
250
|
if (content.match(/import\s+UIKit/) && !content.includes('#if canImport(UIKit)')) {
|
|
256
251
|
pushFinding(this.findings, {
|
|
257
252
|
ruleId: 'ios.mvvm.viewmodel_uikit_dependency',
|
|
258
|
-
severity: '
|
|
253
|
+
severity: 'critical',
|
|
259
254
|
message: 'ViewModel NO debe depender de UIKit. Usar tipos agnósticos de plataforma.',
|
|
260
255
|
filePath: file,
|
|
261
256
|
line: content.split('\n').findIndex(line => line.includes('import UIKit')) + 1
|
|
@@ -266,7 +261,7 @@ class iOSArchitectureRules {
|
|
|
266
261
|
if (classMatch && content.includes('var ') && !content.includes('@Published')) {
|
|
267
262
|
pushFinding(this.findings, {
|
|
268
263
|
ruleId: 'ios.mvvm.missing_published',
|
|
269
|
-
severity: '
|
|
264
|
+
severity: 'critical',
|
|
270
265
|
message: 'ViewModel properties que cambian deben usar @Published para notificar a la View',
|
|
271
266
|
filePath: file,
|
|
272
267
|
line: 1
|
|
@@ -276,7 +271,7 @@ class iOSArchitectureRules {
|
|
|
276
271
|
|
|
277
272
|
if (file.includes('View.swift') || content.includes('struct ') && content.includes(': View')) {
|
|
278
273
|
const hasBusinessLogic =
|
|
279
|
-
/func\s+\w+\([^)]*\)\s*->\s*\w+\s*{[\s\S]{100,}/.test(content) ||
|
|
274
|
+
/func\s+\w+\([^)]*\)\s*->\s*\w+\s*{[\s\S]{100,}/.test(content) ||
|
|
280
275
|
content.includes('URLSession') ||
|
|
281
276
|
content.includes('CoreData') ||
|
|
282
277
|
/\.save\(|\.fetch\(|\.delete\(/.test(content);
|
|
@@ -284,7 +279,7 @@ class iOSArchitectureRules {
|
|
|
284
279
|
if (hasBusinessLogic) {
|
|
285
280
|
pushFinding(this.findings, {
|
|
286
281
|
ruleId: 'ios.mvvm.view_business_logic',
|
|
287
|
-
severity: '
|
|
282
|
+
severity: 'critical',
|
|
288
283
|
message: 'View contiene lógica de negocio. Mover al ViewModel.',
|
|
289
284
|
filePath: file,
|
|
290
285
|
line: 1
|
|
@@ -294,63 +289,6 @@ class iOSArchitectureRules {
|
|
|
294
289
|
});
|
|
295
290
|
}
|
|
296
291
|
|
|
297
|
-
checkMVVMCRules(files) {
|
|
298
|
-
this.checkMVVMRules(files);
|
|
299
|
-
|
|
300
|
-
files.forEach(file => {
|
|
301
|
-
const content = this.readFile(file);
|
|
302
|
-
|
|
303
|
-
if (file.includes('Coordinator.swift')) {
|
|
304
|
-
if (!content.includes('protocol Coordinator') && !content.includes(': Coordinator')) {
|
|
305
|
-
pushFinding(this.findings, {
|
|
306
|
-
ruleId: 'ios.mvvmc.coordinator_protocol',
|
|
307
|
-
severity: 'medium',
|
|
308
|
-
message: 'Coordinator debe conformar protocol Coordinator con start() y navigate(to:)',
|
|
309
|
-
filePath: file,
|
|
310
|
-
line: 1
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (!/func\s+start\(\)/.test(content)) {
|
|
315
|
-
pushFinding(this.findings, {
|
|
316
|
-
ruleId: 'ios.mvvmc.coordinator_missing_start',
|
|
317
|
-
severity: 'high',
|
|
318
|
-
message: 'Coordinator debe implementar func start() para iniciar el flujo',
|
|
319
|
-
filePath: file,
|
|
320
|
-
line: 1
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const hasBusinessLogic =
|
|
325
|
-
content.includes('URLSession') ||
|
|
326
|
-
content.includes('CoreData') ||
|
|
327
|
-
/\.save\(|\.fetch\(|\.delete\(/.test(content);
|
|
328
|
-
|
|
329
|
-
if (hasBusinessLogic) {
|
|
330
|
-
pushFinding(this.findings, {
|
|
331
|
-
ruleId: 'ios.mvvmc.coordinator_business_logic',
|
|
332
|
-
severity: 'high',
|
|
333
|
-
message: 'Coordinator NO debe contener lógica de negocio. Solo navegación.',
|
|
334
|
-
filePath: file,
|
|
335
|
-
line: 1
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (file.includes('ViewModel.swift')) {
|
|
341
|
-
if (content.includes('navigationController') || content.includes('.present(')) {
|
|
342
|
-
pushFinding(this.findings, {
|
|
343
|
-
ruleId: 'ios.mvvmc.viewmodel_navigation',
|
|
344
|
-
severity: 'high',
|
|
345
|
-
message: 'ViewModel NO debe manejar navegación. Delegar al Coordinator.',
|
|
346
|
-
filePath: file,
|
|
347
|
-
line: 1
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
292
|
checkMVPRules(files) {
|
|
355
293
|
files.forEach(file => {
|
|
356
294
|
const content = this.readFile(file);
|
|
@@ -359,7 +297,7 @@ class iOSArchitectureRules {
|
|
|
359
297
|
if (!content.includes('protocol ') && content.includes('View')) {
|
|
360
298
|
pushFinding(this.findings, {
|
|
361
299
|
ruleId: 'ios.mvp.view_not_protocol',
|
|
362
|
-
severity: '
|
|
300
|
+
severity: 'critical',
|
|
363
301
|
message: 'En MVP, View debe ser un protocol implementado por ViewController',
|
|
364
302
|
filePath: file,
|
|
365
303
|
line: 1
|
|
@@ -371,7 +309,7 @@ class iOSArchitectureRules {
|
|
|
371
309
|
if (content.includes('var view:') && !content.includes('weak var view')) {
|
|
372
310
|
pushFinding(this.findings, {
|
|
373
311
|
ruleId: 'ios.mvp.presenter_strong_view',
|
|
374
|
-
severity: '
|
|
312
|
+
severity: 'critical',
|
|
375
313
|
message: 'Presenter debe tener referencia weak a View para evitar retain cycles',
|
|
376
314
|
filePath: file,
|
|
377
315
|
line: content.split('\n').findIndex(line => line.includes('var view:')) + 1
|
|
@@ -385,7 +323,7 @@ class iOSArchitectureRules {
|
|
|
385
323
|
if (hasLogic < 3) {
|
|
386
324
|
pushFinding(this.findings, {
|
|
387
325
|
ruleId: 'ios.mvp.presenter_thin',
|
|
388
|
-
severity: '
|
|
326
|
+
severity: 'critical',
|
|
389
327
|
message: 'Presenter parece tener poca lógica. En MVP, Presenter debe contener toda la lógica de presentación.',
|
|
390
328
|
filePath: file,
|
|
391
329
|
line: 1
|
|
@@ -402,7 +340,7 @@ class iOSArchitectureRules {
|
|
|
402
340
|
if (hasBusinessLogic) {
|
|
403
341
|
pushFinding(this.findings, {
|
|
404
342
|
ruleId: 'ios.mvp.viewcontroller_business_logic',
|
|
405
|
-
severity: '
|
|
343
|
+
severity: 'critical',
|
|
406
344
|
message: 'ViewController NO debe contener lógica de negocio. Delegar al Presenter.',
|
|
407
345
|
filePath: file,
|
|
408
346
|
line: 1
|
|
@@ -494,7 +432,7 @@ class iOSArchitectureRules {
|
|
|
494
432
|
|
|
495
433
|
if (file.includes('Entity.swift')) {
|
|
496
434
|
const hasMethods = (content.match(/func\s+/g) || []).length;
|
|
497
|
-
if (hasMethods > 2) {
|
|
435
|
+
if (hasMethods > 2) {
|
|
498
436
|
pushFinding(this.findings, {
|
|
499
437
|
ruleId: 'ios.viper.entity_with_logic',
|
|
500
438
|
severity: 'medium',
|
|
@@ -537,7 +475,7 @@ class iOSArchitectureRules {
|
|
|
537
475
|
|
|
538
476
|
if (content.includes(': Reducer')) {
|
|
539
477
|
if ((content.includes('URLSession') || content.includes('async ')) &&
|
|
540
|
-
|
|
478
|
+
!content.includes('Effect')) {
|
|
541
479
|
pushFinding(this.findings, {
|
|
542
480
|
ruleId: 'ios.tca.missing_effect',
|
|
543
481
|
severity: 'high',
|