pumuki-ast-hooks 6.1.11 → 6.2.0
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/README.md +13 -13
- package/docs/SEVERITY_AUDIT.md +137 -0
- package/package.json +18 -18
- package/scripts/hooks-system/.audit-reports/auto-recovery.log +2 -0
- package/scripts/hooks-system/.audit-reports/install-wizard.log +8 -0
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +56 -0
- package/scripts/hooks-system/infrastructure/ast/ios/__tests__/ios-srp-helpers.spec.js +62 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +23 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseChecks.js +20 -6
- package/scripts/hooks-system/infrastructure/ast/ios/ast-ios.js +34 -16
- package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-ast-intelligent-strategies.js +110 -29
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenExtractor.js +12 -7
- package/scripts/hooks-system/infrastructure/ast/ios/utils/ios-srp-helpers.js +162 -0
- package/scripts/hooks-system/infrastructure/severity/policies/gate-policies.js +5 -2
- package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh +9 -0
- package/scripts/hooks-system/infrastructure/watchdog/__tests__/.audit-reports/token-monitor.log +6 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Portable, project‑agnostic, multi‑platform enterprise framework to govern AI
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Quick start (30–60s)
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
git init
|
|
@@ -22,9 +22,9 @@ npx ast-install
|
|
|
22
22
|
npx ast-hooks audit
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Default installation mode: `npm-runtime`.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
To use the embedded runtime (`vendored` mode):
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
HOOK_INSTALL_MODE=vendored npx ast-install
|
|
@@ -360,55 +360,55 @@ The Git Flow cycle includes branch validation, commits, push, PR, merge (by poli
|
|
|
360
360
|
|
|
361
361
|
---
|
|
362
362
|
|
|
363
|
-
## 9.
|
|
363
|
+
## 9. Commands (required)
|
|
364
364
|
|
|
365
|
-
###
|
|
365
|
+
### Installation (dev)
|
|
366
366
|
|
|
367
367
|
```bash
|
|
368
368
|
npm install --save-dev pumuki-ast-hooks
|
|
369
369
|
npx ast-install
|
|
370
370
|
```
|
|
371
371
|
|
|
372
|
-
###
|
|
372
|
+
### Installation (legacy)
|
|
373
373
|
|
|
374
374
|
```bash
|
|
375
375
|
npm install --save-dev pumuki-ast-hooks
|
|
376
376
|
npx ast-install
|
|
377
377
|
```
|
|
378
378
|
|
|
379
|
-
###
|
|
379
|
+
### Update
|
|
380
380
|
|
|
381
381
|
```bash
|
|
382
382
|
npm install --save-dev pumuki-ast-hooks@latest
|
|
383
383
|
npx ast-install
|
|
384
384
|
```
|
|
385
385
|
|
|
386
|
-
###
|
|
386
|
+
### Uninstall
|
|
387
387
|
|
|
388
388
|
```bash
|
|
389
389
|
npx ast-uninstall
|
|
390
390
|
npm uninstall pumuki-ast-hooks
|
|
391
391
|
```
|
|
392
392
|
|
|
393
|
-
###
|
|
393
|
+
### Install hooks
|
|
394
394
|
|
|
395
395
|
```bash
|
|
396
396
|
npm run install-hooks
|
|
397
397
|
```
|
|
398
398
|
|
|
399
|
-
###
|
|
399
|
+
### Interactive menu
|
|
400
400
|
|
|
401
401
|
```bash
|
|
402
402
|
npx ast-hooks
|
|
403
403
|
```
|
|
404
404
|
|
|
405
|
-
###
|
|
405
|
+
### Version check
|
|
406
406
|
|
|
407
407
|
```bash
|
|
408
408
|
npm run ast:check-version
|
|
409
409
|
```
|
|
410
410
|
|
|
411
|
-
###
|
|
411
|
+
### Audit
|
|
412
412
|
|
|
413
413
|
```bash
|
|
414
414
|
npm run audit
|
|
@@ -435,7 +435,7 @@ npm run ast:guard:status
|
|
|
435
435
|
npm run ast:guard:logs
|
|
436
436
|
```
|
|
437
437
|
|
|
438
|
-
###
|
|
438
|
+
### Evidence refresh
|
|
439
439
|
|
|
440
440
|
```bash
|
|
441
441
|
npm run ast:refresh
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Auditoría de Severidades - Framework AST Intelligence
|
|
2
|
+
|
|
3
|
+
## Criterios de Clasificación
|
|
4
|
+
|
|
5
|
+
### CRITICAL
|
|
6
|
+
- **Vulnerabilidades de seguridad** que exponen datos sensibles
|
|
7
|
+
- **Data loss** potencial o corrupción de datos
|
|
8
|
+
- **Crashes** probables en producción
|
|
9
|
+
- **Violaciones de seguridad** críticas
|
|
10
|
+
|
|
11
|
+
### HIGH
|
|
12
|
+
- **Bugs probables** que causan comportamiento incorrecto
|
|
13
|
+
- **Memory leaks** y problemas de gestión de memoria
|
|
14
|
+
- **Concurrency issues** que pueden causar race conditions
|
|
15
|
+
- **Violaciones SOLID** que impactan arquitectura
|
|
16
|
+
|
|
17
|
+
### MEDIUM
|
|
18
|
+
- **Code smells** que afectan mantenibilidad
|
|
19
|
+
- **Performance** no crítico pero medible
|
|
20
|
+
- **Violaciones de patrones** establecidos
|
|
21
|
+
- **Deuda técnica** acumulable
|
|
22
|
+
|
|
23
|
+
### LOW
|
|
24
|
+
- **Sugerencias de estilo** y mejores prácticas
|
|
25
|
+
- **Optimizaciones** opcionales
|
|
26
|
+
- **Mejoras** de legibilidad
|
|
27
|
+
- **Warnings** informativos
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Reglas por Severidad
|
|
32
|
+
|
|
33
|
+
### CRITICAL (3 reglas)
|
|
34
|
+
|
|
35
|
+
| Regla | Justificación |
|
|
36
|
+
|-------|---------------|
|
|
37
|
+
| `ios.security.sensitive_userdefaults` | Credenciales en UserDefaults = vulnerabilidad de seguridad |
|
|
38
|
+
| `ios.security.hardcoded_secrets` | API keys/tokens hardcodeados = exposición de secretos |
|
|
39
|
+
| `backend.antipattern.god_classes` | Clases God extremas = arquitectura colapsada |
|
|
40
|
+
|
|
41
|
+
### HIGH (12 reglas)
|
|
42
|
+
|
|
43
|
+
| Regla | Justificación |
|
|
44
|
+
|-------|---------------|
|
|
45
|
+
| `ios.force_unwrapping` | Force unwrap = crash probable en runtime |
|
|
46
|
+
| `ios.safety.force_unwrap_property` | Propiedades force unwrap = crash al acceder |
|
|
47
|
+
| `ios.memory.missing_weak_self` | Retain cycles = memory leaks |
|
|
48
|
+
| `ios.concurrency.task_no_error_handling` | Errores sin manejar = crashes silenciosos |
|
|
49
|
+
| `ios.concurrency.async_ui_update` | UI updates fuera MainActor = crashes/bugs visuales |
|
|
50
|
+
| `ios.quality.long_function` | Funciones >50 líneas = bugs difíciles de detectar |
|
|
51
|
+
| `ios.quality.high_complexity` | Complejidad >10 = bugs probables |
|
|
52
|
+
| `ios.swiftui.complex_body` | Body >100 líneas = performance/bugs |
|
|
53
|
+
| `ios.antipattern.singleton` | Singletons = testing imposible, acoplamiento |
|
|
54
|
+
| `ios.architecture.repository_no_protocol` | Repositorios sin protocolo = violación DIP |
|
|
55
|
+
| `ios.architecture.usecase_wrong_layer` | UseCase en capa incorrecta = arquitectura rota |
|
|
56
|
+
| `ios.solid.srp.god_class` | God class detectada = violación SRP crítica |
|
|
57
|
+
|
|
58
|
+
### MEDIUM (15 reglas)
|
|
59
|
+
|
|
60
|
+
| Regla | Justificación |
|
|
61
|
+
|-------|---------------|
|
|
62
|
+
| `ios.quality.pyramid_of_doom` | Anidación profunda = legibilidad/mantenibilidad |
|
|
63
|
+
| `ios.quality.too_many_params` | >5 parámetros = difícil de usar/mantener |
|
|
64
|
+
| `ios.quality.nested_closures` | >3 closures = callback hell, usar async/await |
|
|
65
|
+
| `ios.encapsulation.public_mutable` | Propiedades públicas mutables = encapsulación rota |
|
|
66
|
+
| `ios.swiftui.too_many_state` | >5 @State = considerar ViewModel |
|
|
67
|
+
| `ios.swiftui.observed_without_state` | @ObservedObject sin ownership = bugs sutiles |
|
|
68
|
+
| `ios.solid.isp.fat_protocol` | Protocolo >5 requisitos = violación ISP |
|
|
69
|
+
| `ios.architecture.usecase_no_execute` | UseCase sin execute() = patrón inconsistente |
|
|
70
|
+
| `ios.architecture.usecase_void` | UseCase retorna Void = no testable |
|
|
71
|
+
| `ios.testing.missing_makesut` | Test sin makeSUT = código duplicado |
|
|
72
|
+
| `ios.naming.god_naming` | Nombres Manager/Helper = posible God class |
|
|
73
|
+
| `ios.i18n.hardcoded_strings` | >3 strings hardcodeados = i18n faltante |
|
|
74
|
+
| `ios.accessibility.missing_labels` | Imágenes sin labels = accesibilidad rota |
|
|
75
|
+
| `ios.solid.dip.concrete_dependency` | Dependencias concretas = violación DIP |
|
|
76
|
+
| `ios.solid.ocp.modification` | Modificar en lugar de extender = violación OCP |
|
|
77
|
+
|
|
78
|
+
### LOW (8 reglas)
|
|
79
|
+
|
|
80
|
+
| Regla | Justificación |
|
|
81
|
+
|-------|---------------|
|
|
82
|
+
| `ios.imports.unused` | Imports sin usar = ruido, build time |
|
|
83
|
+
| `ios.performance.non_final_class` | Clases no final = optimización perdida |
|
|
84
|
+
| `ios.codable.missing_coding_keys` | CodingKeys opcionales = mejor control |
|
|
85
|
+
| `ios.performance.large_equatable` | Equatable >5 props = performance menor |
|
|
86
|
+
| `ios.concurrency.task_cancellation` | Task sin cancelación = recursos no liberados |
|
|
87
|
+
| `ios.performance.blocking_main_thread` | Operaciones síncronas = UI freeze potencial |
|
|
88
|
+
| `ios.testing.missing_leak_tracking` | Tests sin leak tracking = leaks no detectados |
|
|
89
|
+
| `ios.quality.magic_numbers` | Números mágicos = legibilidad |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Cambios Recomendados
|
|
94
|
+
|
|
95
|
+
### Ninguno - Clasificación Correcta
|
|
96
|
+
|
|
97
|
+
Tras la auditoría, **todas las severidades están correctamente asignadas** según los criterios establecidos:
|
|
98
|
+
|
|
99
|
+
- ✅ **CRITICAL**: Solo vulnerabilidades de seguridad y arquitectura colapsada
|
|
100
|
+
- ✅ **HIGH**: Bugs probables, memory leaks, crashes
|
|
101
|
+
- ✅ **MEDIUM**: Code smells, mantenibilidad, patrones
|
|
102
|
+
- ✅ **LOW**: Optimizaciones, estilo, mejoras opcionales
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Estadísticas
|
|
107
|
+
|
|
108
|
+
| Severidad | Cantidad | % |
|
|
109
|
+
|-----------|----------|---|
|
|
110
|
+
| CRITICAL | 3 | 8% |
|
|
111
|
+
| HIGH | 12 | 32% |
|
|
112
|
+
| MEDIUM | 15 | 39% |
|
|
113
|
+
| LOW | 8 | 21% |
|
|
114
|
+
| **TOTAL** | **38** | **100%** |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Validación
|
|
119
|
+
|
|
120
|
+
### Criterios Aplicados
|
|
121
|
+
|
|
122
|
+
1. **CRITICAL**: ¿Expone datos sensibles o causa data loss?
|
|
123
|
+
2. **HIGH**: ¿Causa crashes o memory leaks probables?
|
|
124
|
+
3. **MEDIUM**: ¿Afecta mantenibilidad o patrones?
|
|
125
|
+
4. **LOW**: ¿Es optimización o mejora opcional?
|
|
126
|
+
|
|
127
|
+
### Resultado
|
|
128
|
+
|
|
129
|
+
✅ **Todas las reglas cumplen con los criterios de su severidad asignada**
|
|
130
|
+
|
|
131
|
+
No se requieren reclasificaciones.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
**Auditoría completada:** 24 Enero 2026
|
|
136
|
+
**Framework:** pumuki-ast-hooks v6.1.13
|
|
137
|
+
**Total reglas auditadas:** 38
|
package/package.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki-ast-hooks",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
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": {
|
|
7
|
-
"pumuki-ast-hooks": "
|
|
8
|
-
"audit": "
|
|
9
|
-
"ast-hooks": "
|
|
10
|
-
"ast-install": "
|
|
11
|
-
"ast-uninstall": "
|
|
12
|
-
"ast-violations": "
|
|
13
|
-
"ast-check-version": "
|
|
14
|
-
"ast-gitflow": "
|
|
15
|
-
"ai-commit": "
|
|
16
|
-
"hook-run-orchestrator": "
|
|
17
|
-
"hook-watch": "
|
|
18
|
-
"hook-status": "
|
|
19
|
-
"hook-predict": "
|
|
20
|
-
"hook-playbook": "
|
|
21
|
-
"hook-doc-drift": "
|
|
22
|
-
"hook-plan-review": "
|
|
7
|
+
"pumuki-ast-hooks": "bin/pumuki-mcp-server.js",
|
|
8
|
+
"audit": "bin/audit",
|
|
9
|
+
"ast-hooks": "bin/cli.js",
|
|
10
|
+
"ast-install": "bin/install.js",
|
|
11
|
+
"ast-uninstall": "bin/uninstall.js",
|
|
12
|
+
"ast-violations": "bin/violations-api.js",
|
|
13
|
+
"ast-check-version": "bin/check-version.js",
|
|
14
|
+
"ast-gitflow": "scripts/hooks-system/bin/gitflow-cycle.js",
|
|
15
|
+
"ai-commit": "bin/ai-commit.sh",
|
|
16
|
+
"hook-run-orchestrator": "bin/run-orchestrator.js",
|
|
17
|
+
"hook-watch": "bin/watch-hooks.js",
|
|
18
|
+
"hook-status": "bin/hook-status.js",
|
|
19
|
+
"hook-predict": "bin/predictive-hooks.js",
|
|
20
|
+
"hook-playbook": "bin/run-playbook.js",
|
|
21
|
+
"hook-doc-drift": "bin/check-doc-drift.js",
|
|
22
|
+
"hook-plan-review": "bin/plan-review.js"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"install-hooks": "node bin/install.js",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"license": "MIT",
|
|
82
82
|
"repository": {
|
|
83
83
|
"type": "git",
|
|
84
|
-
"url": "https://github.com/SwiftEnProfundidad/ast-intelligence-hooks.git"
|
|
84
|
+
"url": "git+https://github.com/SwiftEnProfundidad/ast-intelligence-hooks.git"
|
|
85
85
|
},
|
|
86
86
|
"bugs": {
|
|
87
87
|
"url": "https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/issues"
|
|
@@ -46,3 +46,5 @@
|
|
|
46
46
|
{"timestamp":"2026-01-21T13:16:57.700Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
|
|
47
47
|
{"timestamp":"2026-01-21T14:08:08.597Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
|
|
48
48
|
{"timestamp":"2026-01-21T14:14:43.580Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
|
|
49
|
+
{"timestamp":"2026-01-21T14:19:14.000Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
|
|
50
|
+
{"timestamp":"2026-01-21T14:43:15.797Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
|
|
@@ -194,3 +194,11 @@
|
|
|
194
194
|
{"timestamp":"2026-01-21T14:14:44.277Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
|
|
195
195
|
{"timestamp":"2026-01-21T14:14:44.277Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
|
|
196
196
|
{"timestamp":"2026-01-21T14:14:44.277Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
|
|
197
|
+
{"timestamp":"2026-01-21T14:19:14.062Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_START","data":{"repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"},"context":{}}
|
|
198
|
+
{"timestamp":"2026-01-21T14:19:14.069Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
|
|
199
|
+
{"timestamp":"2026-01-21T14:19:14.070Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
|
|
200
|
+
{"timestamp":"2026-01-21T14:19:14.070Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
|
|
201
|
+
{"timestamp":"2026-01-21T14:43:15.954Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_START","data":{"repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"},"context":{}}
|
|
202
|
+
{"timestamp":"2026-01-21T14:43:15.961Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
|
|
203
|
+
{"timestamp":"2026-01-21T14:43:15.961Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
|
|
204
|
+
{"timestamp":"2026-01-21T14:43:15.961Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
|
|
@@ -1450,3 +1450,59 @@
|
|
|
1450
1450
|
{"timestamp":1769004905775,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1451
1451
|
{"timestamp":1769004905775,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1452
1452
|
{"timestamp":1769004905775,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1453
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1454
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1455
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1456
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1457
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1458
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1459
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1460
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1461
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1462
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1463
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1464
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1465
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1466
|
+
{"timestamp":1769005153999,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1467
|
+
{"timestamp":1769005154000,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1468
|
+
{"timestamp":1769005154000,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1469
|
+
{"timestamp":1769005154000,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1470
|
+
{"timestamp":1769005154000,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1471
|
+
{"timestamp":1769005154000,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1472
|
+
{"timestamp":1769005154000,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1473
|
+
{"timestamp":1769005154000,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1474
|
+
{"timestamp":1769005154000,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1475
|
+
{"timestamp":1769005154000,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1476
|
+
{"timestamp":1769005154000,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1477
|
+
{"timestamp":1769005445389,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1478
|
+
{"timestamp":1769005445389,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1479
|
+
{"timestamp":1769005445390,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1480
|
+
{"timestamp":1769005445390,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1481
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1482
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1483
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1484
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1485
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1486
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1487
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1488
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1489
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1490
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1491
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1492
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1493
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1494
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1495
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1496
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1497
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1498
|
+
{"timestamp":1769006595796,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1499
|
+
{"timestamp":1769006595797,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1500
|
+
{"timestamp":1769006595797,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1501
|
+
{"timestamp":1769006595797,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1502
|
+
{"timestamp":1769006595797,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1503
|
+
{"timestamp":1769006595797,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1504
|
+
{"timestamp":1769006595797,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1505
|
+
{"timestamp":1769006619003,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
1506
|
+
{"timestamp":1769006619004,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
1507
|
+
{"timestamp":1769006619004,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
1508
|
+
{"timestamp":1769006619004,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { summarizeSwiftTypes, evaluateMultipleTypeGroups, resolveSrpSeverity, isThinWrapperSummary } = require('../utils/ios-srp-helpers');
|
|
2
|
+
|
|
3
|
+
describe('ios srp heuristics', () => {
|
|
4
|
+
it('flags multiple unrelated types by group', () => {
|
|
5
|
+
const content = `
|
|
6
|
+
struct User { let id: String }
|
|
7
|
+
struct Order { let id: String }
|
|
8
|
+
struct Product { let id: String }
|
|
9
|
+
`;
|
|
10
|
+
const summaries = summarizeSwiftTypes(content);
|
|
11
|
+
const result = evaluateMultipleTypeGroups(summaries);
|
|
12
|
+
expect(result.shouldFlag).toBe(true);
|
|
13
|
+
expect(result.distinctGroups).toBeGreaterThan(1);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('does not flag multiple types with shared prefix', () => {
|
|
17
|
+
const content = `
|
|
18
|
+
struct UserView { var body: some View { Text("x") } }
|
|
19
|
+
final class UserViewModel { func load() {} }
|
|
20
|
+
final class UserRouter { func route() {} }
|
|
21
|
+
`;
|
|
22
|
+
const summaries = summarizeSwiftTypes(content);
|
|
23
|
+
const result = evaluateMultipleTypeGroups(summaries);
|
|
24
|
+
expect(result.shouldFlag).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('counts methods without nested type members', () => {
|
|
28
|
+
const content = `
|
|
29
|
+
struct Wrapper {
|
|
30
|
+
struct Nested { func inner() {} }
|
|
31
|
+
func outer() {}
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
const summaries = summarizeSwiftTypes(content);
|
|
35
|
+
const wrapper = summaries.find((item) => item.name === 'Wrapper');
|
|
36
|
+
expect(wrapper.methodsCount).toBe(1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('detects thin wrappers', () => {
|
|
40
|
+
const content = `
|
|
41
|
+
struct TokenSpyStorageState {
|
|
42
|
+
var savedTokens: [String]
|
|
43
|
+
var deletedTokensCount: Int
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
final class AccessTokenSpyStorage {
|
|
47
|
+
var state: TokenSpyStorageState
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
const summaries = summarizeSwiftTypes(content);
|
|
51
|
+
const wrapper = summaries.find((item) => item.name === 'AccessTokenSpyStorage');
|
|
52
|
+
expect(isThinWrapperSummary(wrapper)).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('resolves severity by layer', () => {
|
|
56
|
+
const config = { coreSeverity: 'critical', defaultSeverity: 'high', testSeverity: 'low' };
|
|
57
|
+
expect(resolveSrpSeverity('Apps/Domain/User.swift', config)).toBe('critical');
|
|
58
|
+
expect(resolveSrpSeverity('Apps/Application/UserUseCase.swift', config)).toBe('critical');
|
|
59
|
+
expect(resolveSrpSeverity('Apps/Tests/UserTests.swift', config)).toBe('low');
|
|
60
|
+
expect(resolveSrpSeverity('Apps/Presentation/UserView.swift', config)).toBe('high');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -211,6 +211,29 @@ class iOSASTIntelligentAnalyzer {
|
|
|
211
211
|
|
|
212
212
|
collectAllNodes(this, substructure, null);
|
|
213
213
|
await analyzeCollectedNodes(this, displayPath);
|
|
214
|
+
|
|
215
|
+
this.detectForceUnwrapping(displayPath);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
detectForceUnwrapping(filePath) {
|
|
219
|
+
const lines = this.fileContent.split('\n');
|
|
220
|
+
lines.forEach((line, index) => {
|
|
221
|
+
const matches = [...line.matchAll(/(\w+)!/g)];
|
|
222
|
+
matches.forEach(match => {
|
|
223
|
+
const charBefore = match.index > 0 ? line[match.index - 1] : '';
|
|
224
|
+
const isLogicalNegation = charBefore === '!' || /[&|=<>]/.test(charBefore);
|
|
225
|
+
|
|
226
|
+
if (!isLogicalNegation) {
|
|
227
|
+
this.pushFinding(
|
|
228
|
+
'ios.force_unwrapping',
|
|
229
|
+
'high',
|
|
230
|
+
filePath,
|
|
231
|
+
index + 1,
|
|
232
|
+
`Force unwrapping (!) detected on '${match[1]}' - use if let or guard let instead`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
});
|
|
214
237
|
}
|
|
215
238
|
|
|
216
239
|
safeStringify(obj) {
|
|
@@ -287,9 +287,16 @@ function analyzeConcurrency({ content, filePath, addFinding }) {
|
|
|
287
287
|
'Manual main thread dispatch - use @MainActor annotation');
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
if (content.includes('Task {')
|
|
291
|
-
|
|
292
|
-
'Task
|
|
290
|
+
if (content.includes('Task {')) {
|
|
291
|
+
const hasCancelCheck = content.includes('Task.isCancelled') ||
|
|
292
|
+
content.includes('Task.checkCancellation') ||
|
|
293
|
+
content.includes('withTaskCancellationHandler') ||
|
|
294
|
+
content.includes('.cancel()');
|
|
295
|
+
|
|
296
|
+
if (!hasCancelCheck) {
|
|
297
|
+
addFinding('ios.concurrency.task_cancellation', 'low', filePath, 1,
|
|
298
|
+
'Task without cancellation handling - consider guard !Task.isCancelled or try Task.checkCancellation()');
|
|
299
|
+
}
|
|
293
300
|
}
|
|
294
301
|
|
|
295
302
|
if (content.includes('var ') && content.includes('queue') && !content.includes('actor')) {
|
|
@@ -304,9 +311,16 @@ function analyzeTesting({ content, filePath, addFinding }) {
|
|
|
304
311
|
'Test file without XCTest or Quick import');
|
|
305
312
|
}
|
|
306
313
|
|
|
307
|
-
if (filePath.includes('Test') &&
|
|
308
|
-
|
|
309
|
-
|
|
314
|
+
if (filePath.includes('Test') && content.includes('func test')) {
|
|
315
|
+
const hasMakeSUT = /func\s+(private\s+)?make[Ss][Uu][Tt]\b/.test(content) ||
|
|
316
|
+
/func\s+(private\s+)?make_sut\b/.test(content) ||
|
|
317
|
+
/func\s+(private\s+)?sut\b/.test(content);
|
|
318
|
+
const testCount = (content.match(/func\s+test/g) || []).length;
|
|
319
|
+
|
|
320
|
+
if (!hasMakeSUT && testCount >= 3) {
|
|
321
|
+
addFinding('ios.testing.missing_makesut', 'medium', filePath, 1,
|
|
322
|
+
'Test without makeSUT pattern - centralize system under test creation');
|
|
323
|
+
}
|
|
310
324
|
}
|
|
311
325
|
|
|
312
326
|
if (filePath.includes('Test') && !content.includes('trackForMemoryLeaks') && content.includes('class')) {
|
|
@@ -17,6 +17,7 @@ const { iOSCICDRules } = require(path.join(__dirname, 'analyzers/iOSCICDRules'))
|
|
|
17
17
|
const { iOSForbiddenLiteralsAnalyzer } = require(path.join(__dirname, 'analyzers/iOSForbiddenLiteralsAnalyzer'));
|
|
18
18
|
const { iOSASTIntelligentAnalyzer } = require(path.join(__dirname, 'analyzers/iOSASTIntelligentAnalyzer'));
|
|
19
19
|
const { iOSModernPracticesRules } = require(path.join(__dirname, 'analyzers/iOSModernPracticesRules'));
|
|
20
|
+
const { summarizeSwiftTypes, evaluateMultipleTypeGroups, resolveSrpSeverity, isThinWrapperSummary } = require(path.join(__dirname, 'utils/ios-srp-helpers'));
|
|
20
21
|
|
|
21
22
|
function detectForbiddenTestableImport({ filePath, content }) {
|
|
22
23
|
if (!filePath || !content) return null;
|
|
@@ -36,8 +37,16 @@ function detectMissingMakeSUT({ filePath, content }) {
|
|
|
36
37
|
if (!content.includes('XCTest')) return null;
|
|
37
38
|
const hasTests = /func\s+test\w*\s*\(/.test(content);
|
|
38
39
|
if (!hasTests) return null;
|
|
39
|
-
|
|
40
|
+
|
|
41
|
+
const hasMakeSUT = /func\s+(private\s+)?make[Ss][Uu][Tt]\b/.test(content) ||
|
|
42
|
+
/func\s+(private\s+)?make_sut\b/.test(content) ||
|
|
43
|
+
/func\s+(private\s+)?sut\b/.test(content);
|
|
44
|
+
|
|
40
45
|
if (hasMakeSUT) return null;
|
|
46
|
+
|
|
47
|
+
const testCount = (content.match(/func\s+test/g) || []).length;
|
|
48
|
+
if (testCount < 3) return null;
|
|
49
|
+
|
|
41
50
|
return {
|
|
42
51
|
ruleId: 'ios.testing.missing_make_sut',
|
|
43
52
|
severity: 'high',
|
|
@@ -128,10 +137,6 @@ async function runIOSIntelligence(project, findings, platform) {
|
|
|
128
137
|
|
|
129
138
|
if (platformOf(filePath) !== "ios") return;
|
|
130
139
|
|
|
131
|
-
sf.getDescendantsOfKind(SyntaxKind.NonNullExpression).forEach((expr) => {
|
|
132
|
-
pushFinding("ios.force_unwrapping", "high", sf, expr, "Force unwrapping (!) detected - use if let or guard let instead", findings);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
140
|
const completionHandlerFilePath = sf.getFilePath();
|
|
136
141
|
const isAnalyzer = /infrastructure\/ast\/|analyzers\/|detectors\/|scanner|analyzer|detector/i.test(completionHandlerFilePath);
|
|
137
142
|
const isCompletionTestFile = /\.(spec|test)\.(js|ts|swift)$/i.test(completionHandlerFilePath);
|
|
@@ -1134,29 +1139,42 @@ async function runIOSIntelligence(project, findings, platform) {
|
|
|
1134
1139
|
// ==========================================
|
|
1135
1140
|
|
|
1136
1141
|
|
|
1137
|
-
const
|
|
1138
|
-
const
|
|
1139
|
-
if (
|
|
1142
|
+
const typeSummaries = summarizeSwiftTypes(content);
|
|
1143
|
+
const typeGroups = evaluateMultipleTypeGroups(typeSummaries);
|
|
1144
|
+
if (typeGroups.shouldFlag && !filePath.includes('Generated')) {
|
|
1145
|
+
const severity = resolveSrpSeverity(filePath, {
|
|
1146
|
+
coreSeverity: 'high',
|
|
1147
|
+
defaultSeverity: 'medium',
|
|
1148
|
+
testSeverity: 'low'
|
|
1149
|
+
});
|
|
1140
1150
|
pushFinding(
|
|
1141
1151
|
"ios.solid.srp_multiple_types",
|
|
1142
|
-
|
|
1152
|
+
severity,
|
|
1143
1153
|
sf,
|
|
1144
1154
|
sf,
|
|
1145
|
-
`File defines ${
|
|
1155
|
+
`File defines ${typeGroups.totalTypes} types across ${typeGroups.distinctGroups} domains - split by responsibility (SRP)`,
|
|
1146
1156
|
findings
|
|
1147
1157
|
);
|
|
1148
1158
|
}
|
|
1149
1159
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1160
|
+
const godClassSeverityConfig = {
|
|
1161
|
+
coreSeverity: 'critical',
|
|
1162
|
+
defaultSeverity: 'high',
|
|
1163
|
+
testSeverity: 'low'
|
|
1164
|
+
};
|
|
1165
|
+
const godClassThreshold = 20;
|
|
1166
|
+
for (const summary of typeSummaries) {
|
|
1167
|
+
if (isThinWrapperSummary(summary)) {
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
if (summary.methodsCount > godClassThreshold) {
|
|
1171
|
+
const severity = resolveSrpSeverity(filePath, godClassSeverityConfig);
|
|
1154
1172
|
pushFinding(
|
|
1155
1173
|
"ios.solid.srp_god_class",
|
|
1156
|
-
|
|
1174
|
+
severity,
|
|
1157
1175
|
sf,
|
|
1158
1176
|
sf,
|
|
1159
|
-
`Type has ${
|
|
1177
|
+
`Type '${summary.name}' has ${summary.methodsCount} methods - split responsibilities (SRP: one reason to change)`,
|
|
1160
1178
|
findings
|
|
1161
1179
|
);
|
|
1162
1180
|
}
|
package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-ast-intelligent-strategies.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const DIValidationService = require('../../../../application/DIValidationService');
|
|
3
|
+
const { resolveSrpSeverity, isThinWrapperSummary } = require('../utils/ios-srp-helpers');
|
|
3
4
|
|
|
4
5
|
const diValidationService = new DIValidationService();
|
|
5
6
|
|
|
@@ -119,17 +120,33 @@ function analyzeImportsAST(analyzer, filePath) {
|
|
|
119
120
|
const unusedImportAllowlist = new Set(['Foundation', 'SwiftUI', 'UIKit', 'Combine']);
|
|
120
121
|
|
|
121
122
|
const foundationTypeUsage = /\b(Data|Date|URL|UUID|Decimal|NSNumber|NSDecimalNumber|NSSet|NSDictionary|NSArray|IndexPath|Notification|FileManager|Bundle|Locale|TimeZone|Calendar|DateComponents|URLRequest|URLSession)\b/;
|
|
123
|
+
const swiftUIUsage = /\b(View|some View|Text|VStack|HStack|ZStack|Button|Image|List|NavigationView|NavigationStack|\.font|\.padding|\.frame|\.background|\.foregroundColor|@State|@Binding|@ObservedObject|@StateObject|@Environment)\b/;
|
|
124
|
+
const uiKitUsage = /\b(UIViewController|UIView|UITableView|UICollectionView|UIButton|UILabel|UIImageView|UINavigationController|viewDidLoad|viewWillAppear)\b/;
|
|
125
|
+
const combineUsage = /\b(Publisher|AnyPublisher|PassthroughSubject|CurrentValueSubject|@Published|sink|subscribe|eraseToAnyPublisher)\b/;
|
|
126
|
+
|
|
122
127
|
for (const imp of analyzer.imports) {
|
|
123
128
|
if (!unusedImportAllowlist.has(imp.name)) continue;
|
|
124
129
|
|
|
125
130
|
let isUsed = analyzer.allNodes.some((n) => {
|
|
126
131
|
const typename = n['key.typename'] || '';
|
|
127
132
|
const name = n['key.name'] || '';
|
|
128
|
-
|
|
133
|
+
const inheritedTypes = n['key.inheritedtypes'] || [];
|
|
134
|
+
const conformsToView = inheritedTypes.some(t => (t['key.name'] || '') === 'View');
|
|
135
|
+
return typename.includes(imp.name) || name.includes(imp.name) || (imp.name === 'SwiftUI' && conformsToView);
|
|
129
136
|
});
|
|
137
|
+
|
|
130
138
|
if (!isUsed && imp.name === 'Foundation') {
|
|
131
139
|
isUsed = foundationTypeUsage.test(content);
|
|
132
140
|
}
|
|
141
|
+
if (!isUsed && imp.name === 'SwiftUI') {
|
|
142
|
+
isUsed = swiftUIUsage.test(content);
|
|
143
|
+
}
|
|
144
|
+
if (!isUsed && imp.name === 'UIKit') {
|
|
145
|
+
isUsed = uiKitUsage.test(content);
|
|
146
|
+
}
|
|
147
|
+
if (!isUsed && imp.name === 'Combine') {
|
|
148
|
+
isUsed = combineUsage.test(content);
|
|
149
|
+
}
|
|
133
150
|
|
|
134
151
|
if (!isUsed) {
|
|
135
152
|
analyzer.pushFinding('ios.imports.unused', 'low', filePath, imp.line, `Unused import: ${imp.name}`);
|
|
@@ -168,16 +185,22 @@ async function analyzeClassAST(analyzer, node, filePath) {
|
|
|
168
185
|
|
|
169
186
|
if (name && !/Spec$|Test$|Mock/.test(name) && !name.includes('Coordinator')) {
|
|
170
187
|
const complexity = calculateComplexityAST(substructure);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
filePath,
|
|
174
|
-
line,
|
|
175
|
-
methodsCount: methods.length,
|
|
176
|
-
significantMethodsCount: significantMethods.length,
|
|
188
|
+
const isThinWrapper = isThinWrapperSummary({
|
|
189
|
+
methodsCount: significantMethods.length,
|
|
177
190
|
propertiesCount: properties.length,
|
|
178
|
-
bodyLength,
|
|
179
|
-
complexity,
|
|
180
191
|
});
|
|
192
|
+
if (!isThinWrapper) {
|
|
193
|
+
analyzer.godClassCandidates.push({
|
|
194
|
+
name,
|
|
195
|
+
filePath,
|
|
196
|
+
line,
|
|
197
|
+
methodsCount: methods.length,
|
|
198
|
+
significantMethodsCount: significantMethods.length,
|
|
199
|
+
propertiesCount: properties.length,
|
|
200
|
+
bodyLength,
|
|
201
|
+
complexity,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
181
204
|
}
|
|
182
205
|
|
|
183
206
|
if (name.includes('ViewController')) {
|
|
@@ -291,8 +314,12 @@ async function analyzeClassAST(analyzer, node, filePath) {
|
|
|
291
314
|
}
|
|
292
315
|
|
|
293
316
|
if (filePath.includes('Test') && name.includes('Test')) {
|
|
294
|
-
const hasMakeSUT = methods.some((m) =>
|
|
295
|
-
|
|
317
|
+
const hasMakeSUT = methods.some((m) => {
|
|
318
|
+
const methodName = (m['key.name'] || '').toLowerCase();
|
|
319
|
+
return methodName.includes('makesut') || methodName.includes('make_sut') || methodName === 'sut';
|
|
320
|
+
});
|
|
321
|
+
const testMethods = methods.filter(m => (m['key.name'] || '').startsWith('test'));
|
|
322
|
+
if (!hasMakeSUT && testMethods.length >= 3) {
|
|
296
323
|
analyzer.pushFinding('ios.testing.missing_makesut', 'medium', filePath, line, `Test class '${name}' missing makeSUT() factory`);
|
|
297
324
|
}
|
|
298
325
|
}
|
|
@@ -424,8 +451,11 @@ function analyzeFunctionAST(analyzer, node, filePath) {
|
|
|
424
451
|
const ifStatements = countStatementsOfType(substructure, 'stmt.if');
|
|
425
452
|
const guardStatements = countStatementsOfType(substructure, 'stmt.guard');
|
|
426
453
|
|
|
427
|
-
|
|
428
|
-
|
|
454
|
+
const hasEarlyReturns = (analyzer.fileContent || '').includes('return') && guardStatements > 0;
|
|
455
|
+
const nestingLevel = calculateNestingDepth(substructure);
|
|
456
|
+
|
|
457
|
+
if (nestingLevel >= 3 && !hasEarlyReturns) {
|
|
458
|
+
analyzer.pushFinding('ios.quality.pyramid_of_doom', 'medium', filePath, line, `Function '${name}' has ${nestingLevel} levels of nesting - use guard clauses for early returns`);
|
|
429
459
|
}
|
|
430
460
|
|
|
431
461
|
const isAsync = attributes.includes('async');
|
|
@@ -458,12 +488,16 @@ function analyzePropertyAST(analyzer, node, filePath) {
|
|
|
458
488
|
const isMutable = isComputed ? hasAccessorSet : true;
|
|
459
489
|
|
|
460
490
|
const hasSetterAccessRestriction = attributes.some(a => String(a).startsWith('setter_access'));
|
|
461
|
-
|
|
491
|
+
const isProtocolRequirement = name === 'body' || attributes.some(a => String(a).includes('protocol'));
|
|
492
|
+
|
|
493
|
+
if (isPublic && isInstance && isMutable && !hasSetterAccessRestriction && !isProtocolRequirement) {
|
|
462
494
|
analyzer.pushFinding('ios.encapsulation.public_mutable', 'medium', filePath, line, `Public mutable property '${name}' - consider private(set)`);
|
|
463
495
|
}
|
|
464
496
|
}
|
|
465
497
|
|
|
466
498
|
function analyzeClosuresAST(analyzer, filePath) {
|
|
499
|
+
const isStruct = analyzer.structs.length > 0;
|
|
500
|
+
|
|
467
501
|
for (const closure of analyzer.closures) {
|
|
468
502
|
const closureText = analyzer.safeStringify(closure);
|
|
469
503
|
const hasSelfReference = closureText.includes('"self"') || closureText.includes('key.name":"self');
|
|
@@ -471,10 +505,16 @@ function analyzeClosuresAST(analyzer, filePath) {
|
|
|
471
505
|
const parentFunc = closure._parent;
|
|
472
506
|
const isEscaping = parentFunc && (parentFunc['key.typename'] || '').includes('@escaping');
|
|
473
507
|
|
|
474
|
-
if (hasSelfReference && isEscaping) {
|
|
475
|
-
const
|
|
508
|
+
if (hasSelfReference && isEscaping && !isStruct) {
|
|
509
|
+
const offset = closure['key.offset'] || 0;
|
|
510
|
+
const length = closure['key.length'] || 100;
|
|
511
|
+
const closureCode = (analyzer.fileContent || '').substring(offset, offset + length);
|
|
476
512
|
|
|
477
|
-
|
|
513
|
+
const hasExplicitSelfInCode = /\bself\b/.test(closureCode);
|
|
514
|
+
const hasWeakCapture = /\[.*weak.*\]/.test(closureCode) || /\[.*unowned.*\]/.test(closureCode);
|
|
515
|
+
const hasCaptureListWithoutSelf = /\[[^\]]+\]/.test(closureCode) && !/\[.*self.*\]/.test(closureCode);
|
|
516
|
+
|
|
517
|
+
if (hasExplicitSelfInCode && !hasWeakCapture && !hasCaptureListWithoutSelf) {
|
|
478
518
|
const line = closure['key.line'] || 1;
|
|
479
519
|
analyzer.pushFinding('ios.memory.missing_weak_self', 'high', filePath, line, 'Escaping closure captures self without [weak self]');
|
|
480
520
|
}
|
|
@@ -543,14 +583,27 @@ function analyzeAdditionalRules(analyzer, filePath) {
|
|
|
543
583
|
analyzer.pushFinding('ios.concurrency.dispatch_queue', 'medium', filePath, line, 'DispatchQueue detected - use async/await in new code');
|
|
544
584
|
}
|
|
545
585
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
586
|
+
const taskMatches = [...(analyzer.fileContent || '').matchAll(/Task\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/gs)];
|
|
587
|
+
taskMatches.forEach(match => {
|
|
588
|
+
const taskBody = match[1] || '';
|
|
589
|
+
const hasTryCall = /\btry\s+/.test(taskBody);
|
|
590
|
+
const hasDoBlock = /\bdo\s*\{/.test(taskBody);
|
|
550
591
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
592
|
+
if (hasTryCall && !hasDoBlock) {
|
|
593
|
+
const line = analyzer.findLineNumber(match[0].substring(0, 20));
|
|
594
|
+
analyzer.pushFinding('ios.concurrency.task_no_error_handling', 'high', filePath, line, 'Task with throwing calls (try) without do-catch - handle errors');
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
if ((analyzer.fileContent || '').includes('UserDefaults')) {
|
|
599
|
+
const content = analyzer.fileContent || '';
|
|
600
|
+
const hasSensitiveData = /UserDefaults.*\.set.*\b(password|token|secret|apiKey|authToken|accessToken|refreshToken)\b/i.test(content) ||
|
|
601
|
+
/\.set.*\b(password|token|secret|apiKey|authToken|accessToken|refreshToken)\b.*UserDefaults/i.test(content);
|
|
602
|
+
|
|
603
|
+
if (hasSensitiveData) {
|
|
604
|
+
const line = analyzer.findLineNumber('UserDefaults');
|
|
605
|
+
analyzer.pushFinding('ios.security.sensitive_userdefaults', 'critical', filePath, line, 'Sensitive credentials in UserDefaults - use Keychain for passwords/tokens');
|
|
606
|
+
}
|
|
554
607
|
}
|
|
555
608
|
|
|
556
609
|
const hardcodedStrings = (analyzer.fileContent || '').match(/Text\s*\(\s*"[^"]{10,}"\s*\)/g) || [];
|
|
@@ -686,15 +739,35 @@ function countStatementsOfType(substructure, stmtType) {
|
|
|
686
739
|
let count = 0;
|
|
687
740
|
const traverse = (nodes) => {
|
|
688
741
|
if (!Array.isArray(nodes)) return;
|
|
689
|
-
for (const
|
|
690
|
-
if ((
|
|
691
|
-
traverse(
|
|
742
|
+
for (const n of nodes) {
|
|
743
|
+
if ((n['key.kind'] || '').includes(stmtType)) count++;
|
|
744
|
+
traverse(n['key.substructure']);
|
|
692
745
|
}
|
|
693
746
|
};
|
|
694
747
|
traverse(substructure);
|
|
695
748
|
return count;
|
|
696
749
|
}
|
|
697
750
|
|
|
751
|
+
function calculateNestingDepth(substructure) {
|
|
752
|
+
let maxDepth = 0;
|
|
753
|
+
const traverse = (nodes, currentDepth) => {
|
|
754
|
+
if (!Array.isArray(nodes)) return;
|
|
755
|
+
for (const n of nodes) {
|
|
756
|
+
const kind = n['key.kind'] || '';
|
|
757
|
+
let depth = currentDepth;
|
|
758
|
+
|
|
759
|
+
if (kind.includes('stmt.if') || kind.includes('stmt.guard') || kind.includes('stmt.for') || kind.includes('stmt.while')) {
|
|
760
|
+
depth++;
|
|
761
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
traverse(n['key.substructure'], depth);
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
traverse(substructure, 0);
|
|
768
|
+
return maxDepth;
|
|
769
|
+
}
|
|
770
|
+
|
|
698
771
|
async function checkDependencyInjectionAST(analyzer, properties, filePath, className, line) {
|
|
699
772
|
await diValidationService.validateDependencyInjection(analyzer, properties, filePath, className, line);
|
|
700
773
|
}
|
|
@@ -802,10 +875,18 @@ function finalizeGodClassDetection(analyzer) {
|
|
|
802
875
|
|
|
803
876
|
const signalCount = [sizeOutlier, complexityOutlier].filter(Boolean).length;
|
|
804
877
|
|
|
805
|
-
|
|
878
|
+
const isTestDouble = /Spy$|Mock$|Stub$|Fake$|Dummy$/i.test(c.name) ||
|
|
879
|
+
c.filePath.includes('Test') && /Double|Spy|Mock|Stub/i.test(c.name);
|
|
880
|
+
|
|
881
|
+
if ((extremeOutlier || signalCount >= 2) && !isTestDouble) {
|
|
882
|
+
const severity = resolveSrpSeverity(c.filePath, {
|
|
883
|
+
coreSeverity: 'critical',
|
|
884
|
+
defaultSeverity: 'high',
|
|
885
|
+
testSeverity: 'low',
|
|
886
|
+
});
|
|
806
887
|
analyzer.pushFinding(
|
|
807
888
|
'ios.solid.srp.god_class',
|
|
808
|
-
|
|
889
|
+
severity,
|
|
809
890
|
c.filePath,
|
|
810
891
|
c.line,
|
|
811
892
|
`God class '${c.name}': ${c.methodsCount} methods (z=${methodsZ.toFixed(2)}), ${c.propertiesCount} properties (z=${propsZ.toFixed(2)}), body ${c.bodyLength} (z=${bodyZ.toFixed(2)}), complexity ${c.complexity} (z=${complexityZ.toFixed(2)}) - VIOLATES SRP`
|
|
@@ -119,14 +119,19 @@ class SourceKittenExtractor {
|
|
|
119
119
|
const forceUnwraps = [];
|
|
120
120
|
const lines = (fileContent || '').split('\n');
|
|
121
121
|
lines.forEach((line, index) => {
|
|
122
|
-
const matches = [...line.matchAll(/(\w+)
|
|
122
|
+
const matches = [...line.matchAll(/(\w+)!/g)];
|
|
123
123
|
matches.forEach(match => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
const charBefore = match.index > 0 ? line[match.index - 1] : '';
|
|
125
|
+
const isLogicalNegation = charBefore === '!' || /[&|=<>]/.test(charBefore);
|
|
126
|
+
|
|
127
|
+
if (!isLogicalNegation) {
|
|
128
|
+
forceUnwraps.push({
|
|
129
|
+
line: index + 1,
|
|
130
|
+
column: match.index + 1,
|
|
131
|
+
variable: match[1],
|
|
132
|
+
context: line.trim(),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
130
135
|
});
|
|
131
136
|
});
|
|
132
137
|
return forceUnwraps;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
function resolveSrpSeverity(filePath, { coreSeverity, defaultSeverity, testSeverity }) {
|
|
2
|
+
const normalized = String(filePath || '').replace(/\\/g, '/');
|
|
3
|
+
const isTest = /Tests|Test|Spec/.test(normalized);
|
|
4
|
+
if (isTest) return testSeverity || defaultSeverity;
|
|
5
|
+
const isCore = normalized.includes('/Domain/') || normalized.includes('/Application/');
|
|
6
|
+
if (isCore) return coreSeverity || defaultSeverity;
|
|
7
|
+
return defaultSeverity;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function findMatchingBraceIndex(content, startIndex) {
|
|
11
|
+
let depth = 0;
|
|
12
|
+
let inString = false;
|
|
13
|
+
let stringChar = '';
|
|
14
|
+
let inLineComment = false;
|
|
15
|
+
let inBlockComment = false;
|
|
16
|
+
|
|
17
|
+
for (let i = startIndex; i < content.length; i += 1) {
|
|
18
|
+
const current = content[i];
|
|
19
|
+
const next = content[i + 1];
|
|
20
|
+
|
|
21
|
+
if (inLineComment) {
|
|
22
|
+
if (current === '\n') inLineComment = false;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (inBlockComment) {
|
|
27
|
+
if (current === '*' && next === '/') {
|
|
28
|
+
inBlockComment = false;
|
|
29
|
+
i += 1;
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (inString) {
|
|
35
|
+
if (current === '\\') {
|
|
36
|
+
i += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (current === stringChar) {
|
|
40
|
+
inString = false;
|
|
41
|
+
}
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (current === '/' && next === '/') {
|
|
46
|
+
inLineComment = true;
|
|
47
|
+
i += 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (current === '/' && next === '*') {
|
|
52
|
+
inBlockComment = true;
|
|
53
|
+
i += 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (current === '"' || current === "'") {
|
|
58
|
+
inString = true;
|
|
59
|
+
stringChar = current;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (current === '{') depth += 1;
|
|
64
|
+
if (current === '}') {
|
|
65
|
+
depth -= 1;
|
|
66
|
+
if (depth === 0) return i;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return -1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function extractSwiftTypeBlocks(content) {
|
|
74
|
+
const blocks = [];
|
|
75
|
+
const pattern = /(^|\n)\s*(?:public|internal|fileprivate|private|open)?\s*(?:final\s+)?(class|struct|enum|protocol)\s+([A-Za-z_][A-Za-z0-9_]*)[^\n{]*\{/g;
|
|
76
|
+
let match = pattern.exec(content);
|
|
77
|
+
while (match) {
|
|
78
|
+
const name = match[3];
|
|
79
|
+
const braceIndex = match.index + match[0].lastIndexOf('{');
|
|
80
|
+
const endIndex = findMatchingBraceIndex(content, braceIndex);
|
|
81
|
+
if (endIndex !== -1) {
|
|
82
|
+
blocks.push({
|
|
83
|
+
name,
|
|
84
|
+
openIndex: braceIndex,
|
|
85
|
+
closeIndex: endIndex,
|
|
86
|
+
body: content.slice(braceIndex + 1, endIndex)
|
|
87
|
+
});
|
|
88
|
+
pattern.lastIndex = endIndex + 1;
|
|
89
|
+
}
|
|
90
|
+
match = pattern.exec(content);
|
|
91
|
+
}
|
|
92
|
+
return blocks;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function stripNestedTypeBlocks(content) {
|
|
96
|
+
const nestedBlocks = extractSwiftTypeBlocks(content);
|
|
97
|
+
if (nestedBlocks.length === 0) return content;
|
|
98
|
+
const sorted = nestedBlocks.slice().sort((a, b) => b.openIndex - a.openIndex);
|
|
99
|
+
let output = content;
|
|
100
|
+
for (const block of sorted) {
|
|
101
|
+
const length = block.closeIndex - block.openIndex + 1;
|
|
102
|
+
output = `${output.slice(0, block.openIndex)}${' '.repeat(length)}${output.slice(block.closeIndex + 1)}`;
|
|
103
|
+
}
|
|
104
|
+
return output;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function countFunctions(content) {
|
|
108
|
+
const matches = content.match(/\bfunc\s+[A-Za-z_][A-Za-z0-9_]*/g) || [];
|
|
109
|
+
return matches.length;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function countProperties(content) {
|
|
113
|
+
const matches = content.match(/(^|\n)\s*(?:@[A-Za-z0-9_.]+\s*)*(var|let)\s+[A-Za-z_][A-Za-z0-9_]*/g) || [];
|
|
114
|
+
return matches.length;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function splitTypeName(name) {
|
|
118
|
+
return String(name).match(/[A-Z]+(?![a-z])|[A-Z]?[a-z]+|[0-9]+/g) || [name];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getTypeGroupKey(name) {
|
|
122
|
+
const parts = splitTypeName(name);
|
|
123
|
+
return parts[0] || name;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function summarizeSwiftTypes(content) {
|
|
127
|
+
const blocks = extractSwiftTypeBlocks(String(content || ''));
|
|
128
|
+
return blocks.map((block) => {
|
|
129
|
+
const sanitizedBody = stripNestedTypeBlocks(block.body);
|
|
130
|
+
return {
|
|
131
|
+
name: block.name,
|
|
132
|
+
methodsCount: countFunctions(sanitizedBody),
|
|
133
|
+
propertiesCount: countProperties(sanitizedBody),
|
|
134
|
+
groupKey: getTypeGroupKey(block.name)
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function evaluateMultipleTypeGroups(summaries) {
|
|
140
|
+
const groups = new Set((summaries || []).map((item) => item.groupKey).filter(Boolean));
|
|
141
|
+
const totalTypes = (summaries || []).length;
|
|
142
|
+
const distinctGroups = groups.size;
|
|
143
|
+
const shouldFlag = totalTypes >= 3 && distinctGroups >= 2;
|
|
144
|
+
return {
|
|
145
|
+
totalTypes,
|
|
146
|
+
distinctGroups,
|
|
147
|
+
shouldFlag,
|
|
148
|
+
groups: Array.from(groups)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isThinWrapperSummary(summary) {
|
|
153
|
+
if (!summary) return false;
|
|
154
|
+
return summary.methodsCount === 0 && summary.propertiesCount <= 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = {
|
|
158
|
+
summarizeSwiftTypes,
|
|
159
|
+
evaluateMultipleTypeGroups,
|
|
160
|
+
resolveSrpSeverity,
|
|
161
|
+
isThinWrapperSummary
|
|
162
|
+
};
|
|
@@ -27,8 +27,11 @@ class GatePolicies {
|
|
|
27
27
|
LOW: this.checkLevel('LOW', grouped.LOW || [], effectivePolicies.LOW || effectivePolicies['LOW'])
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
const passed = !results.CRITICAL.failed && !results.HIGH.failed;
|
|
31
|
-
const blockedBy = results.CRITICAL.failed ? 'CRITICAL' :
|
|
30
|
+
const passed = !results.CRITICAL.failed && !results.HIGH.failed && !results.MEDIUM.failed && !results.LOW.failed;
|
|
31
|
+
const blockedBy = results.CRITICAL.failed ? 'CRITICAL' :
|
|
32
|
+
(results.HIGH.failed ? 'HIGH' :
|
|
33
|
+
(results.MEDIUM.failed ? 'MEDIUM' :
|
|
34
|
+
(results.LOW.failed ? 'LOW' : null)));
|
|
32
35
|
|
|
33
36
|
return {
|
|
34
37
|
passed,
|
|
@@ -62,6 +62,15 @@ evidence_age() {
|
|
|
62
62
|
ensure_evidence_fresh() {
|
|
63
63
|
local age
|
|
64
64
|
age=$(evidence_age)
|
|
65
|
+
|
|
66
|
+
# En modo check (pre-commit), SIEMPRE refrescar para analizar staged files
|
|
67
|
+
if [[ "${GITFLOW_STRICT_CHECK:-false}" == "true" ]]; then
|
|
68
|
+
printf "${CYAN}🔄 Refrescando evidencia para staged files...${NC}\n"
|
|
69
|
+
refresh_evidence "pre-commit"
|
|
70
|
+
return
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# En otros modos, usar umbral de tiempo
|
|
65
74
|
if [[ "$age" == "-1" ]]; then
|
|
66
75
|
printf "${YELLOW}⚠️ Evidencia ausente o inválida. Refrescando...${NC}\n"
|
|
67
76
|
refresh_evidence "missing"
|
package/scripts/hooks-system/infrastructure/watchdog/__tests__/.audit-reports/token-monitor.log
CHANGED
|
@@ -142,3 +142,9 @@
|
|
|
142
142
|
{"timestamp":"2026-01-21T14:14:45.047Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"ok","percentUsed":10,"tokensUsed":100000,"maxTokens":1000000,"source":"realtime","stale":false},"context":{"message":"Result level=ok percent=10% used=100000/1000000 source=realtime"}}
|
|
143
143
|
{"timestamp":"2026-01-21T14:14:45.048Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"warning","percentUsed":91,"tokensUsed":910000,"maxTokens":1000000,"source":"fallback","stale":false},"context":{"message":"Result level=warning percent=91% used=910000/1000000 source=fallback"}}
|
|
144
144
|
{"timestamp":"2026-01-21T14:14:45.048Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"critical","percentUsed":98,"tokensUsed":980000,"maxTokens":1000000,"source":"realtime","stale":true},"context":{"message":"Result level=critical percent=98% used=980000/1000000 source=realtime (stale)"}}
|
|
145
|
+
{"timestamp":"2026-01-21T14:19:16.416Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"ok","percentUsed":10,"tokensUsed":100000,"maxTokens":1000000,"source":"realtime","stale":false},"context":{"message":"Result level=ok percent=10% used=100000/1000000 source=realtime"}}
|
|
146
|
+
{"timestamp":"2026-01-21T14:19:16.417Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"warning","percentUsed":91,"tokensUsed":910000,"maxTokens":1000000,"source":"fallback","stale":false},"context":{"message":"Result level=warning percent=91% used=910000/1000000 source=fallback"}}
|
|
147
|
+
{"timestamp":"2026-01-21T14:19:16.418Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"critical","percentUsed":98,"tokensUsed":980000,"maxTokens":1000000,"source":"realtime","stale":true},"context":{"message":"Result level=critical percent=98% used=980000/1000000 source=realtime (stale)"}}
|
|
148
|
+
{"timestamp":"2026-01-21T14:43:17.747Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"ok","percentUsed":10,"tokensUsed":100000,"maxTokens":1000000,"source":"realtime","stale":false},"context":{"message":"Result level=ok percent=10% used=100000/1000000 source=realtime"}}
|
|
149
|
+
{"timestamp":"2026-01-21T14:43:17.748Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"warning","percentUsed":91,"tokensUsed":910000,"maxTokens":1000000,"source":"fallback","stale":false},"context":{"message":"Result level=warning percent=91% used=910000/1000000 source=fallback"}}
|
|
150
|
+
{"timestamp":"2026-01-21T14:43:17.749Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"critical","percentUsed":98,"tokensUsed":980000,"maxTokens":1000000,"source":"realtime","stale":true},"context":{"message":"Result level=critical percent=98% used=980000/1000000 source=realtime (stale)"}}
|