don-cheli-sdd 1.16.4 → 1.17.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/CHANGELOG.md +12 -0
- package/README.es.md +1 -1
- package/README.md +1 -1
- package/README.pt.md +1 -1
- package/VERSION +1 -1
- package/comandos/especdev/dashboard.md +52 -0
- package/comandos/especdev/gate.md +62 -0
- package/comandos/especdev/metricas.md +42 -0
- package/habilidades/custom-gates/HABILIDAD.md +51 -0
- package/habilidades/metricas-sesion/HABILIDAD.md +72 -0
- package/package.json +1 -1
- package/plantillas/especdev/en/metrics.json +7 -0
- package/plantillas/especdev/es/metrics.json +7 -0
- package/plantillas/especdev/pt/metrics.json +7 -0
- package/plantillas/gates/no-console-log.yml +6 -0
- package/plantillas/gates/no-hardcoded-secrets.yml +6 -0
- package/scripts/.claude/don-cheli/VERSION +1 -1
- package/scripts/.claude/don-cheli/scripts/instalar.sh +1 -1
- package/scripts/generar-config.sh +1 -1
- package/scripts/instalar.sh +1 -1
- package/scripts/sdd-check.sh +407 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ Todos los cambios notables en Don Cheli SDD Framework.
|
|
|
4
4
|
|
|
5
5
|
Formato basado en [Conventional Commits](https://www.conventionalcommits.org/).
|
|
6
6
|
|
|
7
|
+
## [1.17.0](https://github.com/doncheli/don-cheli-sdd/compare/v1.16.5...v1.17.0) (2026-04-02)
|
|
8
|
+
|
|
9
|
+
### Nuevas Funcionalidades
|
|
10
|
+
|
|
11
|
+
* CI/CD integration, Custom Quality Gates y Telemetría (Fases 1-3) ([ac8facb](https://github.com/doncheli/don-cheli-sdd/commit/ac8facbef51543f6c136e614abf07c9f287eafee))
|
|
12
|
+
|
|
13
|
+
## [1.16.5](https://github.com/doncheli/don-cheli-sdd/compare/v1.16.4...v1.16.5) (2026-04-02)
|
|
14
|
+
|
|
15
|
+
### Correcciones
|
|
16
|
+
|
|
17
|
+
* **opencode:** add skills.paths para que OpenCode descubra los skills de Don Cheli ([c73b9fc](https://github.com/doncheli/don-cheli-sdd/commit/c73b9fc55e33be0449975e14a1e530cf97b83c8c))
|
|
18
|
+
|
|
7
19
|
## [1.16.4](https://github.com/doncheli/don-cheli-sdd/compare/v1.16.3...v1.16.4) (2026-04-02)
|
|
8
20
|
|
|
9
21
|
### Correcciones
|
package/README.es.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</p>
|
|
13
13
|
<p align="center">
|
|
14
14
|
<a href="#-instalación"><img src="https://img.shields.io/badge/instalación-2_minutos-brightgreen" alt="Install"></a>
|
|
15
|
-
<img src="https://img.shields.io/badge/versión-1.
|
|
15
|
+
<img src="https://img.shields.io/badge/versión-1.17.0-blue" alt="Version">
|
|
16
16
|
<img src="https://img.shields.io/badge/licencia-Apache%202.0-green" alt="License">
|
|
17
17
|
<img src="https://img.shields.io/badge/idiomas-ES%20|%20EN%20|%20PT-red" alt="Languages">
|
|
18
18
|
<img src="https://img.shields.io/badge/comandos-85+-purple" alt="Commands">
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</p>
|
|
13
13
|
<p align="center">
|
|
14
14
|
<a href="#-installation"><img src="https://img.shields.io/badge/install-2_minutes-brightgreen" alt="Install"></a>
|
|
15
|
-
<img src="https://img.shields.io/badge/version-1.
|
|
15
|
+
<img src="https://img.shields.io/badge/version-1.17.0-blue" alt="Version">
|
|
16
16
|
<img src="https://img.shields.io/badge/license-Apache%202.0-green" alt="License">
|
|
17
17
|
<img src="https://img.shields.io/badge/languages-ES%20|%20EN%20|%20PT-red" alt="Languages">
|
|
18
18
|
<img src="https://img.shields.io/badge/commands-85+-purple" alt="Commands">
|
package/README.pt.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</p>
|
|
13
13
|
<p align="center">
|
|
14
14
|
<a href="#-instalação"><img src="https://img.shields.io/badge/instalação-2_minutos-brightgreen" alt="Install"></a>
|
|
15
|
-
<img src="https://img.shields.io/badge/versão-1.
|
|
15
|
+
<img src="https://img.shields.io/badge/versão-1.17.0-blue" alt="Version">
|
|
16
16
|
<img src="https://img.shields.io/badge/licença-Apache%202.0-green" alt="License">
|
|
17
17
|
<img src="https://img.shields.io/badge/idiomas-ES%20|%20EN%20|%20PT-red" alt="Languages">
|
|
18
18
|
<img src="https://img.shields.io/badge/comandos-85+-purple" alt="Commands">
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.17.0
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Generar dashboard HTML local con métricas de eficiencia del proyecto SDD
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /dc:dashboard — Dashboard de Métricas SDD
|
|
6
|
+
|
|
7
|
+
## Qué hace
|
|
8
|
+
|
|
9
|
+
Genera un archivo `dashboard.html` interactivo con las métricas acumuladas del proyecto.
|
|
10
|
+
|
|
11
|
+
## Fuente de datos
|
|
12
|
+
|
|
13
|
+
Lee `.dc/metrics.json` que se actualiza automáticamente al cerrar cada sesión.
|
|
14
|
+
|
|
15
|
+
## Métricas que muestra
|
|
16
|
+
|
|
17
|
+
### Eficiencia del pipeline
|
|
18
|
+
- **Tiempo por fase:** Promedio de duración de cada fase (Especificar → Revisar)
|
|
19
|
+
- **Quality Gates:** Tasa de aprobación (primera vez vs reintentos)
|
|
20
|
+
- **Stubs detectados:** Cuántos stubs fantasma se encontraron y eliminaron
|
|
21
|
+
|
|
22
|
+
### TDD
|
|
23
|
+
- **Tasa de acierto:** Tests que pasaron al primer intento vs reintentos
|
|
24
|
+
- **Cobertura por feature:** Evolución de cobertura por sesión
|
|
25
|
+
- **RED→GREEN ratio:** Cuántos ciclos TDD se completaron
|
|
26
|
+
|
|
27
|
+
### Estimaciones
|
|
28
|
+
- **Precisión:** Estimado vs real por feature (desviación %)
|
|
29
|
+
- **Modelo más preciso:** Cuál de los 4 modelos acertó más
|
|
30
|
+
|
|
31
|
+
### Seguridad
|
|
32
|
+
- **Hallazgos OWASP:** Por categoría y severidad
|
|
33
|
+
- **Tendencia:** Hallazgos por sesión (debería decrecer)
|
|
34
|
+
|
|
35
|
+
### Sesiones
|
|
36
|
+
- **Total de sesiones:** Completadas y promedio de duración
|
|
37
|
+
- **Productividad:** Features completadas por sesión
|
|
38
|
+
|
|
39
|
+
## Output
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
.dc/dashboard.html — Dashboard HTML standalone (abrir en navegador)
|
|
43
|
+
.dc/metrics.csv — Export CSV para reporting corporativo
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Ejecución
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
/dc:dashboard # Genera dashboard.html
|
|
50
|
+
/dc:dashboard --abrir # Genera y abre en navegador
|
|
51
|
+
/dc:dashboard --csv # Solo exporta CSV
|
|
52
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Crear, listar y ejecutar quality gates custom del proyecto
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /dc:gate — Custom Quality Gates
|
|
6
|
+
|
|
7
|
+
## Subcomandos
|
|
8
|
+
|
|
9
|
+
### `/dc:gate crear <nombre>`
|
|
10
|
+
Crear un nuevo quality gate custom en `.dc/gates/`:
|
|
11
|
+
|
|
12
|
+
1. Preguntar: nombre descriptivo, tipo (grep o script), severidad (block o warn)
|
|
13
|
+
2. Generar archivo YAML en `.dc/gates/<nombre>.yml`
|
|
14
|
+
3. Ejecutar el gate para verificar que funciona
|
|
15
|
+
|
|
16
|
+
### `/dc:gate listar`
|
|
17
|
+
Listar todos los gates custom del proyecto:
|
|
18
|
+
|
|
19
|
+
1. Leer `.dc/gates/*.yml`
|
|
20
|
+
2. Mostrar tabla: nombre, tipo, severidad, estado (activo/desactivado)
|
|
21
|
+
|
|
22
|
+
### `/dc:gate ejecutar [nombre]`
|
|
23
|
+
Ejecutar gates custom:
|
|
24
|
+
|
|
25
|
+
1. Si se pasa nombre: ejecutar solo ese gate
|
|
26
|
+
2. Sin nombre: ejecutar todos los gates en `.dc/gates/`
|
|
27
|
+
3. Mostrar resultado por gate con ✅/⚠️/❌
|
|
28
|
+
4. Resumen final: X pasaron, Y fallaron, Z advertencias
|
|
29
|
+
|
|
30
|
+
### `/dc:gate ci`
|
|
31
|
+
Generar workflow de CI/CD para el proyecto actual:
|
|
32
|
+
|
|
33
|
+
1. Detectar plataforma (GitHub Actions / GitLab CI)
|
|
34
|
+
2. Copiar template desde `examples/ci/` adaptado al proyecto
|
|
35
|
+
3. Incluir custom gates si existen
|
|
36
|
+
|
|
37
|
+
## Formato de gate custom
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
name: Nombre descriptivo
|
|
41
|
+
type: grep | script
|
|
42
|
+
pattern: "regex" # Solo para type: grep
|
|
43
|
+
files: "src/**/*.ts" # Solo para type: grep
|
|
44
|
+
severity: block | warn
|
|
45
|
+
message: "Mensaje al fallar"
|
|
46
|
+
run: | # Solo para type: script
|
|
47
|
+
comando a ejecutar
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Severidad
|
|
51
|
+
|
|
52
|
+
- `block` — Falla la puerta, bloquea el merge
|
|
53
|
+
- `warn` — Advertencia, no bloquea (configurable con `fail-on-warn`)
|
|
54
|
+
|
|
55
|
+
## Ejemplos incluidos
|
|
56
|
+
|
|
57
|
+
Ver `examples/gates/` para 5 gates pre-configurados:
|
|
58
|
+
- `no-console-log.yml` — Prohibir console.log en producción
|
|
59
|
+
- `no-any-typescript.yml` — Evitar tipo `any` en TypeScript
|
|
60
|
+
- `no-hardcoded-secrets.yml` — Detectar secretos hardcodeados
|
|
61
|
+
- `max-file-lines.yml` — Archivos de máximo 300 líneas
|
|
62
|
+
- `require-error-handling.yml` — Manejo de errores en async
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Ver resumen de métricas de eficiencia SDD en terminal
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /dc:metricas — Métricas en Terminal
|
|
6
|
+
|
|
7
|
+
## Qué hace
|
|
8
|
+
|
|
9
|
+
Muestra un resumen compacto de las métricas SDD del proyecto directamente en la terminal.
|
|
10
|
+
|
|
11
|
+
## Fuente de datos
|
|
12
|
+
|
|
13
|
+
Lee `.dc/metrics.json` que se actualiza automáticamente en cada sesión.
|
|
14
|
+
|
|
15
|
+
## Output en terminal
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
═══════════════════════════════════════════
|
|
19
|
+
Don Cheli SDD — Métricas del Proyecto
|
|
20
|
+
═══════════════════════════════════════════
|
|
21
|
+
|
|
22
|
+
📊 Sesiones: 12 completadas (prom. 38 min)
|
|
23
|
+
✅ Quality Gates: 94% aprobación a la primera
|
|
24
|
+
🧪 TDD Acierto: 87% tests pasan al primer intento
|
|
25
|
+
📈 Cobertura: 91% (objetivo: 85%)
|
|
26
|
+
🔍 Stubs: 3 detectados, 3 eliminados
|
|
27
|
+
🛡️ OWASP: 0 críticos, 2 warnings
|
|
28
|
+
📐 Estimación: ±15% desviación promedio
|
|
29
|
+
|
|
30
|
+
Modelo más preciso: Planning Poker IA (±8%)
|
|
31
|
+
Feature más rápida: auth-jwt (1.2h vs 2h estimado)
|
|
32
|
+
═══════════════════════════════════════════
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Subcomandos
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
/dc:metricas # Resumen completo
|
|
39
|
+
/dc:metricas --tdd # Solo métricas de TDD
|
|
40
|
+
/dc:metricas --owasp # Solo métricas de seguridad
|
|
41
|
+
/dc:metricas --estimados # Solo precisión de estimados
|
|
42
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: custom-gates
|
|
3
|
+
description: Sistema de quality gates extensible con plugins YAML
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
tags: [quality, ci-cd, gates, plugins]
|
|
6
|
+
grado_libertad: medio
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Custom Quality Gates
|
|
10
|
+
|
|
11
|
+
## Qué hace
|
|
12
|
+
|
|
13
|
+
Permite a equipos definir sus propias puertas de calidad mediante archivos YAML en `.dc/gates/`.
|
|
14
|
+
|
|
15
|
+
## Tipos de gate
|
|
16
|
+
|
|
17
|
+
### grep
|
|
18
|
+
Busca patrones regex en archivos del proyecto:
|
|
19
|
+
```yaml
|
|
20
|
+
type: grep
|
|
21
|
+
pattern: "regex_aqui"
|
|
22
|
+
files: "src/**/*.ts"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### script
|
|
26
|
+
Ejecuta un script bash que retorna exit 0 (pass) o exit 1 (fail):
|
|
27
|
+
```yaml
|
|
28
|
+
type: script
|
|
29
|
+
run: |
|
|
30
|
+
tu_comando_aqui
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Severidad
|
|
34
|
+
|
|
35
|
+
- `block` — Bloquea el merge/avance
|
|
36
|
+
- `warn` — Advertencia, no bloquea
|
|
37
|
+
|
|
38
|
+
## Integración
|
|
39
|
+
|
|
40
|
+
- Se ejecutan automáticamente en `/dc:revisar`
|
|
41
|
+
- Se ejecutan en CI/CD via `sdd-check.sh` con `INPUT_GATES=custom`
|
|
42
|
+
- Se listan con `/dc:gate listar`
|
|
43
|
+
|
|
44
|
+
## Directorio
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
.dc/gates/
|
|
48
|
+
├── no-console-log.yml
|
|
49
|
+
├── no-hardcoded-secrets.yml
|
|
50
|
+
└── tu-gate-custom.yml
|
|
51
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: metricas-sesion
|
|
3
|
+
description: Telemetría local de eficiencia SDD con tracking de TDD, estimaciones y quality gates
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
tags: [telemetry, metrics, dashboard, reporting]
|
|
6
|
+
grado_libertad: bajo
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Métricas de Sesión
|
|
10
|
+
|
|
11
|
+
## Qué hace
|
|
12
|
+
|
|
13
|
+
Registra automáticamente métricas de cada sesión SDD en `.dc/metrics.json` para tracking de eficiencia.
|
|
14
|
+
|
|
15
|
+
## Datos que captura
|
|
16
|
+
|
|
17
|
+
### Por sesión
|
|
18
|
+
- Timestamp inicio/fin
|
|
19
|
+
- Duración
|
|
20
|
+
- Fase alcanzada (1-6)
|
|
21
|
+
- Quality gates: aprobadas/rechazadas
|
|
22
|
+
- Cobertura alcanzada
|
|
23
|
+
- Stubs detectados
|
|
24
|
+
|
|
25
|
+
### Por feature
|
|
26
|
+
- Escenarios Gherkin generados
|
|
27
|
+
- Tareas TDD completadas
|
|
28
|
+
- Ciclos RED→GREEN
|
|
29
|
+
- Estimado vs real (si se usó /dc:estimar)
|
|
30
|
+
|
|
31
|
+
### Acumulado
|
|
32
|
+
- Total sesiones
|
|
33
|
+
- Promedios por fase
|
|
34
|
+
- Tendencias de cobertura
|
|
35
|
+
- Precisión de estimaciones
|
|
36
|
+
|
|
37
|
+
## Formato de metrics.json
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"version": "1.0.0",
|
|
42
|
+
"project": "mi-proyecto",
|
|
43
|
+
"sessions": [
|
|
44
|
+
{
|
|
45
|
+
"id": "2026-04-01-001",
|
|
46
|
+
"start": "2026-04-01T10:00:00Z",
|
|
47
|
+
"end": "2026-04-01T10:45:00Z",
|
|
48
|
+
"duration_min": 45,
|
|
49
|
+
"phase_reached": 6,
|
|
50
|
+
"gates_passed": 6,
|
|
51
|
+
"gates_total": 6,
|
|
52
|
+
"coverage": 91,
|
|
53
|
+
"stubs_found": 1,
|
|
54
|
+
"stubs_fixed": 1,
|
|
55
|
+
"tdd_cycles": 7,
|
|
56
|
+
"tdd_first_pass": 6
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"features": [],
|
|
60
|
+
"estimates": []
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Activación
|
|
65
|
+
|
|
66
|
+
Se activa automáticamente cuando la habilidad está instalada.
|
|
67
|
+
Se registra al ejecutar `/dc:cerrar-sesion`.
|
|
68
|
+
|
|
69
|
+
## Visualización
|
|
70
|
+
|
|
71
|
+
- `/dc:metricas` — Resumen en terminal
|
|
72
|
+
- `/dc:dashboard` — Dashboard HTML interactivo
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.17.0
|
|
@@ -238,7 +238,7 @@ _gen_continue() {
|
|
|
238
238
|
cat > "$dir/.continue/config/don-cheli.json" 2>/dev/null << 'CONTEOF' || true
|
|
239
239
|
{
|
|
240
240
|
"name": "don-cheli-sdd",
|
|
241
|
-
"version": "1.
|
|
241
|
+
"version": "1.17.0",
|
|
242
242
|
"description": "Don Cheli SDD Framework",
|
|
243
243
|
"rules": [
|
|
244
244
|
"All production code requires tests (TDD: RED → GREEN → REFACTOR)",
|
package/scripts/instalar.sh
CHANGED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Don Cheli SDD Check — Quality Gates for CI/CD
|
|
3
|
+
# Validates SDD artifacts, TDD compliance, coverage and security
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# bash scripts/sdd-check.sh # Run all gates
|
|
7
|
+
# INPUT_GATES=spec,tdd bash scripts/sdd-check.sh # Run specific gates
|
|
8
|
+
#
|
|
9
|
+
# Environment variables (set by GitHub Action or manually):
|
|
10
|
+
# INPUT_GATES — Gates to run: all, spec, tdd, coverage, owasp, custom
|
|
11
|
+
# INPUT_MIN_COVERAGE — Minimum coverage percentage (default: 85)
|
|
12
|
+
# INPUT_FAIL_ON_WARN — Treat warnings as failures (default: false)
|
|
13
|
+
# INPUT_DC_DIR — Path to .dc/ (auto-detect if empty)
|
|
14
|
+
# INPUT_CUSTOM_GATES_DIR — Path to custom gates (default: .dc/gates)
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
# ═══════════════════════════════════════════════════════════════
|
|
19
|
+
# CONFIG
|
|
20
|
+
# ═══════════════════════════════════════════════════════════════
|
|
21
|
+
|
|
22
|
+
GATES="${INPUT_GATES:-all}"
|
|
23
|
+
MIN_COVERAGE="${INPUT_MIN_COVERAGE:-85}"
|
|
24
|
+
FAIL_ON_WARN="${INPUT_FAIL_ON_WARN:-false}"
|
|
25
|
+
CUSTOM_GATES_DIR="${INPUT_CUSTOM_GATES_DIR:-.dc/gates}"
|
|
26
|
+
|
|
27
|
+
# Auto-detect .dc/ or .especdev/ (retrocompatible)
|
|
28
|
+
if [ -n "${INPUT_DC_DIR:-}" ]; then
|
|
29
|
+
DC_DIR="$INPUT_DC_DIR"
|
|
30
|
+
elif [ -d ".dc" ]; then
|
|
31
|
+
DC_DIR=".dc"
|
|
32
|
+
elif [ -d ".especdev" ]; then
|
|
33
|
+
DC_DIR=".especdev"
|
|
34
|
+
else
|
|
35
|
+
DC_DIR=".dc"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# ═══════════════════════════════════════════════════════════════
|
|
39
|
+
# STATE
|
|
40
|
+
# ═══════════════════════════════════════════════════════════════
|
|
41
|
+
|
|
42
|
+
TOTAL_GATES=0
|
|
43
|
+
PASSED_GATES=0
|
|
44
|
+
FAILED_GATES=0
|
|
45
|
+
WARNINGS=0
|
|
46
|
+
REPORT=""
|
|
47
|
+
COVERAGE_VALUE="N/A"
|
|
48
|
+
|
|
49
|
+
# Colors (only for terminal, not for GitHub output)
|
|
50
|
+
if [ -t 1 ]; then
|
|
51
|
+
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'
|
|
52
|
+
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
|
53
|
+
else
|
|
54
|
+
GREEN=''; RED=''; YELLOW=''; CYAN=''; BOLD=''; NC=''
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# ═══════════════════════════════════════════════════════════════
|
|
58
|
+
# HELPERS
|
|
59
|
+
# ═══════════════════════════════════════════════════════════════
|
|
60
|
+
|
|
61
|
+
pass_gate() {
|
|
62
|
+
local name="$1" detail="${2:-}"
|
|
63
|
+
TOTAL_GATES=$((TOTAL_GATES + 1))
|
|
64
|
+
PASSED_GATES=$((PASSED_GATES + 1))
|
|
65
|
+
echo -e "${GREEN}✅${NC} $name${detail:+ — $detail}"
|
|
66
|
+
REPORT="${REPORT}| ✅ | ${name} | ${detail:-OK} |\n"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fail_gate() {
|
|
70
|
+
local name="$1" detail="${2:-}"
|
|
71
|
+
TOTAL_GATES=$((TOTAL_GATES + 1))
|
|
72
|
+
FAILED_GATES=$((FAILED_GATES + 1))
|
|
73
|
+
echo -e "${RED}❌${NC} $name${detail:+ — $detail}"
|
|
74
|
+
REPORT="${REPORT}| ❌ | ${name} | ${detail:-FAILED} |\n"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
warn_gate() {
|
|
78
|
+
local name="$1" detail="${2:-}"
|
|
79
|
+
TOTAL_GATES=$((TOTAL_GATES + 1))
|
|
80
|
+
WARNINGS=$((WARNINGS + 1))
|
|
81
|
+
if [ "$FAIL_ON_WARN" = "true" ]; then
|
|
82
|
+
FAILED_GATES=$((FAILED_GATES + 1))
|
|
83
|
+
echo -e "${YELLOW}⚠️${NC} $name${detail:+ — $detail} (treated as failure)"
|
|
84
|
+
REPORT="${REPORT}| ⚠️→❌ | ${name} | ${detail:-WARNING} |\n"
|
|
85
|
+
else
|
|
86
|
+
PASSED_GATES=$((PASSED_GATES + 1))
|
|
87
|
+
echo -e "${YELLOW}⚠️${NC} $name${detail:+ — $detail}"
|
|
88
|
+
REPORT="${REPORT}| ⚠️ | ${name} | ${detail:-WARNING} |\n"
|
|
89
|
+
fi
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
should_run() {
|
|
93
|
+
local gate="$1"
|
|
94
|
+
[ "$GATES" = "all" ] && return 0
|
|
95
|
+
echo ",$GATES," | grep -q ",$gate," && return 0
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# ═══════════════════════════════════════════════════════════════
|
|
100
|
+
# GATE 1: SPEC VALIDATION
|
|
101
|
+
# ═══════════════════════════════════════════════════════════════
|
|
102
|
+
|
|
103
|
+
run_spec_gate() {
|
|
104
|
+
echo -e "\n${BOLD}Gate 1: Spec Validation${NC}"
|
|
105
|
+
|
|
106
|
+
if [ ! -d "$DC_DIR" ]; then
|
|
107
|
+
fail_gate "SDD Directory" "$DC_DIR/ not found"
|
|
108
|
+
return
|
|
109
|
+
fi
|
|
110
|
+
pass_gate "SDD Directory" "$DC_DIR/ exists"
|
|
111
|
+
|
|
112
|
+
# Check for config
|
|
113
|
+
if [ -f "$DC_DIR/config.yaml" ]; then
|
|
114
|
+
pass_gate "Config" "config.yaml present"
|
|
115
|
+
else
|
|
116
|
+
warn_gate "Config" "config.yaml not found"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# Check for specs
|
|
120
|
+
SPEC_COUNT=$(find "$DC_DIR/specs" -name "*.feature" 2>/dev/null | wc -l | tr -d ' ')
|
|
121
|
+
if [ "$SPEC_COUNT" -gt 0 ]; then
|
|
122
|
+
pass_gate "Gherkin Specs" "$SPEC_COUNT .feature files found"
|
|
123
|
+
else
|
|
124
|
+
fail_gate "Gherkin Specs" "No .feature files in $DC_DIR/specs/"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Check for blueprint
|
|
128
|
+
BP_COUNT=$(find "$DC_DIR/blueprints" -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
|
|
129
|
+
if [ "$BP_COUNT" -gt 0 ]; then
|
|
130
|
+
pass_gate "Blueprint" "$BP_COUNT blueprint(s) found"
|
|
131
|
+
else
|
|
132
|
+
warn_gate "Blueprint" "No blueprints in $DC_DIR/blueprints/"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# Check estado/status
|
|
136
|
+
if [ -f "$DC_DIR/estado.md" ] || [ -f "$DC_DIR/status.md" ]; then
|
|
137
|
+
pass_gate "Status File" "Project status tracked"
|
|
138
|
+
else
|
|
139
|
+
warn_gate "Status File" "No estado.md or status.md"
|
|
140
|
+
fi
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# ═══════════════════════════════════════════════════════════════
|
|
144
|
+
# GATE 2: TDD COMPLIANCE
|
|
145
|
+
# ═══════════════════════════════════════════════════════════════
|
|
146
|
+
|
|
147
|
+
run_tdd_gate() {
|
|
148
|
+
echo -e "\n${BOLD}Gate 2: TDD Compliance${NC}"
|
|
149
|
+
|
|
150
|
+
# Check for test files
|
|
151
|
+
TEST_COUNT=0
|
|
152
|
+
for pattern in "test/" "tests/" "__tests__/" "spec/" "*_test.go" "*.test.ts" "*.test.js" "*.spec.ts" "*.spec.js" "test_*.py" "*_test.py"; do
|
|
153
|
+
COUNT=$(find . -path "./$DC_DIR" -prune -o -path "./node_modules" -prune -o \( -name "$pattern" -o -path "*/$pattern*" \) -print 2>/dev/null | wc -l | tr -d ' ')
|
|
154
|
+
TEST_COUNT=$((TEST_COUNT + COUNT))
|
|
155
|
+
done
|
|
156
|
+
|
|
157
|
+
if [ "$TEST_COUNT" -gt 0 ]; then
|
|
158
|
+
pass_gate "Test Files" "$TEST_COUNT test file(s) found"
|
|
159
|
+
else
|
|
160
|
+
fail_gate "Test Files" "No test files found"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# Check for stub detection (// TODO, // FIXME in src)
|
|
164
|
+
STUB_COUNT=0
|
|
165
|
+
for ext in ts js py go rb java; do
|
|
166
|
+
STUBS=$(grep -rn "// TODO\|// FIXME\|# TODO\|# FIXME\|pass #\|raise NotImplementedError" \
|
|
167
|
+
--include="*.$ext" . 2>/dev/null \
|
|
168
|
+
| grep -v node_modules | grep -v "$DC_DIR" | grep -v test | wc -l | tr -d ' ')
|
|
169
|
+
STUB_COUNT=$((STUB_COUNT + STUBS))
|
|
170
|
+
done
|
|
171
|
+
|
|
172
|
+
if [ "$STUB_COUNT" -eq 0 ]; then
|
|
173
|
+
pass_gate "Stub Detection" "No TODO/FIXME stubs in source code"
|
|
174
|
+
elif [ "$STUB_COUNT" -le 3 ]; then
|
|
175
|
+
warn_gate "Stub Detection" "$STUB_COUNT stub(s) found"
|
|
176
|
+
else
|
|
177
|
+
fail_gate "Stub Detection" "$STUB_COUNT stubs found — possible silent stubs"
|
|
178
|
+
fi
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# ═══════════════════════════════════════════════════════════════
|
|
182
|
+
# GATE 3: COVERAGE
|
|
183
|
+
# ═══════════════════════════════════════════════════════════════
|
|
184
|
+
|
|
185
|
+
run_coverage_gate() {
|
|
186
|
+
echo -e "\n${BOLD}Gate 3: Coverage${NC}"
|
|
187
|
+
|
|
188
|
+
# Try to find coverage report
|
|
189
|
+
COV_FILE=""
|
|
190
|
+
for f in coverage/lcov.info coverage/cobertura.xml coverage/coverage.json htmlcov/index.html coverage.xml .coverage; do
|
|
191
|
+
if [ -f "$f" ]; then
|
|
192
|
+
COV_FILE="$f"
|
|
193
|
+
break
|
|
194
|
+
fi
|
|
195
|
+
done
|
|
196
|
+
|
|
197
|
+
if [ -z "$COV_FILE" ]; then
|
|
198
|
+
warn_gate "Coverage Report" "No coverage report found. Run tests with coverage first."
|
|
199
|
+
return
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
# Extract coverage percentage based on format
|
|
203
|
+
COV_PCT=0
|
|
204
|
+
case "$COV_FILE" in
|
|
205
|
+
*lcov.info)
|
|
206
|
+
LINES_HIT=$(grep -c "^DA:" "$COV_FILE" 2>/dev/null || echo 0)
|
|
207
|
+
LINES_FOUND=$(grep "^DA:" "$COV_FILE" 2>/dev/null | grep -c ",0$" || echo 0)
|
|
208
|
+
if [ "$LINES_HIT" -gt 0 ]; then
|
|
209
|
+
COV_PCT=$(( (LINES_HIT - LINES_FOUND) * 100 / LINES_HIT ))
|
|
210
|
+
fi
|
|
211
|
+
;;
|
|
212
|
+
*cobertura.xml|*coverage.xml)
|
|
213
|
+
COV_PCT=$(grep -o 'line-rate="[0-9.]*"' "$COV_FILE" 2>/dev/null | head -1 | grep -o '[0-9.]*' | head -1 || echo 0)
|
|
214
|
+
COV_PCT=$(echo "$COV_PCT * 100" | bc 2>/dev/null | cut -d. -f1 || echo 0)
|
|
215
|
+
;;
|
|
216
|
+
*coverage.json)
|
|
217
|
+
COV_PCT=$(python3 -c "import json; d=json.load(open('$COV_FILE')); print(int(d.get('totals',{}).get('lines',{}).get('percent',0)))" 2>/dev/null || echo 0)
|
|
218
|
+
;;
|
|
219
|
+
*)
|
|
220
|
+
COV_PCT=0
|
|
221
|
+
;;
|
|
222
|
+
esac
|
|
223
|
+
|
|
224
|
+
COVERAGE_VALUE="${COV_PCT}%"
|
|
225
|
+
|
|
226
|
+
if [ "$COV_PCT" -ge "$MIN_COVERAGE" ]; then
|
|
227
|
+
pass_gate "Coverage" "${COV_PCT}% >= ${MIN_COVERAGE}% minimum"
|
|
228
|
+
else
|
|
229
|
+
fail_gate "Coverage" "${COV_PCT}% < ${MIN_COVERAGE}% minimum"
|
|
230
|
+
fi
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# ═══════════════════════════════════════════════════════════════
|
|
234
|
+
# GATE 4: OWASP QUICK AUDIT
|
|
235
|
+
# ═══════════════════════════════════════════════════════════════
|
|
236
|
+
|
|
237
|
+
run_owasp_gate() {
|
|
238
|
+
echo -e "\n${BOLD}Gate 4: OWASP Quick Audit${NC}"
|
|
239
|
+
|
|
240
|
+
OWASP_ISSUES=0
|
|
241
|
+
|
|
242
|
+
# A01: Hardcoded secrets
|
|
243
|
+
SECRETS=$(grep -rn "password\s*=\s*['\"].\+['\"]" --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.java" \
|
|
244
|
+
. 2>/dev/null | grep -v node_modules | grep -v test | grep -v "$DC_DIR" | wc -l | tr -d ' ')
|
|
245
|
+
if [ "$SECRETS" -gt 0 ]; then
|
|
246
|
+
warn_gate "A01: Hardcoded Secrets" "$SECRETS potential hardcoded passwords"
|
|
247
|
+
OWASP_ISSUES=$((OWASP_ISSUES + SECRETS))
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
# A03: SQL Injection
|
|
251
|
+
SQLI=$(grep -rn "f\".*SELECT\|f\".*INSERT\|f\".*DELETE\|f\".*UPDATE\|\".*SELECT.*\" *%" \
|
|
252
|
+
--include="*.py" --include="*.js" --include="*.ts" . 2>/dev/null \
|
|
253
|
+
| grep -v node_modules | grep -v test | grep -v "$DC_DIR" | wc -l | tr -d ' ')
|
|
254
|
+
if [ "$SQLI" -gt 0 ]; then
|
|
255
|
+
fail_gate "A03: SQL Injection" "$SQLI potential SQL injection patterns"
|
|
256
|
+
OWASP_ISSUES=$((OWASP_ISSUES + SQLI))
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
# A03: XSS via innerHTML/dangerouslySetInnerHTML
|
|
260
|
+
XSS=$(grep -rn "innerHTML\|dangerouslySetInnerHTML\|v-html" \
|
|
261
|
+
--include="*.js" --include="*.ts" --include="*.tsx" --include="*.jsx" --include="*.vue" . 2>/dev/null \
|
|
262
|
+
| grep -v node_modules | grep -v test | grep -v "$DC_DIR" | wc -l | tr -d ' ')
|
|
263
|
+
if [ "$XSS" -gt 0 ]; then
|
|
264
|
+
warn_gate "A03: XSS" "$XSS innerHTML/dangerouslySetInnerHTML usages"
|
|
265
|
+
OWASP_ISSUES=$((OWASP_ISSUES + XSS))
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
# A07: .env committed
|
|
269
|
+
if [ -f ".env" ] && ! grep -q "^\.env$" .gitignore 2>/dev/null; then
|
|
270
|
+
fail_gate "A07: .env exposed" ".env file exists and is not in .gitignore"
|
|
271
|
+
OWASP_ISSUES=$((OWASP_ISSUES + 1))
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
if [ "$OWASP_ISSUES" -eq 0 ]; then
|
|
275
|
+
pass_gate "OWASP Quick Audit" "No critical issues detected"
|
|
276
|
+
fi
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
# ═══════════════════════════════════════════════════════════════
|
|
280
|
+
# GATE 5: CUSTOM GATES
|
|
281
|
+
# ═══════════════════════════════════════════════════════════════
|
|
282
|
+
|
|
283
|
+
run_custom_gates() {
|
|
284
|
+
echo -e "\n${BOLD}Gate 5: Custom Gates${NC}"
|
|
285
|
+
|
|
286
|
+
if [ ! -d "$CUSTOM_GATES_DIR" ]; then
|
|
287
|
+
echo -e " ${CYAN}ℹ${NC} No custom gates directory ($CUSTOM_GATES_DIR)"
|
|
288
|
+
return
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
GATE_FILES=$(find "$CUSTOM_GATES_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null)
|
|
292
|
+
if [ -z "$GATE_FILES" ]; then
|
|
293
|
+
echo -e " ${CYAN}ℹ${NC} No custom gate files found"
|
|
294
|
+
return
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
for gate_file in $GATE_FILES; do
|
|
298
|
+
# Parse YAML manually (no yq dependency)
|
|
299
|
+
GATE_NAME=$(grep "^name:" "$gate_file" | sed 's/^name:\s*//' | tr -d '"' | tr -d "'")
|
|
300
|
+
GATE_TYPE=$(grep "^type:" "$gate_file" | sed 's/^type:\s*//' | tr -d '"' | tr -d "'")
|
|
301
|
+
GATE_SEVERITY=$(grep "^severity:" "$gate_file" | sed 's/^severity:\s*//' | tr -d '"' | tr -d "'")
|
|
302
|
+
GATE_PATTERN=$(grep "^pattern:" "$gate_file" | sed 's/^pattern:\s*//' | tr -d '"' | tr -d "'")
|
|
303
|
+
GATE_FILES_GLOB=$(grep "^files:" "$gate_file" | sed 's/^files:\s*//' | tr -d '"' | tr -d "'")
|
|
304
|
+
GATE_MESSAGE=$(grep "^message:" "$gate_file" | sed 's/^message:\s*//' | tr -d '"' | tr -d "'")
|
|
305
|
+
|
|
306
|
+
[ -z "$GATE_NAME" ] && GATE_NAME=$(basename "$gate_file" .yml)
|
|
307
|
+
[ -z "$GATE_SEVERITY" ] && GATE_SEVERITY="warn"
|
|
308
|
+
|
|
309
|
+
case "$GATE_TYPE" in
|
|
310
|
+
grep)
|
|
311
|
+
MATCHES=$(grep -rn "$GATE_PATTERN" --include="$(echo "$GATE_FILES_GLOB" | sed 's|.*/||')" . 2>/dev/null \
|
|
312
|
+
| grep -v node_modules | grep -v "$DC_DIR" | wc -l | tr -d ' ')
|
|
313
|
+
if [ "$MATCHES" -gt 0 ]; then
|
|
314
|
+
if [ "$GATE_SEVERITY" = "block" ]; then
|
|
315
|
+
fail_gate "Custom: $GATE_NAME" "${GATE_MESSAGE:-$MATCHES matches found}"
|
|
316
|
+
else
|
|
317
|
+
warn_gate "Custom: $GATE_NAME" "${GATE_MESSAGE:-$MATCHES matches found}"
|
|
318
|
+
fi
|
|
319
|
+
else
|
|
320
|
+
pass_gate "Custom: $GATE_NAME" "No matches"
|
|
321
|
+
fi
|
|
322
|
+
;;
|
|
323
|
+
script)
|
|
324
|
+
GATE_RUN=$(sed -n '/^run:/,/^[a-z]/p' "$gate_file" | grep -v "^run:" | grep -v "^[a-z]" | sed 's/^\s*//')
|
|
325
|
+
if [ -n "$GATE_RUN" ]; then
|
|
326
|
+
if eval "$GATE_RUN" > /dev/null 2>&1; then
|
|
327
|
+
pass_gate "Custom: $GATE_NAME" "Script passed"
|
|
328
|
+
else
|
|
329
|
+
if [ "$GATE_SEVERITY" = "block" ]; then
|
|
330
|
+
fail_gate "Custom: $GATE_NAME" "${GATE_MESSAGE:-Script failed}"
|
|
331
|
+
else
|
|
332
|
+
warn_gate "Custom: $GATE_NAME" "${GATE_MESSAGE:-Script failed}"
|
|
333
|
+
fi
|
|
334
|
+
fi
|
|
335
|
+
fi
|
|
336
|
+
;;
|
|
337
|
+
*)
|
|
338
|
+
warn_gate "Custom: $GATE_NAME" "Unknown gate type: $GATE_TYPE"
|
|
339
|
+
;;
|
|
340
|
+
esac
|
|
341
|
+
done
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
# ═══════════════════════════════════════════════════════════════
|
|
345
|
+
# MAIN
|
|
346
|
+
# ═══════════════════════════════════════════════════════════════
|
|
347
|
+
|
|
348
|
+
echo -e "${BOLD}═══════════════════════════════════════════${NC}"
|
|
349
|
+
echo -e "${BOLD} Don Cheli SDD — Quality Gates Check${NC}"
|
|
350
|
+
echo -e "${BOLD}═══════════════════════════════════════════${NC}"
|
|
351
|
+
echo ""
|
|
352
|
+
echo -e " Directory: ${CYAN}$DC_DIR${NC}"
|
|
353
|
+
echo -e " Gates: ${CYAN}$GATES${NC}"
|
|
354
|
+
echo -e " Coverage: ${CYAN}>= ${MIN_COVERAGE}%${NC}"
|
|
355
|
+
|
|
356
|
+
should_run "spec" && run_spec_gate
|
|
357
|
+
should_run "tdd" && run_tdd_gate
|
|
358
|
+
should_run "coverage" && run_coverage_gate
|
|
359
|
+
should_run "owasp" && run_owasp_gate
|
|
360
|
+
should_run "custom" && run_custom_gates
|
|
361
|
+
|
|
362
|
+
# ═══════════════════════════════════════════════════════════════
|
|
363
|
+
# RESULTS
|
|
364
|
+
# ═══════════════════════════════════════════════════════════════
|
|
365
|
+
|
|
366
|
+
echo ""
|
|
367
|
+
echo -e "${BOLD}═══════════════════════════════════════════${NC}"
|
|
368
|
+
|
|
369
|
+
ALL_PASSED="true"
|
|
370
|
+
if [ "$FAILED_GATES" -gt 0 ]; then
|
|
371
|
+
ALL_PASSED="false"
|
|
372
|
+
echo -e "${RED}${BOLD} ❌ FAILED — $PASSED_GATES/$TOTAL_GATES gates passed ($FAILED_GATES failed, $WARNINGS warnings)${NC}"
|
|
373
|
+
else
|
|
374
|
+
echo -e "${GREEN}${BOLD} ✅ PASSED — $PASSED_GATES/$TOTAL_GATES gates passed ($WARNINGS warnings)${NC}"
|
|
375
|
+
fi
|
|
376
|
+
|
|
377
|
+
echo -e "${BOLD}═══════════════════════════════════════════${NC}"
|
|
378
|
+
|
|
379
|
+
# Build markdown report for PR comment
|
|
380
|
+
FULL_REPORT="## 🛡️ Don Cheli SDD — Quality Gates\n\n"
|
|
381
|
+
if [ "$ALL_PASSED" = "true" ]; then
|
|
382
|
+
FULL_REPORT="${FULL_REPORT}**✅ All gates passed** ($PASSED_GATES/$TOTAL_GATES)\n\n"
|
|
383
|
+
else
|
|
384
|
+
FULL_REPORT="${FULL_REPORT}**❌ $FAILED_GATES gate(s) failed** ($PASSED_GATES/$TOTAL_GATES passed)\n\n"
|
|
385
|
+
fi
|
|
386
|
+
FULL_REPORT="${FULL_REPORT}| Status | Gate | Detail |\n|--------|------|--------|\n${REPORT}\n"
|
|
387
|
+
FULL_REPORT="${FULL_REPORT}---\n*Generated by [Don Cheli SDD](https://github.com/doncheli/don-cheli-sdd)*"
|
|
388
|
+
|
|
389
|
+
# GitHub Actions outputs
|
|
390
|
+
if [ -n "${GITHUB_OUTPUT:-}" ]; then
|
|
391
|
+
{
|
|
392
|
+
echo "passed=$ALL_PASSED"
|
|
393
|
+
echo "coverage=$COVERAGE_VALUE"
|
|
394
|
+
echo "gates_passed=$PASSED_GATES"
|
|
395
|
+
echo "gates_total=$TOTAL_GATES"
|
|
396
|
+
# Multiline output
|
|
397
|
+
echo "report<<ENDOFREPORT"
|
|
398
|
+
printf "%b" "$FULL_REPORT"
|
|
399
|
+
echo ""
|
|
400
|
+
echo "ENDOFREPORT"
|
|
401
|
+
} >> "$GITHUB_OUTPUT"
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
# Exit code
|
|
405
|
+
if [ "$ALL_PASSED" = "false" ]; then
|
|
406
|
+
exit 1
|
|
407
|
+
fi
|