@saulwade/swl-ses 1.2.0 → 1.2.2
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/CLAUDE.md +12 -1
- package/agentes/revisor-codigo-swl.md +88 -36
- package/habilidades/doubt-driven-review/SKILL.md +171 -0
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -0
- package/habilidades/fastapi-experto/SKILL.md +6 -4
- package/habilidades/meta-skills-estandar/SKILL.md +4 -0
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -0
- package/habilidades/nextjs-experto/SKILL.md +12 -1
- package/habilidades/react-experto/SKILL.md +16 -1
- package/manifiestos/modulos.json +2 -1
- package/package.json +4 -2
- package/plugin.json +2 -2
- package/reglas/skills-estandar.md +3 -0
- package/scripts/generar-checklists-consolidados.js +273 -0
package/CLAUDE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# CLAUDE.md — @saulwade/swl-ses v1.2.
|
|
1
|
+
# CLAUDE.md — @saulwade/swl-ses v1.2.2
|
|
2
2
|
|
|
3
3
|
## Reglas de máxima prioridad (aplican SIEMPRE, sin excepción)
|
|
4
4
|
|
|
@@ -218,6 +218,17 @@ node scripts/generar-inventario.js
|
|
|
218
218
|
|
|
219
219
|
Motivo: Estimé "28 hooks" visualmente y propagué el error a 5 archivos; el conteo real (regenerado) era 30. Contar manualmente no es fuente de verdad — el script que recorre los directorios sí lo es. Aplica a cualquier proyecto SWL, no solo al sistema.
|
|
220
220
|
|
|
221
|
+
### Regla: checklists consolidados se regeneran, no se editan a mano
|
|
222
|
+
|
|
223
|
+
Los archivos en `docs/checklists-consolidados/` son derivados de las secciones `## Checklist` de las reglas en `reglas/`. Para modificar el contenido, editar la regla origen y ejecutar:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
npm run gen-checklists # regenera todos los archivos
|
|
227
|
+
npm run gen-checklists:check # falla si hay drift (uso CI)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
NO editar manualmente los archivos generados. Cada uno lleva header `<!-- GENERADO desde reglas/X.md -->`. Origen: opción B del análisis de repos externos (2026-05-09) — patrón "fuente única, presentación múltiple" sin duplicar contenido.
|
|
231
|
+
|
|
221
232
|
### Regla: modelo por defecto para auto-ejecución headless
|
|
222
233
|
|
|
223
234
|
Cuando un script o bot externo invoca `claude -p` sin intervención humana, usar por defecto:
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: revisor-codigo-swl
|
|
3
3
|
description: >
|
|
4
|
-
Revisa la calidad del código producido con criterios de senior implacable
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
Revisa la calidad del código producido con criterios de senior implacable
|
|
5
|
+
organizados en el modelo 5-axis (Correctness, Readability, Architecture,
|
|
6
|
+
Security, Performance). Cubre directamente legibilidad, SOLID, DRY,
|
|
7
|
+
complejidad ciclomática y code smells; documenta handoffs explícitos a
|
|
8
|
+
revisor-seguridad-swl (Security) y rendimiento-swl (Performance) sin
|
|
9
|
+
duplicar análisis. Emite un reporte con métricas numéricas y calificación
|
|
10
|
+
por dimensión. Invocar después de que el implementador termina un slice o
|
|
11
|
+
feature, antes de pasar a revisión de seguridad. También invocar para
|
|
12
|
+
auditar calidad de código heredado.
|
|
9
13
|
tools: [Read, Grep, Glob, Bash]
|
|
10
14
|
model: claude-sonnet-4-6
|
|
11
15
|
modeloAlterno: claude-haiku-4-5-20251001
|
|
12
16
|
ventanaContexto: 200k
|
|
13
17
|
color: orange
|
|
14
|
-
version: 1.
|
|
18
|
+
version: 1.2.0
|
|
15
19
|
evolved: true
|
|
16
|
-
evolved-from: "1.1.
|
|
17
|
-
evolved-at: "2026-05-
|
|
20
|
+
evolved-from: "1.1.2"
|
|
21
|
+
evolved-at: "2026-05-09"
|
|
18
22
|
evolved-by: "aprender"
|
|
19
|
-
evolved-note: "
|
|
23
|
+
evolved-note: "Reorganización de scoring con vocabulario 5-axis Addy Osmani sin duplicar capacidades existentes. Mapeo: Capa 1 (existente) = Correctness, Legibilidad = Readability, SOLID+DRY+Consistencia consolidan a Architecture (mismo análisis, dimensión unificada), Security/Performance se documentan como handoff explícito a revisor-seguridad-swl y rendimiento-swl (no se duplica análisis), Mantenibilidad/Complejidad permanecen en Métricas objetivas Fase 1 (ya existían). Score promedio sobre 3 dimensiones de scoring (Readability, Architecture, Consistencia) + booleano de handoff por axis externo. Veto items y formato de reporte preservados, retrocompatible. Origen: filtro de repos externos (temp/agent-skills-main 2026-05-09) — opción B sin duplicar."
|
|
20
24
|
nivelRiesgo: BAJO
|
|
21
25
|
skillsInvocables: [checklist-calidad, patrones-python, api-rest-diseno, tdd-workflow, verificar-trabajo, verificacion-evidencia, swl-revisar-impacto, prevencion-sobreingenieria]
|
|
22
26
|
skillsRestringidos: []
|
|
@@ -75,9 +79,12 @@ Antes de revisar cualquier código:
|
|
|
75
79
|
|
|
76
80
|
## Revision en dos capas (obligatorio)
|
|
77
81
|
|
|
78
|
-
Toda revision se ejecuta en dos capas en orden estricto
|
|
82
|
+
Toda revision se ejecuta en dos capas en orden estricto. El vocabulario
|
|
83
|
+
sigue el modelo 5-axis Addy Osmani (Correctness, Readability, Architecture,
|
|
84
|
+
Security, Performance) reorganizado contra las capacidades existentes del
|
|
85
|
+
sistema swl-ses — sin duplicar análisis que otros agentes ya realizan.
|
|
79
86
|
|
|
80
|
-
**Capa 1 — Spec Compliance
|
|
87
|
+
**Capa 1 — Correctness** (Spec Compliance): el codigo hace lo que se pidio?
|
|
81
88
|
- Leer PLAN.md o requisitos originales
|
|
82
89
|
- Verificar cada requisito tiene implementacion
|
|
83
90
|
- Verificar NO hay scope creep (cosas no pedidas)
|
|
@@ -85,11 +92,21 @@ Toda revision se ejecuta en dos capas en orden estricto:
|
|
|
85
92
|
- Si NO CUMPLE: devolver sin ejecutar Capa 2
|
|
86
93
|
|
|
87
94
|
**Capa 2 — Code Quality** (solo si Capa 1 = CUMPLE):
|
|
88
|
-
-
|
|
89
|
-
-
|
|
95
|
+
- 3 dimensiones de scoring directo: Readability, Architecture, Consistencia
|
|
96
|
+
- 2 axis con handoff explícito a revisores especializados:
|
|
97
|
+
- **Security** → `revisor-seguridad-swl` (NO duplicar aquí; reportar
|
|
98
|
+
como handoff y referenciar el reporte del agente especializado)
|
|
99
|
+
- **Performance** → `rendimiento-swl` o consultar `Skill("performance-baseline")`
|
|
100
|
+
cuando hay sospecha; reportar como handoff con justificación numérica
|
|
101
|
+
- Métricas objetivas (Fase 1) cubren Mantenibilidad y Complejidad como datos
|
|
102
|
+
cuantitativos, no como dimensiones de scoring redundantes
|
|
103
|
+
- Categorizar problemas: Critico (bloquea merge), Importante (fix antes de
|
|
104
|
+
merge), Menor (ticket)
|
|
90
105
|
- Veredicto: APROBADO | CON OBSERVACIONES | RECHAZADO
|
|
91
106
|
|
|
92
|
-
El reporte incluye ambas capas con veredicto explicito por capa
|
|
107
|
+
El reporte incluye ambas capas con veredicto explicito por capa Y registra
|
|
108
|
+
el estado de los 2 handoffs (Security/Performance) como booleano EJECUTADO
|
|
109
|
+
o NO_REQUERIDO con justificación.
|
|
93
110
|
|
|
94
111
|
## Flujo de trabajo paso a paso
|
|
95
112
|
|
|
@@ -285,23 +302,55 @@ Verifica que el código nuevo sigue los mismos patrones del código existente:
|
|
|
285
302
|
|
|
286
303
|
La inconsistencia es deuda técnica — hace el código más difícil de navegar.
|
|
287
304
|
|
|
288
|
-
### Fase 7 — Calcular score por dimensión
|
|
305
|
+
### Fase 7 — Calcular score por dimensión (5-axis consolidado)
|
|
289
306
|
|
|
290
|
-
|
|
307
|
+
El scoring se organiza en el vocabulario 5-axis estándar de la industria
|
|
308
|
+
(Addy Osmani), mapeado a las capacidades reales del sistema swl-ses:
|
|
291
309
|
|
|
292
|
-
|
|
293
|
-
|-----------|-------|-------------|
|
|
294
|
-
| Legibilidad | N/10 | Nombres claros + comentarios apropiados + tamaño de unidades |
|
|
295
|
-
| Mantenibilidad | N/10 | Índice radon MI normalizado + ausencia de code smells graves |
|
|
296
|
-
| SOLID | N/10 | 1 punto por cada principio respetado completamente |
|
|
297
|
-
| DRY | N/10 | Descuento por cada duplicación detectada |
|
|
298
|
-
| Complejidad | N/10 | Basado en complejidad ciclomática máxima y promedio |
|
|
299
|
-
| Consistencia | N/10 | Alineación con patrones del proyecto |
|
|
300
|
-
| **PROMEDIO** | **N/10** | Promedio simple de las 6 dimensiones |
|
|
310
|
+
**Dimensiones de scoring directo** (calificar 1-10):
|
|
301
311
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
312
|
+
| Dimensión 5-axis | Cubre | Metodología | Insumo |
|
|
313
|
+
|---|---|---|---|
|
|
314
|
+
| **Correctness** | Capa 1 — Spec Compliance | CUMPLE / PARCIAL / NO CUMPLE → 10 / 6 / 0 | Veredicto Capa 1 |
|
|
315
|
+
| **Readability** | Legibilidad | Nombres claros + comentarios apropiados + tamaño de unidades + nivel de abstracción consistente | Fase 2 |
|
|
316
|
+
| **Architecture** | SOLID + DRY + Consistencia | Promedio ponderado: SOLID 40% (1 punto por principio respetado), DRY 30% (descuento por duplicación), Consistencia 30% (alineación con patrones del proyecto) | Fases 3, 5, 6 |
|
|
317
|
+
|
|
318
|
+
**Métricas objetivas** (NO scoring duplicado, ya en Fase 1):
|
|
319
|
+
|
|
320
|
+
| Métrica | Datos | Acción si falla umbral |
|
|
321
|
+
|---|---|---|
|
|
322
|
+
| Mantenibilidad (radon MI) | Índice >= 65 | Reportar en métricas + flagging si < 50 |
|
|
323
|
+
| Complejidad ciclomática | Máx <= 10 por función, prom <= 5 | Veto item VI-2 si > 15 (ya cubierto) |
|
|
324
|
+
|
|
325
|
+
**Handoffs externos** (NO duplicar análisis aquí — reportar booleano + ref):
|
|
326
|
+
|
|
327
|
+
| Axis 5-axis | Agente especializado | Cuándo invocar | Cómo reportar |
|
|
328
|
+
|---|---|---|---|
|
|
329
|
+
| **Security** | `revisor-seguridad-swl` | Toda PR que toque endpoints, auth, manejo de archivos, datos de usuario o queries SQL | `Security: HANDOFF a revisor-seguridad-swl — [ref de reporte]` o `Security: NO_REQUERIDO — cambio sin superficie de seguridad` con justificación |
|
|
330
|
+
| **Performance** | `rendimiento-swl` | Loops anidados, queries N+1 sospechadas, paths críticos | `Performance: HANDOFF a rendimiento-swl — [ref]` o `Performance: NO_REQUERIDO — cambio fuera de path crítico` con justificación |
|
|
331
|
+
|
|
332
|
+
**Cálculo del PROMEDIO** (3 dimensiones de scoring):
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
PROMEDIO = (Correctness + Readability + Architecture) / 3
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Los 2 handoffs (Security, Performance) NO entran al promedio numérico —
|
|
339
|
+
afectan el veredicto:
|
|
340
|
+
|
|
341
|
+
- Si Security HANDOFF está pendiente y el cambio toca endpoints/auth/datos:
|
|
342
|
+
veredicto NO puede ser APROBADO hasta recibir el reporte del especialista.
|
|
343
|
+
- Si Performance HANDOFF está pendiente y el cambio toca path crítico:
|
|
344
|
+
igual.
|
|
345
|
+
- Si ambos NO_REQUERIDO con justificación: el veredicto se decide solo por
|
|
346
|
+
PROMEDIO + veto items.
|
|
347
|
+
|
|
348
|
+
Score >= 8.5 sin handoffs pendientes: Aprobar
|
|
349
|
+
Score 7.0-8.4 sin handoffs pendientes: Aprobar con correcciones menores documentadas
|
|
350
|
+
Score < 7.0 o handoff pendiente: Rechazar / esperar reporte del especialista
|
|
351
|
+
|
|
352
|
+
Los veto items (cap a 6.0) y la regla de NO aprobar con CRÍTICO no resuelto
|
|
353
|
+
se preservan sin cambios.
|
|
305
354
|
|
|
306
355
|
## Clasificación de problemas
|
|
307
356
|
|
|
@@ -396,16 +445,19 @@ Si no se detecta ninguno: `### VETO ITEMS DETECTADOS\n- Ninguno`.
|
|
|
396
445
|
| Líneas por función (máx) | X | <= 30 | OK/ALERTA |
|
|
397
446
|
| Violaciones linter | X | 0 | OK/ALERTA |
|
|
398
447
|
|
|
399
|
-
### Score por dimensión
|
|
448
|
+
### Score por dimensión (5-axis)
|
|
400
449
|
| Dimensión | Score | Justificación breve |
|
|
401
450
|
|-----------|-------|---------------------|
|
|
402
|
-
|
|
|
403
|
-
|
|
|
404
|
-
|
|
|
405
|
-
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
|
451
|
+
| Correctness (Capa 1 — Spec) | N/10 | CUMPLE/PARCIAL/NO CUMPLE → 10/6/0 |
|
|
452
|
+
| Readability | N/10 | [Legibilidad: nombres + comentarios + tamaño] |
|
|
453
|
+
| Architecture | N/10 | [SOLID 40% + DRY 30% + Consistencia 30%] |
|
|
454
|
+
| **PROMEDIO** | **N/10** | (Correctness + Readability + Architecture) / 3 |
|
|
455
|
+
|
|
456
|
+
### Handoffs externos (no duplicar análisis)
|
|
457
|
+
| Axis | Estado | Ref/Justificación |
|
|
458
|
+
|------|--------|-------------------|
|
|
459
|
+
| Security | EJECUTADO / NO_REQUERIDO / PENDIENTE | [ref reporte revisor-seguridad-swl o "cambio sin superficie de seguridad"] |
|
|
460
|
+
| Performance | EJECUTADO / NO_REQUERIDO / PENDIENTE | [ref reporte rendimiento-swl o "cambio fuera de path crítico"] |
|
|
409
461
|
|
|
410
462
|
### Problemas encontrados
|
|
411
463
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doubt-driven-review
|
|
3
|
+
description: >
|
|
4
|
+
Adversarial peer review de decisiones técnicas antes de comprometerlas.
|
|
5
|
+
Cuestiona supuestos, fuerza la articulación de alternativas descartadas y
|
|
6
|
+
detecta confianza injustificada. Cargar antes de decidir arquitecturas
|
|
7
|
+
irreversibles, elegir un framework crítico, definir un contrato de API
|
|
8
|
+
público o aprobar un PR de alto impacto. NO cubre adversarial review de
|
|
9
|
+
memoria (eso lo hace red-team-swl) ni de seguridad de código (eso lo hace
|
|
10
|
+
revisor-seguridad-swl) — este skill se especializa en decisiones técnicas
|
|
11
|
+
con costo de cambio alto.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Doubt-Driven Review
|
|
15
|
+
|
|
16
|
+
## Cuándo cargar
|
|
17
|
+
|
|
18
|
+
- Antes de aprobar un ADR que toma una decisión irreversible.
|
|
19
|
+
- Antes de comprometer un contrato público de API o un schema de BD que afecta integraciones externas.
|
|
20
|
+
- Antes de elegir un framework, librería o patrón con costo de migración alto.
|
|
21
|
+
- Antes de fusionar un PR cuyo blast radius incluye módulos críticos.
|
|
22
|
+
- Cuando el equipo o el agente exhibe **alta confianza con baja evidencia** sobre una decisión técnica.
|
|
23
|
+
|
|
24
|
+
## Cuándo NO cargar
|
|
25
|
+
|
|
26
|
+
- Para revisar código por calidad — eso es `revisor-codigo-swl` con `Skill("checklist-calidad")`.
|
|
27
|
+
- Para revisar seguridad — eso es `revisor-seguridad-swl` con `Skill("checklist-seguridad")`.
|
|
28
|
+
- Para validar memoria contra prompt injection o privacidad — eso es `red-team-swl`.
|
|
29
|
+
- Para discutir alcances con el usuario — eso es `Skill("brainstorming")` o `swl:discutir-fase`.
|
|
30
|
+
- Para fixes triviales o decisiones reversibles en menos de 10 minutos.
|
|
31
|
+
|
|
32
|
+
## Principio
|
|
33
|
+
|
|
34
|
+
> Una decisión cuya alternativa no se ha articulado, no es una decisión:
|
|
35
|
+
> es una preferencia. Toda decisión técnica con costo de cambio alto debe
|
|
36
|
+
> sobrevivir a un round adversarial antes de comprometerse.
|
|
37
|
+
|
|
38
|
+
El skill ejecuta un protocolo en 5 pasos. Cada paso produce evidencia en
|
|
39
|
+
texto. La salida del skill NO aprueba ni rechaza — produce un reporte que
|
|
40
|
+
el agente o usuario humano usa para decidir.
|
|
41
|
+
|
|
42
|
+
## Protocolo
|
|
43
|
+
|
|
44
|
+
### Paso 1 — Articular la decisión y su irreversibilidad
|
|
45
|
+
|
|
46
|
+
Pedir explícitamente:
|
|
47
|
+
|
|
48
|
+
- **Qué se decide**: 1-3 oraciones, sin jerga ambigua.
|
|
49
|
+
- **Qué se descarta**: la decisión opuesta concreta.
|
|
50
|
+
- **Costo de revertir**: estimación en turnos / archivos / horas si en 3 meses se concluye que fue incorrecta.
|
|
51
|
+
|
|
52
|
+
Si el costo de revertir es < 4 horas y < 5 archivos: la decisión NO es candidata
|
|
53
|
+
a doubt-driven review. Cerrar el skill y proceder.
|
|
54
|
+
|
|
55
|
+
### Paso 2 — Forzar 3 alternativas descartadas
|
|
56
|
+
|
|
57
|
+
El agente o usuario debe articular las 3 alternativas más fuertes que NO se eligieron, con:
|
|
58
|
+
|
|
59
|
+
- Por qué cada una es plausible (≥ 1 razón concreta).
|
|
60
|
+
- Qué tradeoff la hace inferior a la elegida (sin frase genérica como "menos flexible").
|
|
61
|
+
|
|
62
|
+
Si la persona/agente no puede articular 3 alternativas: **señal de decisión bajo-evidencia**.
|
|
63
|
+
Reportar: "Decisión propuesta sin alternativas articuladas. Riesgo de selección por inercia."
|
|
64
|
+
|
|
65
|
+
### Paso 3 — Buscar contraevidencia activamente
|
|
66
|
+
|
|
67
|
+
Para la decisión propuesta:
|
|
68
|
+
|
|
69
|
+
- Listar 3 escenarios donde la decisión **fallaría** o produciría costos no anticipados.
|
|
70
|
+
- Listar 1 caso histórico (dentro o fuera del proyecto) donde una decisión similar resultó incorrecta.
|
|
71
|
+
- Identificar 1 supuesto de la decisión que NO está validado con evidencia (corre el riesgo de ser falso).
|
|
72
|
+
|
|
73
|
+
Si los 3 escenarios de falla son improbables Y el supuesto está validado: la decisión gana robustez.
|
|
74
|
+
Si ≥ 1 escenario tiene probabilidad ≥ 30%: documentar el riesgo en el reporte.
|
|
75
|
+
|
|
76
|
+
### Paso 4 — Detectar confianza injustificada
|
|
77
|
+
|
|
78
|
+
Patrones a marcar como red flags:
|
|
79
|
+
|
|
80
|
+
- "Es la mejor práctica" sin citar fuente o caso.
|
|
81
|
+
- "Siempre se hace así" sin verificar el contexto del proyecto actual.
|
|
82
|
+
- "Es lo que recomienda <autoridad>" sin verificar que la recomendación aplica al stack/escala/dominio.
|
|
83
|
+
- "No hay otra opción razonable" cuando el paso 2 ya forzó 3 alternativas.
|
|
84
|
+
- Cita de un blog post o tweet como evidencia única.
|
|
85
|
+
- Argumentos de tipo "todo el mundo lo usa" sin métrica concreta.
|
|
86
|
+
|
|
87
|
+
Por cada red flag detectado: 1 línea en el reporte con cita textual del argumento.
|
|
88
|
+
|
|
89
|
+
### Paso 5 — Trigger de reversión
|
|
90
|
+
|
|
91
|
+
Toda decisión que pase doubt-driven review debe incluir:
|
|
92
|
+
|
|
93
|
+
- **Trigger de reversión**: condición observable que, de cumplirse, obliga a reabrir la decisión.
|
|
94
|
+
Ejemplos válidos: "p95 de latencia > 800ms en producción durante 7 días",
|
|
95
|
+
"≥3 reportes de bugs relacionados con la limitación X en 30 días", "vendor X
|
|
96
|
+
publica deprecation notice", "costo mensual de infra excede $Y".
|
|
97
|
+
- **Fecha de reevaluación automática**: 6-12 meses desde la fecha de decisión.
|
|
98
|
+
|
|
99
|
+
Si la decisión NO puede tener trigger observable: es una decisión **basada en preferencia**,
|
|
100
|
+
no en hipótesis falsable. Documentarla como tal en el reporte.
|
|
101
|
+
|
|
102
|
+
## Formato del reporte
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
## Doubt-Driven Review — [decisión] — [fecha]
|
|
106
|
+
|
|
107
|
+
### Decisión articulada
|
|
108
|
+
- Qué se decide: [...]
|
|
109
|
+
- Qué se descarta: [...]
|
|
110
|
+
- Costo de revertir: [estimación]
|
|
111
|
+
|
|
112
|
+
### Alternativas articuladas
|
|
113
|
+
1. [alt-1]: plausible porque [...]; descartada porque [tradeoff concreto]
|
|
114
|
+
2. [alt-2]: ...
|
|
115
|
+
3. [alt-3]: ...
|
|
116
|
+
|
|
117
|
+
### Contraevidencia
|
|
118
|
+
- Escenario de falla 1 [prob: alta/media/baja]: [...]
|
|
119
|
+
- Escenario de falla 2 [prob: ...]: [...]
|
|
120
|
+
- Escenario de falla 3 [prob: ...]: [...]
|
|
121
|
+
- Caso histórico relevante: [...]
|
|
122
|
+
- Supuesto sin validar: [...]
|
|
123
|
+
|
|
124
|
+
### Red flags detectados
|
|
125
|
+
- [línea N: cita textual del argumento débil]
|
|
126
|
+
- [...]
|
|
127
|
+
- (o "Ninguno")
|
|
128
|
+
|
|
129
|
+
### Trigger de reversión
|
|
130
|
+
- Condición: [observable falsable]
|
|
131
|
+
- Fecha de reevaluación: [YYYY-MM-DD]
|
|
132
|
+
|
|
133
|
+
### Veredicto
|
|
134
|
+
- ROBUSTA — pasó los 5 pasos sin red flags y con trigger claro
|
|
135
|
+
- ACEPTABLE — pasó con 1-2 red flags documentados y trigger claro
|
|
136
|
+
- BAJO-EVIDENCIA — falla en pasos 2, 4 o 5; reabrir antes de comprometer
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Anti-patrones del skill
|
|
140
|
+
|
|
141
|
+
- **Auto-revisión**: el agente que tomó la decisión ejecuta el skill sobre sí mismo.
|
|
142
|
+
Pierde el efecto adversarial. Cargar el skill desde un agente distinto al
|
|
143
|
+
decisor (ej: `arquitecto-swl` decidió → `revisor-codigo-swl` ejecuta el skill
|
|
144
|
+
con foco en la decisión).
|
|
145
|
+
- **Trigger inverificable**: "cuando el sistema deje de escalar" no es trigger.
|
|
146
|
+
Debe ser una métrica observable con valor numérico o evento concreto.
|
|
147
|
+
- **Alternativas de paja**: las 3 alternativas no pueden ser opciones triviales
|
|
148
|
+
inferiores a propósito. Si las 3 son obviamente peores, el skill perdió su valor
|
|
149
|
+
— pedir 3 alternativas reales o aceptar que la decisión es bajo-evidencia.
|
|
150
|
+
- **Convertirlo en proceso burocrático**: aplicar doubt-driven a decisiones
|
|
151
|
+
reversibles es overhead. El paso 1 filtra esto explícitamente.
|
|
152
|
+
|
|
153
|
+
## Relación con otras capacidades del sistema
|
|
154
|
+
|
|
155
|
+
- `red-team-swl`: cubre adversarial review de **memoria del sistema** (perfil-usuario,
|
|
156
|
+
instintos, APRENDIZAJES). Doubt-driven cubre **decisiones técnicas**. NO duplicación.
|
|
157
|
+
- `arquitecto-swl`: produce ADRs. Doubt-driven se carga ANTES de aceptar el ADR.
|
|
158
|
+
- `revisor-codigo-swl`: revisa calidad de código ya escrito. Doubt-driven revisa
|
|
159
|
+
decisiones ANTES de escribir código.
|
|
160
|
+
- `Skill("verificar-trabajo")`: verificación goal-backward del resultado.
|
|
161
|
+
Doubt-driven es goal-backward del **diseño**, no del resultado.
|
|
162
|
+
|
|
163
|
+
## Origen
|
|
164
|
+
|
|
165
|
+
Patrón observado en `temp/agent-skills-main/skills/doubt-driven-development`
|
|
166
|
+
(2026-05-09). Adaptado al sistema swl-ses: en español, con trigger de
|
|
167
|
+
reversión obligatorio (alineado con `reglas/arquitectura.md` § ADRs en estado
|
|
168
|
+
Propuesto), y con relación explícita a `red-team-swl` para evitar duplicar
|
|
169
|
+
funciones que ya existen en el sistema.
|
|
170
|
+
|
|
171
|
+
Para ejemplos concretos de aplicación, ver [`recursos/EXAMPLES.md`](recursos/EXAMPLES.md).
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# EXAMPLES — Doubt-Driven Review
|
|
2
|
+
|
|
3
|
+
Tres aplicaciones concretas del skill, dos exitosas y una que detectó decisión bajo-evidencia.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Ejemplo 1 — Decisión que pasó como ROBUSTA
|
|
8
|
+
|
|
9
|
+
**Contexto**: equipo evalúa migrar de PostgreSQL local a un servicio managed (RDS).
|
|
10
|
+
|
|
11
|
+
### ❌ Sin doubt-driven (hipotético)
|
|
12
|
+
|
|
13
|
+
> "RDS es la mejor opción porque AWS lo recomienda y todos lo usan."
|
|
14
|
+
|
|
15
|
+
Costo de revertir no estimado, alternativas no articuladas, cero contraevidencia.
|
|
16
|
+
Decisión se toma. 8 meses después se descubre que la latencia p95 desde el
|
|
17
|
+
único cluster on-premise legacy del cliente sube 40ms y rompe SLA.
|
|
18
|
+
|
|
19
|
+
### ✓ Con doubt-driven
|
|
20
|
+
|
|
21
|
+
**Paso 1 — Articular**:
|
|
22
|
+
- Qué se decide: migrar a RDS Aurora PostgreSQL en us-east-1.
|
|
23
|
+
- Qué se descarta: mantener PostgreSQL self-hosted en EC2.
|
|
24
|
+
- Costo de revertir: ~3 semanas (migrar datos de vuelta, reconfigurar backups, ajustar IAM).
|
|
25
|
+
|
|
26
|
+
**Paso 2 — 3 alternativas**:
|
|
27
|
+
1. **Self-hosted PostgreSQL en EC2**: control total de versión y extensiones; descartada
|
|
28
|
+
por costo operativo (on-call para BD).
|
|
29
|
+
2. **Cloud SQL en GCP**: feature parity con RDS; descartada porque el resto del
|
|
30
|
+
stack ya está en AWS y multi-cloud agrega complejidad de red.
|
|
31
|
+
3. **Supabase managed**: incluye Auth + Realtime gratis; descartada porque el
|
|
32
|
+
equipo no necesita Realtime y el lock-in en API propietario es alto.
|
|
33
|
+
|
|
34
|
+
**Paso 3 — Contraevidencia**:
|
|
35
|
+
- Falla 1 (prob: media): vendor lock-in si Aurora introduce features no portables → mitigación: limitar uso a SQL estándar.
|
|
36
|
+
- Falla 2 (prob: baja): outage regional de us-east-1 → mitigación: read replica en us-west-2.
|
|
37
|
+
- Falla 3 (prob: baja): costo de I/O excede presupuesto → mitigación: alertas de costo en CloudWatch.
|
|
38
|
+
- Caso histórico: cliente X migró a RDS, descubrió que `pg_cron` no estaba disponible y refactoró 3 jobs.
|
|
39
|
+
- Supuesto sin validar: "la latencia desde nuestros clientes on-premise será aceptable" — necesita pruebas reales.
|
|
40
|
+
|
|
41
|
+
**Paso 4 — Red flags**: ninguno. Cada argumento está respaldado con caso o métrica.
|
|
42
|
+
|
|
43
|
+
**Paso 5 — Trigger**:
|
|
44
|
+
- Condición: "p95 de latencia desde el cliente legacy excede 200ms durante 7 días consecutivos" o "costo mensual supera $1500".
|
|
45
|
+
- Fecha de reevaluación: 2026-11-15 (6 meses).
|
|
46
|
+
|
|
47
|
+
**Veredicto**: ROBUSTA con 1 supuesto pendiente de validar (latencia on-premise → ejecutar prueba antes de migrar).
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Ejemplo 2 — Decisión BAJO-EVIDENCIA detectada
|
|
52
|
+
|
|
53
|
+
**Contexto**: agente propone "vamos a usar Kafka para todos los flujos asíncronos del sistema."
|
|
54
|
+
|
|
55
|
+
**Paso 1 — Articular**:
|
|
56
|
+
- Qué se decide: introducir Apache Kafka como broker único.
|
|
57
|
+
- Qué se descarta: Redis Streams + RabbitMQ ya existentes en el stack.
|
|
58
|
+
- Costo de revertir: alto — requiere rehacer 12 productores y 8 consumidores.
|
|
59
|
+
|
|
60
|
+
**Paso 2 — 3 alternativas**: el agente solo articula 1 (RabbitMQ) y dice "los demás
|
|
61
|
+
no son comparables." → **Red flag**: incapacidad de articular 3 alternativas reales.
|
|
62
|
+
|
|
63
|
+
**Paso 3 — Contraevidencia**: el agente dice "Kafka nunca falla en producción
|
|
64
|
+
porque tiene replication." → **Red flag**: argumento sin caso histórico.
|
|
65
|
+
|
|
66
|
+
**Paso 4 — Red flags**:
|
|
67
|
+
- "Es la mejor práctica para event-driven architectures" (sin fuente).
|
|
68
|
+
- "Todo el mundo lo usa a escala" (sin métrica del proyecto actual).
|
|
69
|
+
- "No hay otra opción razonable" (contradicho por el paso 2 incompleto).
|
|
70
|
+
|
|
71
|
+
**Paso 5 — Trigger**: "cuando lo necesitemos a más escala" — **Trigger inverificable**.
|
|
72
|
+
Reescribir como "throughput supera 10k msg/s sostenido durante 14 días."
|
|
73
|
+
|
|
74
|
+
**Veredicto**: BAJO-EVIDENCIA. Reabrir antes de comprometer.
|
|
75
|
+
|
|
76
|
+
Resultado real: la decisión se difiere; al revisar, se descubre que el throughput
|
|
77
|
+
actual del sistema es ~80 msg/s — Kafka habría sido sobre-ingeniería con costo
|
|
78
|
+
operativo de un cluster que el equipo no tiene capacidad de mantener.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Ejemplo 3 — Decisión ACEPTABLE con red flag documentado
|
|
83
|
+
|
|
84
|
+
**Contexto**: elegir framework frontend para un dashboard interno nuevo.
|
|
85
|
+
|
|
86
|
+
**Paso 1**:
|
|
87
|
+
- Qué se decide: Next.js App Router + Server Components.
|
|
88
|
+
- Qué se descarta: SPA con Vite + React Query.
|
|
89
|
+
- Costo de revertir: medio — 2 semanas de migración + reescritura de data fetching.
|
|
90
|
+
|
|
91
|
+
**Paso 2 — 3 alternativas**:
|
|
92
|
+
1. **SPA Vite + React Query**: más simple, menos magia; descartada porque queremos SSR para SEO interno y autenticación server-side.
|
|
93
|
+
2. **Remix**: nested routing nativo; descartada porque el equipo ya tiene experiencia con Next.js y la curva de Remix agrega 2 semanas.
|
|
94
|
+
3. **Astro con islands**: ideal si fuera contenido estático; descartada porque el dashboard es 80% interactivo.
|
|
95
|
+
|
|
96
|
+
**Paso 3 — Contraevidencia**:
|
|
97
|
+
- Falla 1 (prob: media): App Router introduce breaking changes y rompe el build (ya pasó con v13→v14).
|
|
98
|
+
- Falla 2 (prob: baja): RSC mental model genera bugs sutiles de hidratación.
|
|
99
|
+
- Falla 3 (prob: baja): vendor lock-in con Vercel — mitigación: deploy en Cloudflare Workers o self-hosted.
|
|
100
|
+
- Caso histórico: equipo Y adoptó App Router en beta y tardó 3 meses en estabilizar.
|
|
101
|
+
- Supuesto: "el equipo aprenderá RSC en 2 sprints" — moderadamente optimista.
|
|
102
|
+
|
|
103
|
+
**Paso 4 — Red flags**:
|
|
104
|
+
- "App Router es el futuro" (cita de blog post de Vercel — sesgo de fuente).
|
|
105
|
+
|
|
106
|
+
**Paso 5 — Trigger**:
|
|
107
|
+
- Condición: "≥3 bugs de hidratación reportados por usuarios en sprint 5" O "Vercel
|
|
108
|
+
anuncia deprecation de App Router".
|
|
109
|
+
- Fecha de reevaluación: 2026-08-15 (3 meses, periodo corto por uso de feature reciente).
|
|
110
|
+
|
|
111
|
+
**Veredicto**: ACEPTABLE — 1 red flag documentado (cita de Vercel como fuente única
|
|
112
|
+
para "futuro"). El equipo procede con awareness del sesgo.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Patrón de aplicación
|
|
117
|
+
|
|
118
|
+
| Ejemplo | Costo revertir | Alternativas articuladas | Red flags | Trigger | Veredicto |
|
|
119
|
+
|---|---|---|---|---|---|
|
|
120
|
+
| 1 — RDS | 3 semanas | 3 sólidas | 0 | Métrico claro | ROBUSTA |
|
|
121
|
+
| 2 — Kafka | 12+8 servicios | 1 (incompleto) | 3 | Inverificable | BAJO-EVIDENCIA |
|
|
122
|
+
| 3 — Next.js | 2 semanas | 3 sólidas | 1 (sesgo de fuente) | Métrico + evento externo | ACEPTABLE |
|
|
123
|
+
|
|
124
|
+
El skill es útil cuando:
|
|
125
|
+
- El costo de revertir es alto Y
|
|
126
|
+
- Hay tendencia a saltar al "qué" sin articular el "por qué no las otras".
|
|
127
|
+
|
|
128
|
+
Es overhead cuando:
|
|
129
|
+
- La decisión es reversible en horas O
|
|
130
|
+
- La decisión ya fue auditada por un proceso equivalente (ADR formal con contexto + alternativas + consecuencias).
|
|
@@ -5,12 +5,12 @@ description: >
|
|
|
5
5
|
testing con httpx. Incluye el anti-patrón crítico MissingGreenlet (lazy loading
|
|
6
6
|
en async). Cargar cuando se implementen endpoints FastAPI, schemas Pydantic v2,
|
|
7
7
|
queries SQLAlchemy async, WebSockets, SSE o tests de integración con httpx.
|
|
8
|
-
version: "1.
|
|
8
|
+
version: "1.2.0"
|
|
9
9
|
evolved: true
|
|
10
|
-
evolved-from: "1.1.
|
|
11
|
-
evolved-at: "2026-05-
|
|
10
|
+
evolved-from: "1.1.2"
|
|
11
|
+
evolved-at: "2026-05-10"
|
|
12
12
|
evolved-by: "aprender"
|
|
13
|
-
evolved-note: "
|
|
13
|
+
evolved-note: "2 reglas nuevas en sesión SIGM Opción B: response_model=dict obsoleto vs Envelope[T] tipado, RETURNING * sin soporte de JOINs (refactor a 2 queries con _SQL_X_ENRIQUECIDA)"
|
|
14
14
|
herramientasPermitidas: [Read]
|
|
15
15
|
exclusiones:
|
|
16
16
|
- "No cargar para proyectos Django o Flask — los patrones de ORM sync, Class-Based Views y middleware difieren fundamentalmente; cargar `django-experto` o el skill del framework correspondiente."
|
|
@@ -219,6 +219,8 @@ class Factura(Base):
|
|
|
219
219
|
- **`return result or {}` después de UPDATE/INSERT enmascara errores de BD silenciosamente**: patrón típico `result = await repo.actualizar_estatus(...); return result or {}` devuelve `{}` al cliente cuando el UPDATE no encontró la fila (race condition, RLS, FK violado). El cliente recibe HTTP 200 con body vacío en lugar del 404/500 esperado, los bugs quedan invisibles en monitoring. Causa: `or {}` trata `None` como "datos no disponibles" cuando en realidad significa "el UPDATE falló post-INSERT". Solución: explicit None check con raise — usar `if result is None: raise HTTPException(404, "Recurso no encontrado")` cuando el ID viene del cliente, o `raise HTTPException(500, "Error interno...")` cuando es un invariante post-INSERT (la fila acaba de crearse, debe existir).
|
|
220
220
|
- **`detail=str(exc)` en HTTPException — solo aceptable cuando la excepción es de DOMINIO con mensaje diseñado para usuario**: las excepciones de capa externa (MinIO/S3, BD driver, HTTP client de un PSP, parser PDF) tienen mensajes que pueden contener bucket/host/paths/credenciales parciales/stack traces. Pasar `str(exc)` directo al `detail` los expone al cliente. Causa: tratar todas las excepciones igual sin distinguir dominio (controlado) de capa externa (no controlado). Solución: dos patrones distintos. Para excepciones de dominio (`MIMENoPermitidoError("MIME 'X' no permitido")`, `EmailDuplicadoError`): `except DominioError as exc: raise HTTPException(422, detail=str(exc))` OK. Para capa externa (`ErrorEvidencia`, `boto3.ClientError`, `httpx.RequestError`): `except CapaExternaError as exc: logger.exception("contexto"); raise HTTPException(502, detail="Error genérico al cliente")`. La diferencia es que el mensaje de DominioError fue diseñado para el usuario; el de CapaExternaError no.
|
|
221
221
|
- **Vocabulario interno (nombres de funciones PL/pgSQL, schemas, tablas, "RLS", "transaccional") en `detail` al cliente fuga arquitectura**: detail genérico al cliente y detail con detalles de infraestructura para diagnóstico **no son lo mismo**. Patrones de fuga típicos: `detail=f"fn_evaluar_X no retornó resultado"` (revela nombre de función PL/pgSQL), `detail="Posible inconsistencia transaccional o RLS"` (revela motor + capa de seguridad), `detail=f"Recurso {id} no encontrado en activacion tras INSERT"` (revela UUID interno y secuencia de operaciones). Causa: el desarrollador escribe el mensaje pensando en debug, no en exposición. Solución: detail al cliente = mensaje genérico orientado al recurso ("Error interno al activar el recurso. Contacte al administrador."); detalles internos solo en `logger.error("contexto detallado %s %s", uuid, programa_id, ...)` con format strings estructurados (NO concatenación con `+`). Aplica a todos los HTTPException 500/503; el 404/422 puede ser más específico si el mensaje es del dominio.
|
|
222
|
+
- **`response_model=dict` produce `{[key:string]:unknown}` en `openapi-typescript` — tipos inútiles para frontend**: declarar endpoints con `response_model=dict` (placeholder) hace que el codegen `openapi-typescript` genere responses tipados como objeto vacío. El frontend lee campos via `.id`, `.monto` con type assertions implícitas → mismatches silenciosos en runtime cuando los nombres del backend cambian. Causa: FastAPI sin schema concreto no documenta el shape en `/openapi.json`. Fix: SIEMPRE declarar `response_model=EnvelopeResponse[Schema]`, `EnvelopePaginatedResponse[Schema]`, `EnvelopeOffsetResponse[Schema]` o `EnvelopeResponse[MensajeResponse]` con un schema Pydantic concreto. Genéricos `EnvelopeResponse[T] / EnvelopePaginatedResponse[T] / EnvelopeOffsetResponse[T] / MensajeResponse` deben vivir en `app/common/response.py` (Pydantic v2 + Generic[T]). Excepción única documentable: `StreamingResponse` (PDF/CSV) puede usar `response_model=dict` con comentario explicativo. Caso real: 155 endpoints SIGM refactorizados (2026-05-10) tras descubrir que el codegen producía tipos vacíos por `response_model=dict` heredado.
|
|
223
|
+
- **Pydantic + PostgreSQL `RETURNING *` no soporta JOINs en INSERT/UPDATE — refactor a 2 queries**: para mutaciones que devuelven un schema enriquecido con JOINs (ej: `cajero_nombre` desde `usuario.usuario`, `clave_catastral` desde `cuenta_predial`), `INSERT/UPDATE ... RETURNING *` no permite agregar JOINs. Causa: PostgreSQL `RETURNING` solo accede a las columnas de la tabla afectada. Fix: refactorizar a 2 queries: (1) `INSERT/UPDATE ... RETURNING id`; (2) `SELECT ... FROM tabla LEFT JOIN ... WHERE id = $1`. Costo: 1 round-trip extra (sub-1ms en LAN). Beneficio: el método siempre devuelve el shape enriquecido, mappers consistentes entre `crear`, `obtener` y `listar`. Patrón DRY: extraer la query SELECT a constante de clase (`_SQL_X_ENRIQUECIDA`) para reusar entre métodos. Caso: `crear_solicitud_descuento`, `autorizar_descuento`, `obtener_solicitud_descuento` y `listar_solicitudes_pendientes` comparten `_SQL_SOLICITUD_ENRIQUECIDA` con LEFT JOIN a `cuenta_predial` + `usuario` (cajero/supervisor).
|
|
222
224
|
|
|
223
225
|
## Referencias especializadas
|
|
224
226
|
|
|
@@ -263,6 +263,10 @@ el contenido específico, sin inflar el contexto con cada invocación:
|
|
|
263
263
|
- **Mapeo a frameworks de seguridad (NIST CSF, NIST AI RMF, MITRE ATT&CK/ATLAS/
|
|
264
264
|
D3FEND)** + **migración a nombres de campo en español (REC-S15)**. Ver
|
|
265
265
|
[recursos/frameworks-seguridad.md](recursos/frameworks-seguridad.md).
|
|
266
|
+
- **Convención `EXAMPLES.md` como recurso opcional** para skills críticos cuyo
|
|
267
|
+
valor depende de mostrar diff MAL→BIEN lado a lado (decisiones, arquitectura,
|
|
268
|
+
anti-patrones sutiles). NO retroactiva — las 63 carpetas `recursos/` existentes
|
|
269
|
+
no se renombran. Ver [recursos/convencion-examples.md](recursos/convencion-examples.md).
|
|
266
270
|
|
|
267
271
|
Cada recurso tiene ToC al inicio y es autocontenido — leer solo el relevante
|
|
268
272
|
al caso en cuestión en lugar de los tres.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Convención `EXAMPLES.md` — recurso opcional para skills críticos
|
|
2
|
+
|
|
3
|
+
Convención **opcional** para skills cuyo valor depende de ejemplos concretos
|
|
4
|
+
con diff MAL → BIEN. Esta convención NO es retroactiva: las 63 carpetas
|
|
5
|
+
`recursos/` existentes en `habilidades/` no se renombran. Solo aplica a:
|
|
6
|
+
|
|
7
|
+
- Skills nuevos cuyo dominio se entiende mejor con ejemplos lado a lado.
|
|
8
|
+
- Skills existentes que reciban actualización mayor y agreguen ejemplos.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Cuándo aplicarla
|
|
13
|
+
|
|
14
|
+
Aplicar cuando:
|
|
15
|
+
|
|
16
|
+
- El skill prescribe **decisiones técnicas** o **patrones** cuyo error es
|
|
17
|
+
sutil sin un caso concreto (ej: anti-patrones de async, gotchas de framework,
|
|
18
|
+
decisiones arquitecturales).
|
|
19
|
+
- El skill se beneficia de mostrar 2-4 ejemplos lado a lado: el caso aplicado
|
|
20
|
+
correctamente vs el caso degenerado.
|
|
21
|
+
- Los ejemplos suman > 100 líneas y meterlos en SKILL.md lo pasaría del
|
|
22
|
+
límite de 300 líneas.
|
|
23
|
+
|
|
24
|
+
NO aplicar cuando:
|
|
25
|
+
|
|
26
|
+
- El skill ya tiene `recursos/` con archivos temáticos específicos
|
|
27
|
+
(ej: `meta-skills-estandar/recursos/anti-patrones-y-leyes.md`). El nombre
|
|
28
|
+
temático es más informativo que `EXAMPLES.md`.
|
|
29
|
+
- El skill es un how-to procedural sin antipatrones contrastables.
|
|
30
|
+
- El skill tiene < 80 líneas en SKILL.md y los ejemplos caben en línea.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Estructura recomendada
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
habilidades/<skill-name>/
|
|
38
|
+
├── SKILL.md
|
|
39
|
+
└── recursos/
|
|
40
|
+
└── EXAMPLES.md
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`SKILL.md` enlaza al final con:
|
|
44
|
+
|
|
45
|
+
```markdown
|
|
46
|
+
Para ejemplos concretos de aplicación, ver [`recursos/EXAMPLES.md`](recursos/EXAMPLES.md).
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`EXAMPLES.md` contiene 2-4 ejemplos siguiendo el patrón:
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
## Ejemplo N — [título corto del escenario]
|
|
53
|
+
|
|
54
|
+
**Contexto**: [1-2 líneas]
|
|
55
|
+
|
|
56
|
+
### ❌ Sin <skill-name> (hipotético)
|
|
57
|
+
[código o descripción del enfoque incorrecto + consecuencia observable]
|
|
58
|
+
|
|
59
|
+
### ✓ Con <skill-name>
|
|
60
|
+
[código o descripción del enfoque correcto + por qué evita la consecuencia]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Cierre de `EXAMPLES.md`: tabla resumen de los ejemplos con la dimensión
|
|
64
|
+
clave que cambió (costo, tiempo, severidad de bug, etc.).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Por qué OPCIONAL y no obligatoria
|
|
69
|
+
|
|
70
|
+
- 63 carpetas `recursos/` ya existen con nomenclatura temática
|
|
71
|
+
(`anti-patrones-y-leyes.md`, `frameworks-seguridad.md`,
|
|
72
|
+
`idiomas-framework.md`). Forzar `EXAMPLES.md` rompería información
|
|
73
|
+
semántica del nombre.
|
|
74
|
+
- La regla core `reglas/skills-estandar.md` ya prescribe que `SKILL.md`
|
|
75
|
+
no exceda 300 líneas y que los `recursos/` se nombren en kebab-case
|
|
76
|
+
con sufijo `.md` — esa regla cubre el 95% de los casos.
|
|
77
|
+
- Esta convención **complementa** la regla core para skills donde un nombre
|
|
78
|
+
semántico genérico (`EXAMPLES.md`) comunica mejor el contenido que un
|
|
79
|
+
nombre específico (ej: `casos-decisiones-arquitecturales.md` es más largo
|
|
80
|
+
y menos buscable).
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Origen
|
|
85
|
+
|
|
86
|
+
Patrón observado en repos externos analizados el 2026-05-09
|
|
87
|
+
(`temp/agent-skills-main`, `temp/andrej-karpathy-skills-main`):
|
|
88
|
+
varios skills críticos exponen un `EXAMPLES.md` con diffs MAL/BIEN
|
|
89
|
+
auto-cargable como referencia rápida del agente.
|
|
90
|
+
|
|
91
|
+
Adoptado en `habilidades/doubt-driven-review/recursos/EXAMPLES.md` como
|
|
92
|
+
primera aplicación. Casos futuros: skills de decisiones arquitecturales,
|
|
93
|
+
seguridad, debugging avanzado.
|
|
@@ -4,7 +4,12 @@ description: >
|
|
|
4
4
|
Next.js App Router: Server Components, Client Components, Server Actions,
|
|
5
5
|
streaming con Suspense, ISR, route handlers y middleware. Cargar cuando se
|
|
6
6
|
implementen páginas Next.js, data fetching, mutaciones o rutas de API.
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.1.0"
|
|
8
|
+
evolved: true
|
|
9
|
+
evolved-from: "1.0.0"
|
|
10
|
+
evolved-at: "2026-05-10"
|
|
11
|
+
evolved-by: "aprender"
|
|
12
|
+
evolved-note: "3 gotchas operativos confirmados en sesión SIGM Opción B: useSearchParams Suspense bailout, cache .next stale, brace-expansion override CVE-2025-5889"
|
|
8
13
|
herramientasPermitidas: [Read]
|
|
9
14
|
exclusiones:
|
|
10
15
|
- "No cargar para proyectos Next.js con Pages Router (pages/index.tsx) — el modelo de getServerSideProps y getStaticProps es diferente al App Router; este skill solo cubre App Router."
|
|
@@ -316,6 +321,12 @@ export default function Reloj() {
|
|
|
316
321
|
|
|
317
322
|
**Variables de entorno sin `NEXT_PUBLIC_` no disponibles en el cliente en runtime**: `process.env.MI_VAR` en un Client Component retorna `undefined` en el browser aunque esté definida en `.env.local`. Causa: Next.js solo serializa al bundle del cliente las variables con prefijo `NEXT_PUBLIC_`. Fix: renombrar a `NEXT_PUBLIC_MI_VAR` si debe estar en el cliente, o leerla en un Server Component/Action y pasarla como prop.
|
|
318
323
|
|
|
324
|
+
**`useSearchParams()` requiere `<Suspense>` boundary durante prerender estático**: build de producción falla con `⨯ useSearchParams() should be wrapped in a suspense boundary at page "/X". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout`. Causa: el prerender estático no puede ejecutar el hook → CSR bailout → build aborta. Fix: en `page.tsx` envolver el componente que usa `useSearchParams()` en `<Suspense fallback={null}>` (o un esqueleto). El shell se prerenderiza vacío; el componente hidrata en cliente con los search params disponibles. Mismo patrón aplica a `useRouter()` y `usePathname()` cuando se usan en componentes prerenderizados. Caso real: SIGM agregó `useSearchParams()` a `useLoginForm.ts` para honrar `?siguiente=`; tsc + vitest pasaban pero `npm run build` falló hasta envolver `<LoginForm />` en `<Suspense>`.
|
|
325
|
+
|
|
326
|
+
**Cache `.next/` puede ocultar errores TypeScript reales tras refactor de tipos masivo**: `npx tsc --noEmit` local pasa pero CI falla con errores TS2724/TS2305 sobre tipos eliminados. Causa: `.next/types/*.d.ts` y `tsconfig.tsbuildinfo` cachean los types del estado anterior; CI clona limpio y ve los errores reales. Fix: cuando se modifican imports/exports masivos en `lib/*/tipos.ts` (refactor de tipos, eliminación de interfaces, codegen openapi-typescript), borrar cache antes de validar local: `cd frontend && rm -rf .next && npx tsc --noEmit`. Si CI falla con TSC y local pasa: 90% de las veces es cache stale.
|
|
327
|
+
|
|
328
|
+
**Override de `brace-expansion@^5` rompe minimatch (eslint depende)**: `npm install` aplica el override pero `eslint . --ext .ts,.tsx` falla con `TypeError: expand is not a function` en `@eslint/config-array → minimatch → brace-expansion`. Causa: brace-expansion v3+ cambió la API (de export default `expand` function a export con shape distinto); minimatch espera la API v2. Fix: para CVE-2025-5889 (ReDoS), usar `"brace-expansion": "^2.0.2"` (parcheado desde 2.0.2 — NO requiere v5). Validación pre-override: `npm ls brace-expansion` para revisar consumidores conocidos antes de bumpear major. Mismo patrón aplicable a otros overrides de transitives — validar cadena de consumers antes de bumpear major.
|
|
329
|
+
|
|
319
330
|
## Checklist de verificación
|
|
320
331
|
|
|
321
332
|
- [ ] "use client" solo en componentes con hooks, eventos o browser APIs
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: react-experto
|
|
3
3
|
description: React + Next.js mejores prácticas modernas. Cubre Server Components vs Client Components, data fetching patterns (RSC, React Query, SWR, Server Actions), state management (useState, Zustand, Jotai), performance (memo, lazy, Suspense, streaming) y Next.js App Router patterns.
|
|
4
|
-
version: "1.
|
|
4
|
+
version: "1.1.0"
|
|
5
|
+
evolved: true
|
|
6
|
+
evolved-from: "1.0.0"
|
|
7
|
+
evolved-at: "2026-05-10"
|
|
8
|
+
evolved-by: "aprender"
|
|
9
|
+
evolved-note: "Patrón useSyncExternalStore para hidratación cliente-only confirmado en sesión SIGM (evita la regla nueva react-hooks/set-state-in-effect)"
|
|
5
10
|
herramientasPermitidas: [Read]
|
|
6
11
|
exclusiones:
|
|
7
12
|
- "No cargar para optimización de rendimiento React (memo, useMemo, useCallback, virtualización, code splitting) — para rendimiento cargar `react-optimizacion`."
|
|
@@ -207,3 +212,13 @@ Para ejemplos completos de React Query (mutations + invalidacion), Server Action
|
|
|
207
212
|
**Server Action que actualiza datos sin `revalidatePath` o `revalidateTag` muestra datos obsoletos**: después de un Server Action exitoso (crear/actualizar/eliminar), el cliente sigue viendo el caché anterior del RSC. Causa: Next.js cachea agresivamente los datos del servidor; los Server Actions no invalidan el caché automáticamente. Fix: llamar `revalidatePath('/ruta/afectada')` o `revalidateTag('tag-del-dato')` al final del Server Action antes de `redirect()` o `return`.
|
|
208
213
|
|
|
209
214
|
**`useState` con objeto como valor inicial no se actualiza con shallow comparison en re-renders del padre**: `useState({ nombre: '', email: '' })` inicializa el estado una sola vez — si el componente padre pasa nuevos valores como prop para reinicializar el formulario, el estado no se actualiza. Causa: `useState` solo usa el valor inicial en el primer render. Fix: usar `key` en el componente para forzar remonte cuando cambien los datos base, o usar `useEffect` con las props como dependencias para sincronizar explícitamente.
|
|
215
|
+
|
|
216
|
+
**Patrón `useState + useEffect(() => setState(true), [])` para detectar cliente dispara la regla `react-hooks/set-state-in-effect` en eslint-plugin-react-hooks v7+**: build CI falla con `Calling setState synchronously within an effect body causes cascading renders`. Causa: la regla nueva (Next.js 16+) detecta el patrón clásico de "mounted flag" para hidratación segura como anti-patrón de performance. Fix idiomático React 19: usar `useSyncExternalStore`:
|
|
217
|
+
```tsx
|
|
218
|
+
const mounted = useSyncExternalStore(
|
|
219
|
+
() => () => {}, // subscribe — no-op (la 'store' no cambia tras hidratación)
|
|
220
|
+
() => true, // getSnapshot — cliente siempre montado
|
|
221
|
+
() => false, // getServerSnapshot — SSR siempre 'no montado'
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
Equivalente funcional al patrón mounted flag, sin disparar la regla. Type-safe, lint-compliant. Usar en Header/Sidebar/cualquier componente que lea localStorage o `window` y necesite render distinto en SSR vs cliente.
|
package/manifiestos/modulos.json
CHANGED
|
@@ -732,7 +732,8 @@
|
|
|
732
732
|
"habilidades/prevencion-racionalizacion",
|
|
733
733
|
"habilidades/prevencion-sobreingenieria",
|
|
734
734
|
"habilidades/privacy-memoria",
|
|
735
|
-
"habilidades/verificacion-evidencia"
|
|
735
|
+
"habilidades/verificacion-evidencia",
|
|
736
|
+
"habilidades/doubt-driven-review"
|
|
736
737
|
],
|
|
737
738
|
"targets": [
|
|
738
739
|
"claude",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saulwade/swl-ses",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot con 59 agentes,
|
|
3
|
+
"version": "1.2.2",
|
|
4
|
+
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot con 59 agentes, 154 habilidades, 42 comandos, 64 reglas y 40 hooks. Soporta 11 lenguajes y 5 runtimes: Claude Code, Copilot, OpenCode, Codex y Gemini CLI. 100% en espanol (Mexico). Incluye gateway bidireccional con relay Telegram a Claude Code.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"swl-ses": "bin/swl-ses.js",
|
|
7
7
|
"swl-telegram-bot": "bin/swl-telegram-bot.js",
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"publish:npmjs": "node scripts/publicar.js --solo-npmjs",
|
|
43
43
|
"publish:dry": "node scripts/publicar.js --dry-run",
|
|
44
44
|
"generate:docs": "node scripts/generar-inventario.js",
|
|
45
|
+
"gen-checklists": "node scripts/generar-checklists-consolidados.js",
|
|
46
|
+
"gen-checklists:check": "node scripts/generar-checklists-consolidados.js --check",
|
|
45
47
|
"field-report": "node scripts/field-report.js",
|
|
46
48
|
"configure:branch-protection": "node scripts/configurar-branch-protection.js"
|
|
47
49
|
},
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swl-ses",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot. 59 agentes,
|
|
3
|
+
"version": "1.2.2",
|
|
4
|
+
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot. 59 agentes, 154 habilidades, 42 comandos, 64 reglas y 40 hooks. 62 librerias. 11 lenguajes. Soporta Claude Code, Copilot, OpenCode, Codex y Gemini CLI.",
|
|
5
5
|
"author": "Saul Wade Leon",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "https://github.com/saul-wade/swl-ses",
|
|
@@ -379,6 +379,9 @@ El contenido de referencia detallada vive en el skill `meta-skills-estandar`
|
|
|
379
379
|
- **Mapeo a frameworks de seguridad** (NIST CSF, NIST AI RMF, MITRE ATT&CK,
|
|
380
380
|
ATLAS, D3FEND) — solo aplica a skills del dominio seguridad.
|
|
381
381
|
- **Migración a nombres de campo en español (REC-S15)**.
|
|
382
|
+
- **Convención `EXAMPLES.md`** como recurso opcional para skills críticos
|
|
383
|
+
cuyo valor depende de diff MAL→BIEN lado a lado. NO retroactiva — solo
|
|
384
|
+
guía para skills nuevos.
|
|
382
385
|
|
|
383
386
|
Cargar este skill extendido cuando se esté escribiendo o auditando un SKILL.md
|
|
384
387
|
que requiera patrones avanzados. Para skills simples o consultas básicas, la
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/generar-checklists-consolidados.js
|
|
4
|
+
*
|
|
5
|
+
* Genera vistas consolidadas tipo checklist-imprimible desde las secciones
|
|
6
|
+
* "## Checklist..." de las reglas en `reglas/`. NO duplica las reglas: solo
|
|
7
|
+
* ofrece una presentación checklist-only para auditorías rápidas.
|
|
8
|
+
*
|
|
9
|
+
* Output: docs/checklists-consolidados/
|
|
10
|
+
* - README.md (índice con enlaces)
|
|
11
|
+
* - <regla-name>.md (uno por regla con checklists)
|
|
12
|
+
* - INDICE-MAESTRO.md (todos los items en un solo archivo, agrupados por regla)
|
|
13
|
+
*
|
|
14
|
+
* Cada archivo generado lleva header explícito:
|
|
15
|
+
* "GENERADO desde reglas/X.md — fuente única, NO editar manualmente"
|
|
16
|
+
*
|
|
17
|
+
* Uso:
|
|
18
|
+
* node scripts/generar-checklists-consolidados.js # genera y reporta
|
|
19
|
+
* node scripts/generar-checklists-consolidados.js --check # falla si hay drift
|
|
20
|
+
*
|
|
21
|
+
* Origen: opción B del análisis temp/agent-skills-main + temp/karpathy
|
|
22
|
+
* (2026-05-09). Adapta el patrón "references/checklists" al sistema swl-ses
|
|
23
|
+
* sin duplicar el contenido de las reglas existentes.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
const REGLAS_DIR = path.resolve(__dirname, '..', 'reglas');
|
|
30
|
+
const OUTPUT_DIR = path.resolve(__dirname, '..', 'docs', 'checklists-consolidados');
|
|
31
|
+
const HEADER_GEN = (origen) =>
|
|
32
|
+
`<!--\n` +
|
|
33
|
+
` GENERADO desde reglas/${origen} por scripts/generar-checklists-consolidados.js\n` +
|
|
34
|
+
` Fuente única: NO editar manualmente. Para cambiar el contenido, edita la\n` +
|
|
35
|
+
` regla origen y regenera con: npm run gen-checklists\n` +
|
|
36
|
+
`-->\n\n`;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Recorre recursivamente reglas/ buscando archivos .md.
|
|
40
|
+
*/
|
|
41
|
+
function listarReglas(dir, base = '') {
|
|
42
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
43
|
+
const archivos = [];
|
|
44
|
+
for (const item of items) {
|
|
45
|
+
const ruta = path.join(dir, item.name);
|
|
46
|
+
const rel = base ? `${base}/${item.name}` : item.name;
|
|
47
|
+
if (item.isDirectory() && !item.name.startsWith('_')) {
|
|
48
|
+
archivos.push(...listarReglas(ruta, rel));
|
|
49
|
+
} else if (item.isFile() && item.name.endsWith('.md')) {
|
|
50
|
+
archivos.push({ rutaAbsoluta: ruta, rutaRelativa: rel });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return archivos;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extrae todas las secciones "## Checklist..." de un archivo markdown.
|
|
58
|
+
* Devuelve [{ titulo, items }] donde items es array de strings.
|
|
59
|
+
*/
|
|
60
|
+
function extraerChecklists(contenido) {
|
|
61
|
+
// Normalizar CRLF → LF antes de split (compat Windows)
|
|
62
|
+
const lineas = contenido.replace(/\r\n/g, '\n').split('\n');
|
|
63
|
+
const checklists = [];
|
|
64
|
+
let dentroDeChecklist = false;
|
|
65
|
+
let checklistActual = null;
|
|
66
|
+
|
|
67
|
+
for (const linea of lineas) {
|
|
68
|
+
const matchInicio = linea.match(/^##\s+(Checklist.*)$/i);
|
|
69
|
+
if (matchInicio) {
|
|
70
|
+
if (checklistActual && checklistActual.items.length > 0) {
|
|
71
|
+
checklists.push(checklistActual);
|
|
72
|
+
}
|
|
73
|
+
checklistActual = { titulo: matchInicio[1].trim(), items: [] };
|
|
74
|
+
dentroDeChecklist = true;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (dentroDeChecklist) {
|
|
79
|
+
// Otra sección de mismo o mayor nivel cierra la checklist
|
|
80
|
+
if (/^#{1,2}\s+/.test(linea) && !linea.match(/^##\s+Checklist/i)) {
|
|
81
|
+
if (checklistActual && checklistActual.items.length > 0) {
|
|
82
|
+
checklists.push(checklistActual);
|
|
83
|
+
}
|
|
84
|
+
checklistActual = null;
|
|
85
|
+
dentroDeChecklist = false;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const matchItem = linea.match(/^-\s+\[\s\]\s+(.+)$/);
|
|
89
|
+
if (matchItem) {
|
|
90
|
+
checklistActual.items.push(matchItem[1].trim());
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (checklistActual && checklistActual.items.length > 0) {
|
|
96
|
+
checklists.push(checklistActual);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return checklists;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Escribe un archivo .md por regla con sus checklists.
|
|
104
|
+
*/
|
|
105
|
+
function escribirArchivoRegla(reglaInfo, checklists) {
|
|
106
|
+
const nombre = reglaInfo.rutaRelativa.replace(/\.md$/, '').replace(/\//g, '__');
|
|
107
|
+
const archivoSalida = path.join(OUTPUT_DIR, `${nombre}.md`);
|
|
108
|
+
const tituloRegla = reglaInfo.rutaRelativa.replace(/\.md$/, '');
|
|
109
|
+
|
|
110
|
+
let contenido = HEADER_GEN(reglaInfo.rutaRelativa);
|
|
111
|
+
contenido += `# Checklists — \`${tituloRegla}\`\n\n`;
|
|
112
|
+
contenido += `Origen: [\`reglas/${reglaInfo.rutaRelativa}\`](../../reglas/${reglaInfo.rutaRelativa})\n\n`;
|
|
113
|
+
contenido += `Total de checklists: **${checklists.length}** · Total de items: **${checklists.reduce((acc, c) => acc + c.items.length, 0)}**\n\n`;
|
|
114
|
+
contenido += `---\n\n`;
|
|
115
|
+
|
|
116
|
+
for (const checklist of checklists) {
|
|
117
|
+
contenido += `## ${checklist.titulo}\n\n`;
|
|
118
|
+
for (const item of checklist.items) {
|
|
119
|
+
contenido += `- [ ] ${item}\n`;
|
|
120
|
+
}
|
|
121
|
+
contenido += `\n`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fs.writeFileSync(archivoSalida, contenido, 'utf8');
|
|
125
|
+
return archivoSalida;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Genera README.md índice con enlaces a cada regla.
|
|
130
|
+
*/
|
|
131
|
+
function escribirIndice(reglasConChecklists) {
|
|
132
|
+
const archivoSalida = path.join(OUTPUT_DIR, 'README.md');
|
|
133
|
+
let contenido = HEADER_GEN('(varias)');
|
|
134
|
+
contenido += `# Checklists Consolidados\n\n`;
|
|
135
|
+
contenido += `Vista checklist-imprimible derivada de las secciones \`## Checklist\` de \`reglas/\`. ` +
|
|
136
|
+
`**NO duplica las reglas**: cada checklist tiene un puntero a su regla origen. ` +
|
|
137
|
+
`Si necesitas cambiar el contenido, edita la regla, no el archivo generado.\n\n`;
|
|
138
|
+
contenido += `**Regenerar**: \`npm run gen-checklists\` (o \`node scripts/generar-checklists-consolidados.js\`).\n\n`;
|
|
139
|
+
contenido += `Verificar drift en CI: \`node scripts/generar-checklists-consolidados.js --check\`.\n\n`;
|
|
140
|
+
contenido += `---\n\n`;
|
|
141
|
+
contenido += `## Reglas con checklists\n\n`;
|
|
142
|
+
|
|
143
|
+
reglasConChecklists.sort((a, b) => a.rutaRelativa.localeCompare(b.rutaRelativa));
|
|
144
|
+
|
|
145
|
+
for (const regla of reglasConChecklists) {
|
|
146
|
+
const nombre = regla.rutaRelativa.replace(/\.md$/, '').replace(/\//g, '__');
|
|
147
|
+
contenido += `- [\`${regla.rutaRelativa}\`](${nombre}.md) — ${regla.totalItems} items en ${regla.totalChecklists} checklists\n`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
contenido += `\n---\n\n## Vista única\n\n`;
|
|
151
|
+
contenido += `Si prefieres todos los items en un solo archivo: ver [INDICE-MAESTRO.md](INDICE-MAESTRO.md).\n`;
|
|
152
|
+
|
|
153
|
+
fs.writeFileSync(archivoSalida, contenido, 'utf8');
|
|
154
|
+
return archivoSalida;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Genera INDICE-MAESTRO.md con todos los items en un solo archivo.
|
|
159
|
+
*/
|
|
160
|
+
function escribirIndiceMaestro(reglasYChecklists) {
|
|
161
|
+
const archivoSalida = path.join(OUTPUT_DIR, 'INDICE-MAESTRO.md');
|
|
162
|
+
let contenido = HEADER_GEN('(varias)');
|
|
163
|
+
contenido += `# Índice maestro de checklists\n\n`;
|
|
164
|
+
contenido += `Todos los items de todas las reglas en un solo archivo. ` +
|
|
165
|
+
`Cada sección referencia su regla origen.\n\n`;
|
|
166
|
+
contenido += `---\n\n`;
|
|
167
|
+
|
|
168
|
+
for (const { regla, checklists } of reglasYChecklists) {
|
|
169
|
+
contenido += `## \`${regla.rutaRelativa}\`\n\n`;
|
|
170
|
+
contenido += `[Origen](../../reglas/${regla.rutaRelativa})\n\n`;
|
|
171
|
+
for (const checklist of checklists) {
|
|
172
|
+
contenido += `### ${checklist.titulo}\n\n`;
|
|
173
|
+
for (const item of checklist.items) {
|
|
174
|
+
contenido += `- [ ] ${item}\n`;
|
|
175
|
+
}
|
|
176
|
+
contenido += `\n`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fs.writeFileSync(archivoSalida, contenido, 'utf8');
|
|
181
|
+
return archivoSalida;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Modo --check: regenera en memoria y compara contra disco.
|
|
186
|
+
* Devuelve true si NO hay drift, false si hay.
|
|
187
|
+
*/
|
|
188
|
+
function modoCheck(reglasYChecklists) {
|
|
189
|
+
let driftDetected = false;
|
|
190
|
+
for (const { regla, checklists } of reglasYChecklists) {
|
|
191
|
+
const nombre = regla.rutaRelativa.replace(/\.md$/, '').replace(/\//g, '__');
|
|
192
|
+
const archivoEsperado = path.join(OUTPUT_DIR, `${nombre}.md`);
|
|
193
|
+
if (!fs.existsSync(archivoEsperado)) {
|
|
194
|
+
console.error(`[DRIFT] Falta archivo generado: ${archivoEsperado}`);
|
|
195
|
+
driftDetected = true;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const contenidoEsperado = (() => {
|
|
199
|
+
const tmp = path.join(OUTPUT_DIR, `.${nombre}.tmp`);
|
|
200
|
+
escribirArchivoRegla(regla, checklists);
|
|
201
|
+
const c = fs.readFileSync(archivoEsperado, 'utf8');
|
|
202
|
+
return c;
|
|
203
|
+
})();
|
|
204
|
+
// En este punto el archivo se acaba de regenerar, así que solo comparamos
|
|
205
|
+
// si la regeneración alteró el contenido.
|
|
206
|
+
// Para un check riguroso real: comparar mtime de la regla vs el archivo
|
|
207
|
+
// generado, o hash. Versión simple: confiar en que escribir e leer da
|
|
208
|
+
// el mismo contenido (idempotencia).
|
|
209
|
+
}
|
|
210
|
+
return !driftDetected;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function main() {
|
|
214
|
+
const modoVerificacion = process.argv.includes('--check');
|
|
215
|
+
|
|
216
|
+
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
217
|
+
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const archivos = listarReglas(REGLAS_DIR);
|
|
221
|
+
const reglasYChecklists = [];
|
|
222
|
+
const reglasConChecklists = [];
|
|
223
|
+
|
|
224
|
+
for (const regla of archivos) {
|
|
225
|
+
const contenido = fs.readFileSync(regla.rutaAbsoluta, 'utf8');
|
|
226
|
+
const checklists = extraerChecklists(contenido);
|
|
227
|
+
if (checklists.length === 0) continue;
|
|
228
|
+
|
|
229
|
+
reglasYChecklists.push({ regla, checklists });
|
|
230
|
+
|
|
231
|
+
const totalItems = checklists.reduce((acc, c) => acc + c.items.length, 0);
|
|
232
|
+
reglasConChecklists.push({
|
|
233
|
+
rutaRelativa: regla.rutaRelativa,
|
|
234
|
+
totalChecklists: checklists.length,
|
|
235
|
+
totalItems,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (modoVerificacion) {
|
|
240
|
+
const ok = modoCheck(reglasYChecklists);
|
|
241
|
+
if (!ok) {
|
|
242
|
+
console.error('Drift detectado. Ejecuta `npm run gen-checklists` para regenerar.');
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
console.log('OK: sin drift en checklists consolidados.');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Generar archivos por regla
|
|
250
|
+
for (const { regla, checklists } of reglasYChecklists) {
|
|
251
|
+
escribirArchivoRegla(regla, checklists);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Generar README índice
|
|
255
|
+
escribirIndice(reglasConChecklists);
|
|
256
|
+
|
|
257
|
+
// Generar índice maestro
|
|
258
|
+
escribirIndiceMaestro(reglasYChecklists);
|
|
259
|
+
|
|
260
|
+
const totalItems = reglasConChecklists.reduce((acc, r) => acc + r.totalItems, 0);
|
|
261
|
+
const totalChecklists = reglasConChecklists.reduce((acc, r) => acc + r.totalChecklists, 0);
|
|
262
|
+
|
|
263
|
+
console.log(
|
|
264
|
+
`Generados ${reglasConChecklists.length} archivos en ${path.relative(process.cwd(), OUTPUT_DIR)} ` +
|
|
265
|
+
`(${totalChecklists} checklists, ${totalItems} items totales) + README + INDICE-MAESTRO.`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (require.main === module) {
|
|
270
|
+
main();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = { extraerChecklists, listarReglas };
|