@saulwade/swl-ses 1.0.1 → 1.1.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 +8 -5
- package/README.md +3 -3
- package/agentes/accesibilidad-wcag-swl.md +5 -7
- package/agentes/arquitecto-swl.md +5 -3
- package/agentes/auto-evolucion-swl.md +42 -12
- package/agentes/backend-api-swl.md +5 -3
- package/agentes/backend-csharp-swl.md +5 -3
- package/agentes/backend-go-swl.md +5 -3
- package/agentes/backend-java-swl.md +5 -3
- package/agentes/backend-node-swl.md +5 -3
- package/agentes/backend-python-swl.md +5 -3
- package/agentes/backend-rust-swl.md +5 -3
- package/agentes/backend-workers-swl.md +5 -3
- package/agentes/cloud-infra-swl.md +5 -6
- package/agentes/consolidador-swl.md +5 -3
- package/agentes/datos-swl.md +5 -7
- package/agentes/depurador-swl.md +6 -3
- package/agentes/devops-ci-swl.md +5 -3
- package/agentes/disenador-ui-swl.md +5 -7
- package/agentes/documentador-swl.md +5 -3
- package/agentes/frontend-angular-swl.md +5 -11
- package/agentes/frontend-css-swl.md +5 -9
- package/agentes/frontend-react-swl.md +5 -9
- package/agentes/frontend-swl.md +5 -9
- package/agentes/frontend-tailwind-swl.md +5 -9
- package/agentes/implementador-swl.md +6 -3
- package/agentes/investigador-swl.md +5 -3
- package/agentes/investigador-ux-swl.md +5 -9
- package/agentes/llm-apps-swl.md +5 -3
- package/agentes/migrador-swl.md +6 -3
- package/agentes/mobile-android-swl.md +5 -3
- package/agentes/mobile-cross-swl.md +5 -3
- package/agentes/mobile-ios-swl.md +5 -3
- package/agentes/mobile-testing-swl.md +5 -3
- package/agentes/notificador-swl.md +5 -3
- package/agentes/observabilidad-swl.md +5 -3
- package/agentes/orquestador-swl.md +29 -8
- package/agentes/pagos-swl.md +5 -3
- package/agentes/perfilador-usuario-swl.md +4 -2
- package/agentes/planificador-swl.md +5 -3
- package/agentes/producto-prd-swl.md +5 -3
- package/agentes/red-team-swl.md +4 -2
- package/agentes/release-manager-swl.md +6 -8
- package/agentes/rendimiento-swl.md +5 -6
- package/agentes/resolutor-build-swl.md +5 -3
- package/agentes/revisor-angular-swl.md +5 -3
- package/agentes/revisor-codigo-swl.md +90 -4
- package/agentes/revisor-csharp-swl.md +5 -3
- package/agentes/revisor-go-swl.md +5 -3
- package/agentes/revisor-java-swl.md +5 -3
- package/agentes/revisor-kotlin-swl.md +5 -3
- package/agentes/revisor-nextjs-swl.md +5 -3
- package/agentes/revisor-php-swl.md +5 -3
- package/agentes/revisor-react-swl.md +5 -3
- package/agentes/revisor-rust-swl.md +5 -3
- package/agentes/revisor-seguridad-swl.md +5 -3
- package/agentes/revisor-swift-swl.md +5 -3
- package/agentes/revisor-typescript-swl.md +5 -3
- package/agentes/sre-swl.md +5 -3
- package/agentes/tdd-qa-swl.md +5 -3
- package/agentes/ux-disenador-swl.md +5 -9
- package/comandos/swl/evaluar-skill.md +18 -0
- package/comandos/swl/evolucion-estado.md +49 -0
- package/comandos/swl/release.md +77 -1
- package/comandos/swl/salud.md +23 -0
- package/habilidades/checklist-seguridad/SKILL.md +57 -1
- package/habilidades/extractor-de-aprendizajes/SKILL.md +15 -5
- package/habilidades/fastapi-experto/SKILL.md +10 -1
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/manejo-errores/SKILL.md +63 -4
- package/habilidades/patrones-python/SKILL.md +5 -4
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/release-semver/SKILL.md +85 -1
- package/hooks/auto-evolucion.js +35 -1
- package/hooks/clasificador-mensajes.js +50 -3
- package/hooks/lib/agent-routing.js +107 -0
- package/hooks/lib/delegation-tracker.js +162 -44
- package/hooks/lib/evolution-tracker.js +12 -3
- package/hooks/lib/memory-search.js +59 -1
- package/hooks/lib/nudge-tracker.js +10 -1
- package/hooks/lib/provenance-tracker.js +11 -3
- package/hooks/lib/text-similarity.js +241 -0
- package/hooks/metricas-evolucion.js +168 -1
- package/hooks/monitor-contexto.js +54 -6
- package/hooks/preservar-estado-pre-compact.js +11 -1
- package/hooks/risk-scoring.js +10 -1
- package/hooks/tracking-costos.js +10 -1
- package/hooks/validar-formato-post-subagente.js +140 -0
- package/hooks/validar-memoria-hook.js +218 -0
- package/manifiestos/agent-output-schemas.json +57 -0
- package/manifiestos/hooks-config.json +18 -0
- package/manifiestos/modulos.json +3 -0
- package/manifiestos/skills-lock.json +1065 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/reglas/arquitectura.md +20 -0
- package/reglas/fragmentos-compartidos.md +152 -0
- package/reglas/gobernanza.md +10 -1
- package/reglas/seguridad-agentes.md +12 -0
- package/reglas/skills-estandar.md +19 -0
- package/schemas/agent-frontmatter.schema.json +18 -0
- package/scripts/auditar-agentes-gaps.js +9 -1
- package/scripts/auditar-cobertura-frameworks.js +9 -1
- package/scripts/auditar-skills-gaps.js +9 -1
- package/scripts/bootstrap-instintos.js +11 -1
- package/scripts/generar-inventario.js +112 -9
- package/scripts/generar-matriz-lenguajes.js +271 -0
- package/scripts/generar-skills-lock.js +190 -0
- package/scripts/lib/estado.js +12 -2
- package/scripts/lib/gitignore-manifest.js +32 -2
- package/scripts/migrar-csv-a-array.js +168 -0
- package/scripts/migrar-fase-dominio.js +201 -0
- package/scripts/publicar.js +88 -18
|
@@ -5,15 +5,15 @@ description: >
|
|
|
5
5
|
generics, utility types, module patterns y testing. Detecta uso de any, type assertions
|
|
6
6
|
inseguras, tipos duplicados y generics innecesarios. Invocar para revisión de
|
|
7
7
|
código TypeScript puro, librerías, o backends Node.js.
|
|
8
|
-
tools: Read, Grep, Glob, Bash
|
|
8
|
+
tools: [Read, Grep, Glob, Bash]
|
|
9
9
|
model: claude-sonnet-4-6
|
|
10
10
|
modeloAlterno: claude-haiku-4-5-20251001
|
|
11
11
|
ventanaContexto: 200k
|
|
12
12
|
color: blue
|
|
13
13
|
version: 1.0.0
|
|
14
14
|
nivelRiesgo: BAJO
|
|
15
|
-
skillsInvocables: typescript-avanzado, node-experto, checklist-calidad, tdd-workflow
|
|
16
|
-
skillsRestringidos:
|
|
15
|
+
skillsInvocables: [typescript-avanzado, node-experto, checklist-calidad, tdd-workflow]
|
|
16
|
+
skillsRestringidos: []
|
|
17
17
|
permisosRed: false
|
|
18
18
|
permisosEscritura: true
|
|
19
19
|
permisosComandos: true
|
|
@@ -22,6 +22,8 @@ toolBudget:
|
|
|
22
22
|
standard: 20
|
|
23
23
|
complex: 35
|
|
24
24
|
evolvable: true # nivelRiesgo=BAJO
|
|
25
|
+
fase: verify
|
|
26
|
+
dominio: quality
|
|
25
27
|
exclusiones:
|
|
26
28
|
- "No invocar para implementar código TypeScript — este agente solo revisa; la implementación corresponde a frontend-*-swl o backend-node-swl."
|
|
27
29
|
- "No invocar para revisar lenguajes distintos a TypeScript/JavaScript — usar el revisor especializado correspondiente."
|
package/agentes/sre-swl.md
CHANGED
|
@@ -7,7 +7,7 @@ description: >
|
|
|
7
7
|
redacte un post-mortem, se planifique chaos testing, o se necesite
|
|
8
8
|
cuantificar la confiabilidad de un sistema. Complementa a observabilidad-swl
|
|
9
9
|
(que implementa métricas) con el framework de confiabilidad.
|
|
10
|
-
tools: Read, Write, Edit, Bash, Grep, Glob, Skill
|
|
10
|
+
tools: [Read, Write, Edit, Bash, Grep, Glob, Skill]
|
|
11
11
|
model: claude-opus-4-7
|
|
12
12
|
modeloAlterno: claude-sonnet-4-6
|
|
13
13
|
ventanaContexto: 200k
|
|
@@ -15,8 +15,8 @@ permissionMode: acceptEdits
|
|
|
15
15
|
color: red
|
|
16
16
|
version: 1.0.0
|
|
17
17
|
nivelRiesgo: ALTO
|
|
18
|
-
skillsInvocables: sre-patrones, performance-baseline, monitoring-alertas, checklist-seguridad
|
|
19
|
-
skillsRestringidos: frontend-css-swl, mobile-flutter
|
|
18
|
+
skillsInvocables: [sre-patrones, performance-baseline, monitoring-alertas, checklist-seguridad]
|
|
19
|
+
skillsRestringidos: [frontend-css-swl, mobile-flutter]
|
|
20
20
|
permisosRed: false
|
|
21
21
|
permisosEscritura: true
|
|
22
22
|
permisosComandos: true
|
|
@@ -25,6 +25,8 @@ toolBudget:
|
|
|
25
25
|
standard: 30
|
|
26
26
|
complex: 60
|
|
27
27
|
evolvable: false # nivelRiesgo=ALTO
|
|
28
|
+
fase: release
|
|
29
|
+
dominio: infra
|
|
28
30
|
exclusiones:
|
|
29
31
|
- "No invocar para implementar features de aplicación — este agente trabaja en confiabilidad y operaciones, no en lógica de negocio."
|
|
30
32
|
- "No invocar para configurar pipelines CI/CD — ese trabajo corresponde a devops-ci-swl."
|
package/agentes/tdd-qa-swl.md
CHANGED
|
@@ -6,15 +6,15 @@ description: >
|
|
|
6
6
|
y gaps de cobertura. Objetivo: >80% líneas, >70% branches. Invocar antes de
|
|
7
7
|
implementar (TDD) o después de implementar (auditoría de cobertura). NUNCA
|
|
8
8
|
modifica código de producción — solo archivos de test y fixtures.
|
|
9
|
-
tools: Read, Write, Edit, Bash, Grep, Glob
|
|
9
|
+
tools: [Read, Write, Edit, Bash, Grep, Glob]
|
|
10
10
|
model: claude-sonnet-4-6
|
|
11
11
|
modeloAlterno: claude-haiku-4-5-20251001
|
|
12
12
|
ventanaContexto: 200k
|
|
13
13
|
color: purple
|
|
14
14
|
version: 1.0.0
|
|
15
15
|
nivelRiesgo: BAJO
|
|
16
|
-
skillsInvocables: tdd-workflow, testing-python, checklist-calidad, manejo-errores, webapp-testing, php-testing, nextjs-testing
|
|
17
|
-
skillsRestringidos:
|
|
16
|
+
skillsInvocables: [tdd-workflow, testing-python, checklist-calidad, manejo-errores, webapp-testing, php-testing, nextjs-testing]
|
|
17
|
+
skillsRestringidos: []
|
|
18
18
|
permisosRed: false
|
|
19
19
|
permisosEscritura: true
|
|
20
20
|
permisosComandos: true
|
|
@@ -23,6 +23,8 @@ toolBudget:
|
|
|
23
23
|
standard: 30
|
|
24
24
|
complex: 50
|
|
25
25
|
evolvable: true # nivelRiesgo=BAJO
|
|
26
|
+
fase: verify
|
|
27
|
+
dominio: quality
|
|
26
28
|
exclusiones:
|
|
27
29
|
- "No invocar para testing E2E de aplicaciones móviles — ese trabajo corresponde a mobile-testing-swl."
|
|
28
30
|
- "No invocar para testing de seguridad o penetration testing — ese trabajo corresponde a revisor-seguridad-swl o red-team-swl."
|
|
@@ -9,7 +9,7 @@ description: >
|
|
|
9
9
|
disenador-ui-swl (capa visual y tokens). NO invocar para implementacion de
|
|
10
10
|
codigo — produce specs, no codigo. Para implementar usar frontend-*-swl con
|
|
11
11
|
la UI-SPEC.md producida.
|
|
12
|
-
tools: Read, Write, Grep, Glob, WebSearch
|
|
12
|
+
tools: [Read, Write, Grep, Glob, WebSearch]
|
|
13
13
|
model: claude-sonnet-4-6
|
|
14
14
|
modeloAlterno: claude-haiku-4-5-20251001
|
|
15
15
|
ventanaContexto: 200k
|
|
@@ -17,18 +17,14 @@ permissionMode: plan
|
|
|
17
17
|
color: pink
|
|
18
18
|
version: 1.0.0
|
|
19
19
|
nivelRiesgo: BAJO
|
|
20
|
-
skillsInvocables: ux-diseno, wireframes-flujos, design-tokens, accesibilidad-a11y, diseno-responsivo
|
|
21
|
-
skillsRestringidos:
|
|
22
|
-
- fastapi-python
|
|
23
|
-
- django-expert
|
|
24
|
-
- postgresql-table-design
|
|
25
|
-
- python-patterns
|
|
26
|
-
- angular-component
|
|
27
|
-
- angular-forms
|
|
20
|
+
skillsInvocables: [ux-diseno, wireframes-flujos, design-tokens, accesibilidad-a11y, diseno-responsivo]
|
|
21
|
+
skillsRestringidos: [fastapi-python, django-expert, postgresql-table-design, python-patterns, angular-component, angular-forms]
|
|
28
22
|
permisosRed: true
|
|
29
23
|
permisosEscritura: true
|
|
30
24
|
permisosComandos: false
|
|
31
25
|
evolvable: true # nivelRiesgo=BAJO
|
|
26
|
+
fase: plan
|
|
27
|
+
dominio: ux
|
|
32
28
|
exclusiones:
|
|
33
29
|
- "No invocar para implementar código de interfaz — este agente produce UI-SPEC.md, no código; usar frontend-react-swl, frontend-angular-swl o frontend-swl para la implementación."
|
|
34
30
|
- "No invocar cuando el pipeline tiene roles separados: preferir investigador-ux-swl para research y disenador-ui-swl para especificaciones visuales puras."
|
|
@@ -356,6 +356,24 @@ Asignar badge:
|
|
|
356
356
|
< 60 → Sin badge — no hacer merge hasta corregir
|
|
357
357
|
```
|
|
358
358
|
|
|
359
|
+
### Gate de promoción para auto-evolucion-swl (G8)
|
|
360
|
+
|
|
361
|
+
Este comando es el **gate obligatorio** del flujo de promoción de skills
|
|
362
|
+
auto-generados desde `_userland/plugins/` a `habilidades/`. El agente
|
|
363
|
+
`auto-evolucion-swl` invoca este comando antes de mover un skill al core
|
|
364
|
+
(ver `agentes/auto-evolucion-swl.md` sección "Gate G8").
|
|
365
|
+
|
|
366
|
+
Umbral de promoción:
|
|
367
|
+
|
|
368
|
+
| Badge | Acción de auto-evolucion |
|
|
369
|
+
|-------|-------------------------|
|
|
370
|
+
| Platino, Oro, Plata | Promover a `habilidades/`, registrar en manifiestos |
|
|
371
|
+
| Bronce | NO promover, devolver a `_userland/` con feedback |
|
|
372
|
+
| Sin badge | NO promover, registrar rechazo en `.planning/evolucion/promociones-rechazadas.jsonl` |
|
|
373
|
+
|
|
374
|
+
Si un skill es rechazado ≥3 veces consecutivas, el agente escala al usuario
|
|
375
|
+
con análisis de qué dimensiones bajan el score. Origen: ADR 0013 sección 3C.
|
|
376
|
+
|
|
359
377
|
## Paso 4 — Emitir reporte
|
|
360
378
|
|
|
361
379
|
Generar el siguiente reporte directamente en la conversación:
|
|
@@ -38,6 +38,30 @@ Si el archivo no existe: ejecutar el regenerado del Paso 0. Si sigue sin
|
|
|
38
38
|
existir: reportar "el ciclo de evolución no ha corrido aún; ejecuta cualquier
|
|
39
39
|
tarea para poblar métricas iniciales".
|
|
40
40
|
|
|
41
|
+
## Paso 1.5 — Cargar contexto del kernel (gobernanza)
|
|
42
|
+
|
|
43
|
+
Para el bloque **KERNEL** del dashboard, recolectar:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 1. Conteo de agentes evolvable=false vs total
|
|
47
|
+
TOTAL=$(ls agentes/*.md | grep -v "^agentes/_" | wc -l)
|
|
48
|
+
EVOL_FALSE=$(grep -l '^evolvable: false' agentes/*.md | wc -l)
|
|
49
|
+
|
|
50
|
+
# 2. Último ADR del repo madre — el más reciente por fecha en frontmatter
|
|
51
|
+
ULTIMO_ADR=$(ls .planning/adrs/[0-9]*.md | sort -r | head -1)
|
|
52
|
+
TITULO=$(head -1 "$ULTIMO_ADR" | sed 's/^# //')
|
|
53
|
+
FECHA=$(grep -m1 '^\*\*Fecha\*\*:' "$ULTIMO_ADR" | sed 's/.*\*\*Fecha\*\*:\s*//')
|
|
54
|
+
|
|
55
|
+
echo "ADR_KERNEL_LINEA=\"$TITULO — $FECHA\""
|
|
56
|
+
echo "RATIO=\"$EVOL_FALSE/$TOTAL\""
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Estos valores alimentan la sección KERNEL del template.
|
|
60
|
+
|
|
61
|
+
Si `.planning/evolucion/politica-evolvable.md` no existe, reportar como
|
|
62
|
+
hallazgo crítico: el kernel SIN política documentada es violación de
|
|
63
|
+
`reglas/gobernanza.md`.
|
|
64
|
+
|
|
41
65
|
## Paso 2 — Emitir el dashboard (formato humano)
|
|
42
66
|
|
|
43
67
|
Template de salida:
|
|
@@ -74,6 +98,26 @@ Template de salida:
|
|
|
74
98
|
Aplicadas ............. <n>
|
|
75
99
|
Revertidas ............ <n>
|
|
76
100
|
Neta .................. <n>
|
|
101
|
+
Rollback ratio ........ <%> (N/A si aplicadas == 0)
|
|
102
|
+
|
|
103
|
+
CALIDAD CONDUCTUAL (14d)
|
|
104
|
+
Fallos por tipo:
|
|
105
|
+
bad_output_format <n>
|
|
106
|
+
tool_error <n>
|
|
107
|
+
timeout <n>
|
|
108
|
+
schema_violation <n>
|
|
109
|
+
task_incomplete <n>
|
|
110
|
+
unknown <n>
|
|
111
|
+
Reintentos consecutivos
|
|
112
|
+
Total ............... <n> (mismo agente, misma task, ≤30 min)
|
|
113
|
+
Top agente .......... <agente>: <n>
|
|
114
|
+
Latencia top agente
|
|
115
|
+
Mediana ............. <ms>
|
|
116
|
+
p95 ................. <ms>
|
|
117
|
+
Sin instrumentación todavía:
|
|
118
|
+
• precision_routing_fase_dominio
|
|
119
|
+
• utilidad_memoria_recuperada
|
|
120
|
+
• violaciones_formato_post_ejecucion
|
|
77
121
|
|
|
78
122
|
ALERTAS PERSISTENTES
|
|
79
123
|
<si alertasActivas.length === 0>
|
|
@@ -82,6 +126,11 @@ Template de salida:
|
|
|
82
126
|
[!] kind=X target=Y count=Z (primera: fecha)
|
|
83
127
|
...
|
|
84
128
|
|
|
129
|
+
KERNEL (gobernanza)
|
|
130
|
+
Política evolvable .... .planning/evolucion/politica-evolvable.md
|
|
131
|
+
Último ADR kernel ..... <ADR-NNNN — título — fecha>
|
|
132
|
+
Agentes evolvable=false <n>/<total>
|
|
133
|
+
|
|
85
134
|
═══════════════════════════════════════════════════════════════
|
|
86
135
|
```
|
|
87
136
|
|
package/comandos/swl/release.md
CHANGED
|
@@ -120,7 +120,57 @@ Actualiza la versión en TODOS los archivos que la contienen. Para proyectos SWL
|
|
|
120
120
|
|
|
121
121
|
Para proyectos no-SWL: actualiza los archivos detectados en Paso 1 (package.json, pyproject.toml, VERSION).
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
### Tres capas de versionado — NO confundir
|
|
124
|
+
|
|
125
|
+
El sistema SWL versiona en tres capas independientes. El bump de versión del SISTEMA toca SOLO la capa 1:
|
|
126
|
+
|
|
127
|
+
| Capa | Qué versiona | Cuándo cambia | Tocar en `/swl:release`? |
|
|
128
|
+
|------|--------------|---------------|--------------------------|
|
|
129
|
+
| **1. Sistema** | El paquete `@saulwade/swl-ses` como conjunto | En cada release | **SÍ — los 15 archivos del checklist arriba** |
|
|
130
|
+
| **2. Componente individual** | Frontmatter `version:` de cada agente o skill (`agentes/*.md`, `habilidades/*/SKILL.md`) | Cuando el componente específico cambia (ver `/swl:aprender` Paso 6 acción 2) | **NO — cada componente versiona independiente** |
|
|
131
|
+
| **3. Histórico** | Referencias a versiones pasadas en `CHANGELOG.md`, `CHANGELOG-LEGACY.md`, ADRs, RELEASE-NOTES, comentarios `Histórico: hasta vX.Y...` | Nunca (son inmutables por definición) | **NO — son registros históricos** |
|
|
132
|
+
|
|
133
|
+
Verificación post-bump con `grep -rn "<versión-anterior>"` revelará decenas o cientos de matches en capas 2 y 3 — eso es esperado y correcto. Filtrar ruido para confirmar que las únicas líneas modificadas son de la capa 1:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Después del bump, validar consistencia SOLO en archivos canónicos del sistema
|
|
137
|
+
node -e "
|
|
138
|
+
const p=require('./package.json'),l=require('./plugin.json');
|
|
139
|
+
const lock=require('./package-lock.json');
|
|
140
|
+
const ok = p.version===l.version && l.version===lock.version;
|
|
141
|
+
console.log(ok ? 'OK consistente: '+p.version : 'INCONSISTENTE');
|
|
142
|
+
"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Si el chequeo del nodo arriba dice OK pero `grep` aún muestra matches de la versión anterior, esos matches son **capa 2 (frontmatter)** o **capa 3 (histórico)** y son legítimos — no tocarlos.
|
|
146
|
+
|
|
147
|
+
### Republish-only entre registries (caso especial)
|
|
148
|
+
|
|
149
|
+
Si el publish dual falla en uno de los 2 registries (typically npmjs o GitHub
|
|
150
|
+
Packages) pero el otro queda publicado, **NO se puede reintentar la misma versión**
|
|
151
|
+
— ningún registry permite sobreescribir versiones. Ejecutar inmediatamente un bump
|
|
152
|
+
PATCH (1.X.Y → 1.X.(Y+1)) siguiendo el checklist arriba, y publicar solo al
|
|
153
|
+
registry faltante con `node scripts/publicar.js --solo-npmjs` o `--solo-github`.
|
|
154
|
+
|
|
155
|
+
Documentar el republish exclusivamente como "republish de coordinación entre
|
|
156
|
+
registries" en CHANGELOG, sin atribuirle cambios funcionales que no existen.
|
|
157
|
+
|
|
158
|
+
Ver `Skill("release-semver")` sección "Publish a múltiples registries" para detalles.
|
|
159
|
+
|
|
160
|
+
## Paso 6.5 — Regenerar skills-lock.json
|
|
161
|
+
|
|
162
|
+
Antes del CHANGELOG, regenerar el lock de skills para capturar el estado de
|
|
163
|
+
los 151 SKILL.md de la release:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
node scripts/generar-skills-lock.js
|
|
167
|
+
git add manifiestos/skills-lock.json
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
El lock contiene SHA256 de cada SKILL.md y permite que `/swl:salud` detecte
|
|
171
|
+
drift silencioso entre releases. Si el lock no cambió respecto al anterior,
|
|
172
|
+
el commit lo refleja como no-op (idempotente). El archivo es pequeño (~37KB)
|
|
173
|
+
y debe versionarse.
|
|
124
174
|
|
|
125
175
|
## Paso 7 — Generar CHANGELOG
|
|
126
176
|
|
|
@@ -178,6 +228,32 @@ Resultado: 13/15 OK, 2 FALLA(S) obligatoria(s)
|
|
|
178
228
|
|
|
179
229
|
Esta gate existe porque el agente `release-manager-swl` ha omitido archivos en 3 releases consecutivos (5.10.3, 5.10.4, 5.10.5 — ver APRENDIZAJES.md) pese a tener la checklist documentada en su frontmatter. Un checklist textual no basta: se necesita ejecución.
|
|
180
230
|
|
|
231
|
+
### 10.1.1 Gate de cobertura por lenguaje (regeneración obligatoria)
|
|
232
|
+
|
|
233
|
+
Tras el verificar-release.js, regenerar la matriz lenguaje × cobertura SWL:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
node scripts/generar-matriz-lenguajes.js
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
El script reescribe `.planning/cobertura-lenguajes.md` con el estado actual.
|
|
240
|
+
Comparar contra el commit anterior:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
git diff .planning/cobertura-lenguajes.md
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Si algún lenguaje **bajó de status** (completo → parcial, parcial → faltante)
|
|
247
|
+
sin ADR documentado en `.planning/adrs/` justificando la regresión, **NO hacer
|
|
248
|
+
release**. La regresión silenciosa de cobertura erosiona la promesa "polyglot"
|
|
249
|
+
del repo.
|
|
250
|
+
|
|
251
|
+
Si el cambio es esperado (eliminación deliberada de un componente), el ADR
|
|
252
|
+
debe existir con número y fecha y referenciarse en el commit del release.
|
|
253
|
+
|
|
254
|
+
Lo mismo aplica para releases con incremento de cobertura: la nueva matriz se
|
|
255
|
+
commitea junto al release y se menciona en RELEASE-NOTES.
|
|
256
|
+
|
|
181
257
|
### 10.2 Verificación manual post-gate
|
|
182
258
|
|
|
183
259
|
```bash
|
package/comandos/swl/salud.md
CHANGED
|
@@ -286,6 +286,29 @@ Si `SWL_AUDIT_AGENTES` no está definida, este paso se omite — los reportes
|
|
|
286
286
|
son opt-in por diseño (CLAUDE.md: "Variables de entorno opt-in para
|
|
287
287
|
integraciones enterprise").
|
|
288
288
|
|
|
289
|
+
## Paso 5e — Verificación de drift de skills (skills-lock)
|
|
290
|
+
|
|
291
|
+
Si existe `manifiestos/skills-lock.json`, comparar el hash actual de cada
|
|
292
|
+
`habilidades/*/SKILL.md` contra el lock. Detecta ediciones silenciosas de
|
|
293
|
+
skills entre release y release.
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
node scripts/generar-skills-lock.js --check
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Estados posibles:
|
|
300
|
+
|
|
301
|
+
| Salida | Significado | Acción |
|
|
302
|
+
|--------|-------------|--------|
|
|
303
|
+
| `✓ skills-lock OK: N skills sin drift` | Todos los hashes coinciden | Continuar |
|
|
304
|
+
| `✗ skills-lock DRIFT detectado` | Skills modificados, nuevos o faltantes | Listar diferencias y proponer regenerar |
|
|
305
|
+
|
|
306
|
+
Si NO existe el lock (proyecto fresco o pre-1.1.0), este paso se omite y
|
|
307
|
+
emite advertencia sugerente: `node scripts/generar-skills-lock.js`.
|
|
308
|
+
|
|
309
|
+
El lock se regenera automáticamente como parte de `/swl:release`. Drift
|
|
310
|
+
fuera de release indica ediciones manuales no committed.
|
|
311
|
+
|
|
289
312
|
## Paso 6b — Formato de salida enriquecido (HealthRow)
|
|
290
313
|
|
|
291
314
|
El módulo `scripts/lib/health-row.js` genera filas de salud con formato visual enriquecido
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: checklist-seguridad
|
|
3
3
|
description: Checklist de seguridad basado en OWASP Top 10 + seguridad de agentes autónomos (A11). Cubre inyección, autenticación, exposición de datos, control de acceso, configuración insegura, XSS, deserialización, componentes vulnerables, logging y agencia excesiva de IA. Produce reporte con hallazgos y remediaciones.
|
|
4
|
-
version: "1.
|
|
4
|
+
version: "1.1.1"
|
|
5
|
+
evolved: true
|
|
6
|
+
evolved-from: "1.1.0"
|
|
7
|
+
evolved-at: "2026-05-04"
|
|
8
|
+
evolved-by: "aprender"
|
|
9
|
+
evolved-note: "Corregir contradicción interna en ejemplo BIEN de IDOR pre-side-effect: el detail exponía UUID interno violando el gotcha 'vocabulario interno' de fastapi-experto v1.1.1. Detail al cliente ahora es genérico (Recurso no encontrado); UUID y tenant solo en logger.info para auditoría."
|
|
5
10
|
herramientasPermitidas: [Read, Grep]
|
|
6
11
|
evolvable: true # default para skill estandar
|
|
7
12
|
nist_csf: [PR.PS-01, PR.DS-02, PR.DS-10, DE.CM-09, RS.MI-01]
|
|
@@ -58,6 +63,57 @@ grep -rn "user_id.*Query\|owner_id.*Query" --include="*.py" | head -20
|
|
|
58
63
|
**Señal de vulnerabilidad**: endpoint que filtra por `user_id` recibido como
|
|
59
64
|
parámetro de URL sin compararlo con el `user_id` del token JWT.
|
|
60
65
|
|
|
66
|
+
### IDOR pre-side-effect: validar pertenencia ANTES de operaciones costosas
|
|
67
|
+
|
|
68
|
+
Un endpoint que sube/cifra/procesa material antes de validar que el recurso pertenece al
|
|
69
|
+
tenant del usuario es un IDOR amplificado: aunque RLS o el FK rechacen la operación al
|
|
70
|
+
final, los **side-effects intermedios** (lectura de bytes del cliente, cifrado AES-GCM,
|
|
71
|
+
upload a MinIO/S3 bajo un namespace, llamada costosa a API externa) **ya ocurrieron** y
|
|
72
|
+
pueden contaminar storage, agotar recursos o filtrar PII bajo el namespace incorrecto.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
# MAL — IDOR pre-side-effect: el upload ocurre antes del check RLS
|
|
76
|
+
async def subir_evidencia(entrega_id: UUID, archivo: UploadFile, ...):
|
|
77
|
+
contenido = await archivo.read() # side-effect: bytes leídos
|
|
78
|
+
cifrado = aes_gcm_encrypt(contenido, master_key) # side-effect: PII cifrada
|
|
79
|
+
object_key = f"tenant_{usuario.tenant_id}/{entrega_id}/{uuid4()}"
|
|
80
|
+
await minio.put_object(object_key, cifrado) # side-effect: subido a MinIO
|
|
81
|
+
# AHORA el INSERT corre con RLS — si entrega_id es de otro tenant, falla con 404,
|
|
82
|
+
# pero el PII ya quedó cifrado bajo el namespace de tenant_A para entrega_B.
|
|
83
|
+
await repo.insert_evidencia(entrega_id, object_key, ...)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# BIEN — validar pertenencia ANTES de cualquier side-effect costoso
|
|
88
|
+
async def subir_evidencia(entrega_id: UUID, archivo: UploadFile, ...):
|
|
89
|
+
if await repo_entrega.obtener_por_id(entrega_id) is None:
|
|
90
|
+
# RLS filtra el SELECT — None significa "no existe en este tenant".
|
|
91
|
+
# Detail genérico al cliente; UUID + tenant solo en log interno.
|
|
92
|
+
# (Ver gotcha "vocabulario interno en detail" de fastapi-experto.)
|
|
93
|
+
logger.info(
|
|
94
|
+
"IDOR check fallido: recurso %s no accesible para tenant %s",
|
|
95
|
+
entrega_id, usuario.tenant_id,
|
|
96
|
+
)
|
|
97
|
+
raise HTTPException(404, "Recurso no encontrado.")
|
|
98
|
+
|
|
99
|
+
contenido = await archivo.read() # solo si validación pasó
|
|
100
|
+
cifrado = aes_gcm_encrypt(contenido, master_key)
|
|
101
|
+
...
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Cuándo aplicar**: cualquier endpoint que combine path-param de recurso (`{recurso_id}`)
|
|
105
|
+
con uno o más de estos side-effects: `archivo.read()`, cifrado, upload a storage externo,
|
|
106
|
+
llamada a API costosa de PSP/proveedor, generación de PDF, envío de email/SMS. Antes
|
|
107
|
+
del primer side-effect, ejecutar `repo.obtener_por_id(recurso_id)` y retornar 404 si None.
|
|
108
|
+
|
|
109
|
+
**Mecanismos que NO son suficientes por sí solos**:
|
|
110
|
+
- RLS DENY-by-default: protege el INSERT/UPDATE/SELECT, pero NO los side-effects intermedios.
|
|
111
|
+
- FK constraint: protege la integridad referencial, pero el side-effect ya ocurrió.
|
|
112
|
+
- `require_permiso`: valida que el usuario tiene permiso de la acción, NO que el recurso
|
|
113
|
+
específico pertenezca a su tenant.
|
|
114
|
+
|
|
115
|
+
La validación pre-side-effect es **defensa en profundidad**: complementa RLS/FK, no las reemplaza.
|
|
116
|
+
|
|
61
117
|
---
|
|
62
118
|
|
|
63
119
|
## A02 — Cryptographic Failures (Exposición de datos sensibles)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: extractor-de-aprendizajes
|
|
3
3
|
description: Convertir errores y patrones descubiertos durante la implementación en nuevas habilidades o reglas. Ciclo de mejora continua del sistema SWL.
|
|
4
|
-
version: "1.0.
|
|
4
|
+
version: "1.0.3"
|
|
5
5
|
herramientasPermitidas: [Read]
|
|
6
6
|
exclusiones:
|
|
7
7
|
- "No cargar para actualizar el perfil del usuario — las correcciones explícitas del usuario van a `instintos/perfil-usuario.yaml` vía `perfilador-usuario-swl`, no a APRENDIZAJES.md."
|
|
@@ -10,10 +10,10 @@ exclusiones:
|
|
|
10
10
|
- "No cargar si el aprendizaje no tiene causa raíz identificada — documentar síntoma sin causa produce reglas que no previenen el error real."
|
|
11
11
|
evolvable: true # default para skill estandar
|
|
12
12
|
evolved: true
|
|
13
|
-
evolved-from: "
|
|
14
|
-
evolved-at: "2026-
|
|
13
|
+
evolved-from: "1.1.1"
|
|
14
|
+
evolved-at: "2026-05-02"
|
|
15
15
|
evolved-by: "aprender"
|
|
16
|
-
evolved-note: "
|
|
16
|
+
evolved-note: "Extender gotcha Explore (papers → papers+repos) tras confirmación x4 — sync desde skill global"
|
|
17
17
|
---
|
|
18
18
|
# Extractor de Aprendizajes
|
|
19
19
|
|
|
@@ -296,7 +296,17 @@ Durante `/swl:aprender`, aplicar estas reglas:
|
|
|
296
296
|
- **`rating: HIGH` asignado sin verificar criterio de irreversibilidad**: el agente promueve a CLAUDE.md un aprendizaje "MEDIUM" por el entusiasmo del momento. Causa: no se aplicó el criterio de "decisión irreversible o bug crítico". Solución: antes de asignar HIGH, verificar: ¿cambiar esto en el futuro requeriría refactorizar múltiples archivos o migrar datos? Si no, mantener MEDIUM.
|
|
297
297
|
- **Regla incompleta sobre registro de hooks**: una entrada en memoria dice "registrar en modulos.json" pero omite `hooks-config.json`, y en la siguiente iteración se repite el fallo en CI porque ambos manifiestos son obligatorios. Causa: la regla de la lección anterior no cubrió todos los manifiestos afectados. Solución: al documentar una regla sobre registro en manifiestos, listar EXPLÍCITAMENTE cada manifiesto con su responsabilidad distinta (`modulos.json` = qué copiar; `hooks-config.json` = cómo registrar evento). Evidencia: tres incidentes históricos del mismo patrón incompleto (v5.7.1, v5.7.2/3, v5.11.0).
|
|
298
298
|
- **Inventario estimado a mano en vez de regenerar**: el agente cuenta "28 hooks" visualmente y propaga la cifra a 5 archivos (CLAUDE/README/package/plugin/SALUD); al regenerar con `scripts/generar-inventario.js` el número real es 30. Causa: confiar en la observación directa en vez de la fuente de verdad determinista. Solución: antes de modificar cualquier contador en documentación oficial, ejecutar el script de inventario y usar su salida como ground truth.
|
|
299
|
-
- **Sub-agente Explore sobreestima
|
|
299
|
+
- **Sub-agente Explore sobreestima al analizar fuentes externas (papers + repos)** [CONFIRMADO x4]: cuando se delega análisis de una fuente externa (paper arXiv, repositorio GitHub, documentación de framework) al sub-agente Explore, el reporte propone implementar contribuciones sin filtrar por restricciones del sistema destino Y, peor, sin verificar qué de eso ya existe en el proyecto. **Dos modos de falla observados**:
|
|
300
|
+
|
|
301
|
+
**Modo A — papers académicos** (sesión 2026-04-25): sub-agente propuso 50h+ para implementar SPRT + Lyapunov + compositionality theorem del paper Bhardwaj 2026; costo real validado fue ~5h (solo Drift Score + Recovery Catalog). Causa: el Explore evalúa portabilidad técnica sin aplicar filtro de "datos disponibles" ni "infraestructura zero-deps". Evidencia: 3 papers analizados (evolver, Bhardwaj 2026, Zhang et al. 2026) con descarte sistemático ~70-90% del contenido propuesto.
|
|
302
|
+
|
|
303
|
+
**Modo B — repositorios externos** (sesión 2026-05-02 cosecha-temp/ EMAIA): sub-agente analizando `temp/docetl-main` (UC Berkeley, arXiv:2410.12189) afirmó dos cosas FALSAS verificables contra el código del proyecto destino: (1) "EMAIA ya usa LiteLLM" — verificación contra `pyproject.toml` + `grep -r litellm core/` mostró que EMAIA tiene clientes propios (`OllamaClient`/`NIMClient`/`vLLMClient`) y NO usa LiteLLM en absoluto; (2) recomendó portar Gleaning como patrón nuevo, cuando `core/learning/evaluator_optimizer.py` y `core/llm/quality_judge.py` YA implementan Anthropic Evaluator-Optimizer + LLM-as-judge. El fix real era WIRING (~30 LOC), no porting. Costo "MEDIO" del agente quedó como BAJO real. Causa: el Explore evalúa el repo externo en aislamiento, sin leer paths del proyecto destino para verificar qué mecanismos ya existen.
|
|
304
|
+
|
|
305
|
+
**Solución unificada — aplicar filtros antes de aceptar propuesta del Explore**:
|
|
306
|
+
|
|
307
|
+
*Para papers académicos (Modo A)*: 3 filtros críticos: (1) ¿requiere SMT solver / SPRT / Lyapunov / DTMC / formal verification? → descartar (rompe zero-deps); (2) ¿requiere N>100 sesiones de campo para validación estadística? → descartar; (3) ¿genera valor accionable HOY o solo elegancia matemática? → solo implementar si HOY.
|
|
308
|
+
|
|
309
|
+
*Para repos externos (Modo B)*: 4 filtros críticos: (1) ¿qué % es teoría/código no-portable vs portable? — si >70% no-portable, recortar; (2) **¿la propuesta reescribe mecanismos existentes en el proyecto destino?** — VERIFICAR con `Grep`/`Read` 2-3 afirmaciones concretas del agente contra el código real ANTES de aceptar (ej: agente dice "X usa Y" → `grep -l Y` para confirmar); (3) ¿LOC nuevas estimadas vs reutilizar lo existente? — si la propuesta supera 500 LOC para un solo patrón, hay sobre-ingeniería; (4) ¿el alcance reducido cubre 80% del valor? — Pareto: identificar el patrón mínimo que captura la mayor parte del beneficio. **Patrón de validación obligatorio**: extraer 2-3 afirmaciones factuales del reporte del Explore (ej: "X usa LiteLLM", "no existe Gleaning en el proyecto") y verificar cada una con `Grep`/`Read` antes de aceptar el plan.
|
|
300
310
|
- **Hooks de calidad pre-commit bloquean fixtures de tests como falsos positivos**: el hook `calidad-pre-commit.js` aplica regex `\b(api_key|password|token|secret)\s*[=:]\s*["'][^"'\s]{4,}["']` que matchea fixtures legítimos en archivos de test. Caso real: test que valida que la función `sanitizar()` redacta `api_key="abc12345xyz"` se bloquea. Causa: el hook no distingue contexto de test vs producción. Solución: en archivos de test, construir fixtures con concatenación de strings (`'api' + '_key'`, `'pass' + 'word'`) o agregar marcador placeholder reconocido por el hook (`fake_`, `dummy_`, `placeholder`, `example`, `os.environ`). NUNCA bypassear el hook con `--no-verify` — el detector cumple su función; ajustar el fixture es lo correcto.
|
|
301
311
|
|
|
302
312
|
## Anti-patrones del proceso de extracción
|
|
@@ -5,7 +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.1.
|
|
8
|
+
version: "1.1.2"
|
|
9
|
+
evolved: true
|
|
10
|
+
evolved-from: "1.1.1"
|
|
11
|
+
evolved-at: "2026-05-04"
|
|
12
|
+
evolved-by: "aprender"
|
|
13
|
+
evolved-note: "Fix lógica del gotcha endswith asyncpg: el formato 'INSERT oid N' tiene 3 partes (no 2), len(partes) == 2 fallaba para INSERT. Solución correcta: int(partes[-1]) — siempre el count, sea con o sin oid."
|
|
9
14
|
herramientasPermitidas: [Read]
|
|
10
15
|
exclusiones:
|
|
11
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."
|
|
@@ -210,6 +215,10 @@ class Factura(Base):
|
|
|
210
215
|
- **El endpoint GET hace `db.commit()` y el test pasa, pero en producción los datos se modifican inesperadamente**: un GET que comitea no es idempotente — herramientas de monitoreo, crawlers de SEO o retries del cliente pueden ejecutar el GET múltiples veces. Causa: `db.commit()` en un GET activa transacciones que modifican estado. Solución: según la regla del skill, los endpoints GET NUNCA deben hacer `db.commit()` — si el endpoint necesita registrar que se accedió al recurso, usar una tarea async en background con `BackgroundTasks`.
|
|
211
216
|
- **Pydantic v2 `model_validator(mode='before')` silencia errores de tipo al recibir None donde se espera un dict**: Pydantic v2 convierte `None` a `{}` en algunos contextos de validación `before`, produciendo un modelo con todos los campos como `None` en lugar de fallar con `ValidationError`. Causa: el `mode='before'` recibe el valor crudo antes de la coerción de tipos; si el validator retorna el valor sin verificar, Pydantic intenta instanciar el modelo con datos inválidos. Solución: en el `model_validator(mode='before')`, verificar explícitamente que el valor no es `None` antes de procesarlo: `if values is None: raise ValueError("...")`.
|
|
212
217
|
- **Baseline Alembic con `create_all()` genera deuda estructural en toda la cadena de migraciones posterior**: si el baseline `0001_initial.py` usa `Base.metadata.create_all(bind=op.get_bind())` en lugar de `op.create_table(...)` explícito, cada migración posterior (0002, 0003, ...) debe asumir que el estado real del schema puede diverger del árbol de modelos declarado y volverse idempotente con helpers tipo `_column_if_missing`, `_index_if_missing`. Causa: `create_all` refleja el estado actual de los modelos Python, no un snapshot explícito del schema — si el schema de producción diverge (índices agregados a mano, columnas con defaults distintos), las migraciones posteriores fallan con errores de "already exists" o no ejecutan acciones que asumen que el estado previo era el declarado. Solución: **nunca** usar `create_all()` en migraciones de Alembic. Baseline debe tener `op.create_table(...)` explícito por cada tabla del schema inicial. Si el proyecto ya tiene esta deuda, dos opciones: (a) migraciones posteriores 100% idempotentes con `IF NOT EXISTS` y helpers, (b) regenerar baseline con `alembic revision --autogenerate` tras un `alembic stamp head` a una base limpia y squash del historial.
|
|
218
|
+
- **`tag.endswith("1")` para parsear count de asyncpg DELETE/UPDATE matchea falsos positivos**: `conn.execute("DELETE FROM ...")` retorna un string `"DELETE N"` donde N es el número de filas afectadas. Usar `tag.endswith("1")` para verificar "exactamente 1 fila afectada" matchea **incorrectamente** `"DELETE 10"`, `"DELETE 11"`, `"DELETE 21"`, `"DELETE 100"`. Aunque el query actual sea `WHERE id = $1` (PK, máximo 1 fila), un refactor futuro a `WHERE programa_id = $1` haría que el método retornara True silenciosamente para 10+ filas. Causa: `endswith` opera sobre sufijo textual, no parsea el número. **El formato del tag varía por comando**: `"UPDATE N"` y `"DELETE N"` tienen 2 partes, pero `"INSERT oid N"` tiene 3 partes (asyncpg/PostgreSQL siempre incluye el oid en INSERT, generalmente `0` en tablas modernas sin OIDs). Usar `len(partes) == 2` falla para INSERT. Solución portable que cubre los tres casos: leer el **último** componente, que siempre es el count: `partes = tag.split(); afectadas = int(partes[-1]) if len(partes) >= 2 and partes[-1].isdigit() else 0`. Para verificar "exactamente 1": `afectadas == 1`. Aplicable a UPDATE, DELETE e INSERT por igual.
|
|
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
|
+
- **`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
|
+
- **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.
|
|
213
222
|
|
|
214
223
|
## Referencias especializadas
|
|
215
224
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{
|
|
2
|
-
"SKILL.md": {
|
|
3
|
-
"evolved": true,
|
|
4
|
-
"evolvedFrom": "
|
|
5
|
-
"evolvedAt": "2026-
|
|
6
|
-
"evolvedBy": "aprender",
|
|
7
|
-
"evolvedNote": "
|
|
8
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"SKILL.md": {
|
|
3
|
+
"evolved": true,
|
|
4
|
+
"evolvedFrom": "1.1.0",
|
|
5
|
+
"evolvedAt": "2026-05-02",
|
|
6
|
+
"evolvedBy": "aprender",
|
|
7
|
+
"evolvedNote": "Gotcha nueva: try/catch require para módulos opcionales (15 archivos migrados)"
|
|
8
|
+
}
|
|
9
9
|
}
|
|
@@ -4,12 +4,12 @@ description: >
|
|
|
4
4
|
Patrones de manejo de errores en Python y TypeScript. Jerarquía de excepciones,
|
|
5
5
|
excepciones personalizadas, códigos de error, logging estructurado, error boundaries,
|
|
6
6
|
degradación elegante, patrones de reintento y circuit breakers.
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.2.0"
|
|
8
8
|
evolved: true
|
|
9
|
-
evolved-from: "1.
|
|
10
|
-
evolved-at: "2026-04
|
|
9
|
+
evolved-from: "1.1.1"
|
|
10
|
+
evolved-at: "2026-05-04"
|
|
11
11
|
evolved-by: "aprender"
|
|
12
|
-
evolved-note: "
|
|
12
|
+
evolved-note: "+sección anti-patrón except Exception con isinstance interno — usar excepts planos por tipo (sync desde global tras sesión SIGM Fase 5b). Preserva gotcha try/catch require de v1.1.1."
|
|
13
13
|
herramientasPermitidas: [Read]
|
|
14
14
|
exclusiones:
|
|
15
15
|
- "No cargar para monitoreo y alertas en producción (Prometheus, Grafana, PagerDuty) — para observabilidad cargar `monitoring-alertas` o `sre-patrones`."
|
|
@@ -76,6 +76,50 @@ class ErrorExterno(ErrorAplicacion):
|
|
|
76
76
|
|
|
77
77
|
---
|
|
78
78
|
|
|
79
|
+
## Anti-patrón: `except Exception` con `isinstance` interno
|
|
80
|
+
|
|
81
|
+
**Problema**: capturar todo con `except Exception` y luego despachar por tipo con
|
|
82
|
+
`isinstance` enmascara excepciones futuras del módulo y dificulta seguir el flujo
|
|
83
|
+
de control.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# MAL — except genérico con isinstance interno
|
|
87
|
+
try:
|
|
88
|
+
registro = await ev_svc.subir_evidencia(...)
|
|
89
|
+
except (MIMENoPermitidoError, TamanoExcedidoError) as exc:
|
|
90
|
+
raise HTTPException(422, detail=...) from exc
|
|
91
|
+
except Exception as exc: # ← captura TODO lo que no caía arriba
|
|
92
|
+
if isinstance(exc, ErrorEvidencia):
|
|
93
|
+
raise HTTPException(502, detail="MinIO error") from exc
|
|
94
|
+
raise # cualquier otra excepción se re-lanza
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Problemas:
|
|
98
|
+
1. Si en el futuro `ev_svc` lanza una nueva excepción `EvidenciaCorruptaError` que debería
|
|
99
|
+
ser 422, queda capturada por `except Exception` y se re-lanza como 500 silenciosamente.
|
|
100
|
+
2. La intención del código no es clara: ¿qué tipos esperamos? ¿qué hace fallback?
|
|
101
|
+
3. Auditores de seguridad no pueden verificar fácilmente qué excepciones están manejadas.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# BIEN — except plano por tipo, sin isinstance interno
|
|
105
|
+
try:
|
|
106
|
+
registro = await ev_svc.subir_evidencia(...)
|
|
107
|
+
except (MIMENoPermitidoError, TamanoExcedidoError) as exc:
|
|
108
|
+
raise HTTPException(422, detail=str(exc)) from exc
|
|
109
|
+
except ErrorEvidencia as exc:
|
|
110
|
+
logger.exception("Error MinIO al subir evidencia")
|
|
111
|
+
raise HTTPException(502, detail="Error genérico al cliente") from exc
|
|
112
|
+
# Cualquier otra excepción se propaga al handler global — explícito.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Reglas:
|
|
116
|
+
- Un `except` por tipo (o tupla de tipos relacionados) que requiera tratamiento distinto.
|
|
117
|
+
- NO usar `except Exception` salvo en handlers globales explícitos (FastAPI exception_handler).
|
|
118
|
+
- Si necesitas distinguir múltiples tipos en un mismo handler, usa `except (A, B, C)` no
|
|
119
|
+
`except Exception` + `isinstance`.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
79
123
|
## Python — Encadenamiento de Excepciones
|
|
80
124
|
|
|
81
125
|
SIEMPRE usar `raise ... from exc` para preservar la cadena de errores:
|
|
@@ -238,6 +282,21 @@ function debeVerificar() {
|
|
|
238
282
|
|
|
239
283
|
Aplica a: health checks periódicos, verificación de nuevas versiones, consultas de cuota/rate-limit de APIs externas, chequeos de certificados, polling de colas. La línea divisoria es "¿el resultado anterior fue información real o fue ausencia de información?".
|
|
240
284
|
|
|
285
|
+
**Patrón `try { require } catch` para módulos opcionales en Node con fallback noop**: un hook o lib en `hooks/lib/` requiere de otra lib del mismo directorio (ej. `atomic-write.js`) que puede o no existir según la versión instalada del sistema o si el archivo se ejecuta standalone (CLI ad-hoc, test aislado). Importar con `require('./atomic-write')` directo lanza `MODULE_NOT_FOUND` y el hook crashea, bloqueando cualquier operación que lo invoque. Causa: `require` es síncrono y propaga la excepción al caller. Fix: envolver el require en try/catch y proveer un fallback que cumpla el mismo contrato de la dependencia, sin garantías adicionales (atomicidad, persistencia, etc.):
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
let atomicWriteSync, atomicWriteJSON;
|
|
289
|
+
try {
|
|
290
|
+
({ atomicWriteSync, atomicWriteJSON } = require('./atomic-write'));
|
|
291
|
+
} catch {
|
|
292
|
+
// Fallback no-atómico — funciona pero pierde la garantía de write atómico
|
|
293
|
+
atomicWriteSync = (p, c, e) => fs.writeFileSync(p, c, e);
|
|
294
|
+
atomicWriteJSON = (p, o) => fs.writeFileSync(p, JSON.stringify(o, null, 2), 'utf8');
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Aplica a: módulos en `hooks/lib/` que se cargan desde otros hooks pero también pueden ejecutarse vía `node -e` o tests aislados, libs que dependen de otras libs del mismo paquete pero queremos que el lib funcione si alguien la copia sola, scripts de mantenimiento que viven en `scripts/` y referencian utilidades de `hooks/lib/`. NO aplica a dependencias del package.json (esas SÍ deben fallar si no están — son contratos firmes); aplica solo a dependencias internas del propio repositorio cuya disponibilidad no es garantizada en todos los contextos de ejecución. Evidencia: 15 archivos críticos de `hooks/` y `scripts/` migraron a este patrón en sesión 2026-05-02.
|
|
299
|
+
|
|
241
300
|
## Gotcha — Retry con cliente HTTP interno: propagar el timeout
|
|
242
301
|
|
|
243
302
|
### NUNCA: wrapper de retry que promete N segundos sobre cliente HTTP con timeout M << N
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: patrones-python
|
|
3
3
|
description: Idiomas pythonicos, PEP 8, type hints modernos, dataclasses, async/await, context managers, decorators y generators. Patrones de código limpio en Python.
|
|
4
|
-
version: "1.3.
|
|
4
|
+
version: "1.3.1"
|
|
5
5
|
evolved: true
|
|
6
|
-
evolved-from: "1.
|
|
7
|
-
evolved-at: "2026-04
|
|
6
|
+
evolved-from: "1.3.0"
|
|
7
|
+
evolved-at: "2026-05-04"
|
|
8
8
|
evolved-by: "aprender"
|
|
9
|
-
evolved-note: "
|
|
9
|
+
evolved-note: "+1 gotcha: assert se elimina con PYTHONOPTIMIZE=1 — usar if/raise para invariantes (sync desde global tras sesión SIGM Fase 5b)"
|
|
10
10
|
herramientasPermitidas: [Read, Glob, Grep]
|
|
11
11
|
exclusiones:
|
|
12
12
|
- "No cargar para patrones de un framework específico (FastAPI, Django, Celery) — los idiomas generales de este skill aplican, pero los patrones de framework tienen restricciones adicionales; cargar el skill del framework correspondiente."
|
|
@@ -204,6 +204,7 @@ ver [recursos/referencia-completa.md](recursos/referencia-completa.md).
|
|
|
204
204
|
- **Decorator que usa `functools.wraps` pero no preserva type hints si la función decorada tiene anotaciones genéricas**: el `@wraps` copia `__wrapped__`, `__doc__` y `__name__`, pero el tipo de retorno inferido por `mypy` es el del wrapper, no el del wrapped. Causa: `functools.wraps` no puede preservar el tipado estático del wrapped — mypy ve el tipo del wrapper `Callable[..., Any]`. Solución: usar `TypeVar` y tipado genérico en el decorator con `ParamSpec` (Python 3.10+): `P = ParamSpec('P'); T = TypeVar('T')` y tipar el wrapper como `Callable[P, T]`.
|
|
205
205
|
- **`__slots__` en clase Python produce `TypeError: multiple bases have instance lay-out conflict`** al heredar de otra clase con `__slots__`: las subclases con `__slots__` requieren que todos los ancestros también tengan `__slots__`, o que el ancestro directo sea `object`. Causa: si `ClaseBase` no tiene `__slots__`, tiene un `__dict__` implícito; si `ClaseHija` tiene `__slots__`, hay conflicto de layout de memoria. Solución: o agregar `__slots__ = ()` vacío a la clase base, o eliminar `__slots__` de la subclase — no mezclar clases con y sin `__slots__` en la misma jerarquía.
|
|
206
206
|
- **`property` setter que modifica un campo privado no refleja el cambio en `__repr__` generado por dataclass**: el `@property` en un dataclass crea un campo de clase que conflictúa con el campo de instancia del dataclass. Causa: `@dataclass` genera `__repr__` basado en los campos declarados en `__init__` — si el setter modifica un atributo con nombre diferente (ej: `_valor`), `__repr__` muestra el campo original sin la modificación. Solución: usar `field(init=False, repr=False)` para el campo interno y exponer solo la `property` en la interfaz pública.
|
|
207
|
+
- **`assert` no es guard de invariantes en producción con `PYTHONOPTIMIZE=1` o `python -O`**: el bytecode optimizado **elimina** todos los `assert` del módulo, por lo que `assert x is not None; return x` puede retornar `None` violando el contrato `-> dict` en producción aunque pase tests en desarrollo. El test runner por defecto NO usa `-O`, por lo que el bug es invisible hasta que alguien despliega con `PYTHONOPTIMIZE=1` (configuración común para reducir memoria en imágenes Docker production). Causa: `assert` está documentado en Python como herramienta de **debugging**, no de validación. Solución: para invariantes que DEBEN cumplirse en producción, usar guard explícito con raise: `if x is None: raise HTTPException(500, "Invariante violado")` o `if x is None: raise RuntimeError(...)`. Reservar `assert` solo para tests, scripts, o pre-condiciones triviales en código de desarrollo. Regla rápida: si el assert protege un caso que activa una respuesta del usuario o un side-effect, NO es assert — es validación y debe ser `if/raise`.
|
|
207
208
|
|
|
208
209
|
---
|
|
209
210
|
|