@saulwade/swl-ses 2.3.1 → 2.4.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 +242 -240
- package/README.md +597 -597
- package/agentes/_intent-spec.md +73 -73
- package/agentes/_propose-step.md +90 -90
- package/agentes/accesibilidad-wcag-swl.md +690 -690
- package/agentes/arquitecto-swl.md +267 -267
- package/agentes/auto-evolucion-swl.md +908 -908
- package/agentes/backend-api-swl.md +472 -472
- package/agentes/backend-csharp-swl.md +420 -420
- package/agentes/backend-go-swl.md +390 -390
- package/agentes/backend-java-swl.md +281 -281
- package/agentes/backend-node-swl.md +479 -479
- package/agentes/backend-python-swl.md +605 -605
- package/agentes/backend-rust-swl.md +364 -364
- package/agentes/backend-workers-swl.md +482 -482
- package/agentes/cloud-infra-swl.md +509 -509
- package/agentes/consolidador-swl.md +541 -541
- package/agentes/datos-swl.md +605 -605
- package/agentes/depurador-swl.md +352 -352
- package/agentes/devops-ci-swl.md +400 -400
- package/agentes/disenador-ui-swl.md +569 -569
- package/agentes/documentador-swl.md +345 -345
- package/agentes/frontend-angular-swl.md +621 -621
- package/agentes/frontend-css-swl.md +716 -716
- package/agentes/frontend-react-swl.md +692 -692
- package/agentes/frontend-swl.md +496 -496
- package/agentes/frontend-tailwind-swl.md +826 -826
- package/agentes/gh-fix-ci-swl.md +275 -275
- package/agentes/implementador-swl.md +328 -328
- package/agentes/investigador-swl.md +432 -432
- package/agentes/investigador-ux-swl.md +505 -505
- package/agentes/llm-apps-swl.md +278 -278
- package/agentes/migrador-swl.md +442 -442
- package/agentes/mobile-android-swl.md +511 -511
- package/agentes/mobile-cross-swl.md +541 -541
- package/agentes/mobile-ios-swl.md +502 -502
- package/agentes/mobile-testing-swl.md +302 -302
- package/agentes/nemesis-auditor-swl.md +285 -285
- package/agentes/notificador-swl.md +918 -918
- package/agentes/observabilidad-swl.md +438 -438
- package/agentes/orquestador-swl.md +1053 -1026
- package/agentes/pagos-swl.md +310 -310
- package/agentes/perfilador-usuario-swl.md +321 -321
- package/agentes/planificador-swl.md +399 -399
- package/agentes/producto-prd-swl.md +589 -589
- package/agentes/red-team-swl.md +218 -218
- package/agentes/release-manager-swl.md +590 -590
- package/agentes/rendimiento-swl.md +713 -713
- package/agentes/resolutor-build-swl.md +245 -245
- package/agentes/revisor-angular-swl.md +278 -278
- package/agentes/revisor-codigo-swl.md +538 -505
- package/agentes/revisor-csharp-swl.md +264 -264
- package/agentes/revisor-go-swl.md +259 -259
- package/agentes/revisor-java-swl.md +257 -257
- package/agentes/revisor-kotlin-swl.md +273 -273
- package/agentes/revisor-nextjs-swl.md +281 -281
- package/agentes/revisor-php-swl.md +271 -271
- package/agentes/revisor-react-swl.md +278 -278
- package/agentes/revisor-rust-swl.md +346 -346
- package/agentes/revisor-seguridad-swl.md +399 -399
- package/agentes/revisor-swift-swl.md +268 -268
- package/agentes/revisor-typescript-swl.md +346 -346
- package/agentes/sre-swl.md +291 -291
- package/agentes/tdd-qa-swl.md +393 -393
- package/comandos/swl/contexto.md +0 -2
- package/comandos/swl/deuda-codigo.md +97 -0
- package/habilidades/angular-moderno/SKILL.md +5 -1
- package/habilidades/contenedores-docker/SKILL.md +3 -1
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/harness-claude-code/SKILL.md +5 -4
- package/habilidades/harness-claude-code/recursos/disciplina-harness-regla.md +6 -5
- package/habilidades/legacy-code-rescue/SKILL.md +2 -1
- package/habilidades/meta-skills-estandar/SKILL.md +1 -1
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +2 -2
- package/habilidades/postgresql-experto/SKILL.md +3 -1
- package/habilidades/prevencion-sobreingenieria/SKILL.md +70 -92
- package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -0
- package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -0
- package/habilidades/tdd-workflow/SKILL.md +3 -1
- package/hooks/audit-trail.js +4 -4
- package/hooks/calidad-pre-commit.js +15 -11
- package/hooks/captura-acciones-post.js +3 -2
- package/hooks/captura-acciones-session.js +3 -2
- package/hooks/contexto-iteracion.js +2 -1
- package/hooks/contexto-subagente.js +68 -0
- package/hooks/guardrail-modelo.js +13 -3
- package/hooks/inyeccion-contexto.js +12 -12
- package/hooks/registro-turnos.js +5 -4
- package/hooks/spec-gate.js +2 -1
- package/hooks/sugerir-contribuir.js +2 -1
- package/hooks/sugerir-regenerar-inventario.js +3 -2
- package/hooks/tdd-gate.js +2 -1
- package/llms.txt +3 -3
- package/manifiestos/canonical-hashes.json +995 -10
- package/manifiestos/hook-profiles.json +0 -2
- package/manifiestos/hooks-config.json +469 -477
- package/manifiestos/invariantes-criticos.json +30 -0
- package/manifiestos/modulos.json +1390 -1390
- package/manifiestos/skills-lock.json +24 -24
- package/package.json +93 -93
- package/plugin.json +369 -371
- package/reglas/arreglar-al-detectar.md +16 -0
- package/reglas/pruebas.md +7 -3
- package/schemas/agent-frontmatter.schema.json +3 -1
- package/scripts/actualizar.js +253 -254
- package/scripts/doctor.js +25 -17
- package/scripts/instalador.js +4 -1
- package/scripts/lib/detectar-runtime.js +58 -0
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/hooks-settings.js +40 -3
- package/scripts/lib/preservar-usuario.js +39 -5
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/tui/pantallas/inspect.js +175 -173
- package/scripts/tui/pantallas/uninstall-wizard.js +210 -208
- package/scripts/tui/pantallas/update-wizard.js +234 -232
- package/scripts/tui/pantallas/welcome.js +189 -187
- package/scripts/validar.js +1 -1
- package/scripts/verificar-release.js +51 -0
- package/hooks/lib/context-compressor.js +0 -657
- package/hooks/linea-estado.js +0 -328
- package/hooks/monitor-contexto.js +0 -373
|
@@ -150,8 +150,8 @@ porcentaje del costo viene de sub-agentes vs. trabajo del padre.
|
|
|
150
150
|
- El skill se invoca igual: `Skill("nombre")`. La diferencia es interna
|
|
151
151
|
(el harness Anthropic decide si lo carga al padre o lo ejecuta como
|
|
152
152
|
sub-agente según el frontmatter).
|
|
153
|
-
- Hooks SWL siguen aplicando: `
|
|
154
|
-
|
|
153
|
+
- Hooks SWL siguen aplicando: `audit-trail.js` y la telemetría de agentes
|
|
154
|
+
registran las invocaciones del sub-agente.
|
|
155
155
|
|
|
156
156
|
## Referencias
|
|
157
157
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: postgresql-experto
|
|
3
3
|
description: PostgreSQL avanzado. JSONB, arrays, tipos personalizados, búsqueda de texto completo, window functions, CTEs recursivos, Row Level Security y funciones almacenadas.
|
|
4
|
-
version: "1.2.
|
|
4
|
+
version: "1.2.1"
|
|
5
5
|
herramientasPermitidas: [Read, Grep]
|
|
6
6
|
exclusiones:
|
|
7
7
|
- "No cargar para optimización de queries SQL (EXPLAIN ANALYZE, índices, partitioning) — para optimización cargar `sql-optimizacion`."
|
|
@@ -142,6 +142,8 @@ ver [recursos/referencia-completa.md](recursos/referencia-completa.md).
|
|
|
142
142
|
|
|
143
143
|
## Gotchas / Errores comunes no obvios
|
|
144
144
|
|
|
145
|
+
**Bajo `lc_ctype=C` (default de `postgres:alpine`), `upper()`/`lower()` son operaciones puramente ASCII — no transforman caracteres acentuados**: `upper('auditoría')` devuelve `AUDITORíA` (la `í` queda intacta), así que una normalización de búsqueda que ejecute `upper()` ANTES de remover acentos con `translate()` produce strings que nunca matchean — la búsqueda falla silenciosamente devolviendo 0 resultados. Causa: en locale `C` las funciones de case solo mapean `[a-z]↔[A-Z]`. Fix: ejecutar `translate()` (remover acentos) SIEMPRE antes de `upper()`/`lower()`, nunca después; y aplicar el mismo orden en ambos lados de la comparación (columna y término de búsqueda). Origen: SIGAF 2026-07-02 — filtros de menú completamente rotos en `postgres:alpine`.
|
|
146
|
+
|
|
145
147
|
**`SET LOCAL app.empresa_id` para RLS no persiste fuera de una transacción**: `SET LOCAL` establece la variable solo para la transacción actual — si se ejecuta fuera de `BEGIN/COMMIT`, tiene el mismo efecto que `SET` (sesión completa). En aplicaciones con pooling (PgBouncer en transaction mode), la sesión se reutiliza entre conexiones y la variable puede persistir de una petición anterior. Causa: el pool de conexiones reutiliza sessions sin limpiar el estado. Fix: usar SIEMPRE `SET LOCAL` dentro de una transacción explícita (`async with session.begin()`) y verificar que el pool esté en transaction mode.
|
|
146
148
|
|
|
147
149
|
**RLS NO se aplica a superusuarios — verificar siempre con rol app-only**: `FORCE ROW LEVEL SECURITY` hace que las policies apliquen al OWNER de la tabla, pero los superusuarios SIEMPRE bypassean RLS. En imágenes oficiales (`postgis/postgis`, `postgres`), el rol que se crea por defecto (con `POSTGRES_USER`) es superusuario. Caso real: `SET app.tenant_id = COAT` mostraba 20 predios del tenant MINA porque la query corría como `sigm` superuser. Fix: para verificar aislamiento RLS o exponer al backend de producción, crear un rol app-only sin BYPASSRLS:
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
name: prevencion-sobreingenieria
|
|
3
3
|
description: >
|
|
4
4
|
Previene sobre-ingeniería y código innecesario en generación asistida por IA.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
Escalera de decisión de 7 peldaños (YAGNI → reusar → stdlib → nativo →
|
|
6
|
+
dependencia instalada → una línea → mínimo que funciona) con guardas
|
|
7
|
+
intocables de seguridad, más cuatro principios: pensar antes de codificar,
|
|
8
|
+
simplicidad primero, cambios quirúrgicos y ejecución orientada a metas
|
|
9
|
+
verificables. Cargar antes de implementar features, refactorizar código o
|
|
10
|
+
resolver bugs — especialmente cuando el agente tiende a agregar abstracciones
|
|
11
|
+
especulativas, reinventar lo que la plataforma ya tiene, o tocar código fuera
|
|
12
|
+
del alcance solicitado. Incluye la convención de marcador `simplificado:`
|
|
13
|
+
(techo + trigger) cosechable por /swl:deuda-codigo.
|
|
14
|
+
version: "1.3.0"
|
|
11
15
|
herramientasPermitidas: [Read, Grep]
|
|
12
16
|
toolsRequeridos: Read, Grep, Glob
|
|
13
17
|
exclusiones:
|
|
@@ -19,12 +23,11 @@ evolvable: true # default para skill estandar
|
|
|
19
23
|
---
|
|
20
24
|
# Prevención de sobre-ingeniería
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
Principios de Andrej Karpathy sobre errores comunes de LLMs al generar
|
|
27
|
+
código + escalera de decisión de ponytail, adaptados al ecosistema SWL.
|
|
24
28
|
|
|
25
29
|
> **Para 9 ejemplos MAL→BIEN lado a lado con código real**, ver
|
|
26
|
-
> [recursos/EXAMPLES.md](recursos/EXAMPLES.md)
|
|
27
|
-
> escenario operativo concreto de SWL (qué agente debe citarlo, en qué flujo).
|
|
30
|
+
> [recursos/EXAMPLES.md](recursos/EXAMPLES.md) — cada uno mapeado a su escenario SWL.
|
|
28
31
|
|
|
29
32
|
## Cuándo cargar
|
|
30
33
|
|
|
@@ -34,6 +37,59 @@ al generar código, adaptado al ecosistema SWL.
|
|
|
34
37
|
- Cuando el usuario reporta que el agente "hace demasiado" o "cambia cosas que no debía"
|
|
35
38
|
- En revisiones de código donde se detecta complejidad innecesaria
|
|
36
39
|
|
|
40
|
+
## Escalera de decisión — antes de escribir código
|
|
41
|
+
|
|
42
|
+
Detenerse en el **primer peldaño que aplique** (adaptada de
|
|
43
|
+
[ponytail](https://github.com/DietrichGebert/ponytail), MIT — benchmark
|
|
44
|
+
agéntico: −54% LOC, −20% costo, 100% seguridad vs el mismo agente sin disciplina):
|
|
45
|
+
|
|
46
|
+
1. **¿Necesita existir?** Necesidad especulativa = omitir y decirlo en una línea (YAGNI).
|
|
47
|
+
2. **¿Ya existe en este codebase?** Helper, util, tipo o patrón que ya vive aquí
|
|
48
|
+
→ reusarlo. Reimplementar lo que está a unos archivos es el desperdicio más común.
|
|
49
|
+
3. **¿La stdlib lo hace?** Usarla.
|
|
50
|
+
4. **¿Una feature nativa de la plataforma lo cubre?** `<input type="date">`
|
|
51
|
+
sobre una lib de picker, CSS sobre JS, constraint de BD sobre código de app.
|
|
52
|
+
Tablas de referencia: [recursos/soluciones-nativas.md](recursos/soluciones-nativas.md).
|
|
53
|
+
5. **¿Una dependencia ya instalada lo resuelve?** Usarla. NUNCA agregar una
|
|
54
|
+
nueva para lo que unas líneas cubren (refuerza `usar-context7.md`).
|
|
55
|
+
6. **¿Cabe en una línea?** Una línea.
|
|
56
|
+
7. **Solo entonces**: el mínimo código que funciona.
|
|
57
|
+
|
|
58
|
+
La escalera corre DESPUÉS de entender el problema, no en su lugar: leer la
|
|
59
|
+
tarea y el código que toca, trazar el flujo real, y entonces subir. Un diff
|
|
60
|
+
pequeño que no se entiende no es eficiencia — es un segundo bug.
|
|
61
|
+
|
|
62
|
+
### Guardas intocables (nunca simplificar)
|
|
63
|
+
|
|
64
|
+
Validación de inputs en fronteras de confianza (trust boundaries) · manejo de
|
|
65
|
+
errores que previene pérdida de datos · medidas de seguridad · básicos de
|
|
66
|
+
accesibilidad · lo que el usuario pidió explícitamente (si insiste en la
|
|
67
|
+
versión completa, construirla sin re-debatir).
|
|
68
|
+
|
|
69
|
+
Entre dos opciones stdlib del mismo tamaño, elegir la correcta en edge cases:
|
|
70
|
+
menos código no significa el algoritmo más frágil. La lógica no trivial deja
|
|
71
|
+
UN check ejecutable mínimo (test pequeño o assert de auto-verificación); los
|
|
72
|
+
one-liners triviales no lo requieren — YAGNI aplica también a los tests.
|
|
73
|
+
|
|
74
|
+
### Bug fix = causa raíz, no síntoma
|
|
75
|
+
|
|
76
|
+
El reporte nombra un síntoma. Antes de editar, grep de TODOS los callers de la
|
|
77
|
+
función a tocar: un guard en la función compartida es un diff menor que un guard
|
|
78
|
+
por caller — parchar solo la ruta del ticket deja rotos a los callers hermanos.
|
|
79
|
+
|
|
80
|
+
### Marcador de simplificación deliberada
|
|
81
|
+
|
|
82
|
+
Toda simplificación intencional con techo conocido se marca con un comentario
|
|
83
|
+
`simplificado:` que nombra el techo y el trigger de upgrade:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# simplificado: lock global, pasar a locks por cuenta si el throughput importa
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`/swl:deuda-codigo` cosecha los marcadores hacia `.planning/DEUDA-TECNICA.md`.
|
|
90
|
+
Un marcador **sin trigger** es deuda silenciosa (prohibida por
|
|
91
|
+
`arreglar-al-detectar.md`) — siempre nombrar la condición observable de upgrade.
|
|
92
|
+
|
|
37
93
|
## Principio 1 — Pensar antes de codificar
|
|
38
94
|
|
|
39
95
|
Antes de escribir una sola línea, explicitar supuestos y resolver ambigüedades.
|
|
@@ -236,87 +292,9 @@ de código requiere análisis profundo. La simplicidad también aplica al proces
|
|
|
236
292
|
- **Principio 1 (clarificar antes de implementar) aplicado a tareas que ya tienen spec completa**: el agente pregunta por decisiones que ya están documentadas en el PLAN.md aprobado. Causa: no leer la spec antes de preguntar. Solución: el Principio 1 aplica cuando hay ambigüedad real después de leer el PLAN.md, CLAUDE.md y los archivos relevantes — si la respuesta está en los documentos ya leídos, preguntar es sobreingeniería del proceso, no clarificación.
|
|
237
293
|
- **Criterio de 3 usos para abstracciones aplicado a tipos (interfaces/protocolos)**: el agente no crea una interfaz para un tipo que se usa en un solo lugar. Causa: confundir "3 usos para abstracciones de comportamiento" con "3 usos para tipos". Solución: el criterio de 3 usos aplica a factories, strategies, y patrones de comportamiento especulativos; una interfaz TypeScript o un Protocol Python que define un contrato usado una vez puede ser la solución correcta si el tipo es el boundary entre módulos.
|
|
238
294
|
|
|
239
|
-
---
|
|
240
|
-
|
|
241
295
|
## Variables residuales post-refactor son code smell
|
|
242
296
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
limpió. Se parecen a "variables temporales del desarrollador" pero delatan un
|
|
248
|
-
refactor incompleto — la lógica que las usaba se movió o eliminó, pero las
|
|
249
|
-
asignaciones quedaron.
|
|
250
|
-
|
|
251
|
-
### Por qué son peligrosas
|
|
252
|
-
|
|
253
|
-
- **Confunden al próximo lector**: asumirá que la variable se usa más abajo.
|
|
254
|
-
- **Inflan el diff cognitivo** del refactor real.
|
|
255
|
-
- **Ocultan bugs sutiles**: el valor asignado puede tener side effects (llamada
|
|
256
|
-
a función costosa o con efecto en estado) que ya no aporta nada al flujo.
|
|
257
|
-
- **Invalidan la métrica de cobertura**: líneas "cubiertas" por tests que en
|
|
258
|
-
realidad no afectan el resultado.
|
|
259
|
-
|
|
260
|
-
### Patrón operativo
|
|
261
|
-
|
|
262
|
-
Después de **cualquier refactor** que modifique el flujo de una función (extraer
|
|
263
|
-
método, inlinear función, cambiar el shape de un return), correr:
|
|
264
|
-
|
|
265
|
-
```bash
|
|
266
|
-
ruff check --select=F841 ruta/al/modulo.py
|
|
267
|
-
# o para todo el proyecto
|
|
268
|
-
ruff check --select=F841 .
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
Cada F841 detectado post-refactor es candidato 1 de 2:
|
|
272
|
-
|
|
273
|
-
1. **Variable muerta** (caso común post-refactor): eliminarla.
|
|
274
|
-
2. **Variable viva pero mal nombrada** (caso raro): si el valor se usa como
|
|
275
|
-
side effect intencional (ej: evaluar una propiedad para cachear), renombrar
|
|
276
|
-
con prefijo `_` y comentar el propósito.
|
|
277
|
-
|
|
278
|
-
### Anti-patrón típico
|
|
279
|
-
|
|
280
|
-
```python
|
|
281
|
-
# Antes del refactor: había un if que usaba exito_key
|
|
282
|
-
def procesar(data):
|
|
283
|
-
exito, exito_key = intentar(data)
|
|
284
|
-
if exito_key == "via_rapida":
|
|
285
|
-
return procesar_rapido(data)
|
|
286
|
-
return procesar_normal(data, exito_key)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
# Después de refactor que eliminó la rama "via_rapida"
|
|
290
|
-
def procesar(data):
|
|
291
|
-
exito, exito_key = intentar(data) # ← F841: exito_key asignado, no usado
|
|
292
|
-
return procesar_normal(data) # ← también exito residual
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### Patrón canónico
|
|
296
|
-
|
|
297
|
-
```python
|
|
298
|
-
# Post-refactor limpio
|
|
299
|
-
def procesar(data):
|
|
300
|
-
_ = intentar(data) # si el side effect de intentar() importa
|
|
301
|
-
return procesar_normal(data)
|
|
302
|
-
|
|
303
|
-
# o mejor: si ni siquiera el side effect importa
|
|
304
|
-
def procesar(data):
|
|
305
|
-
return procesar_normal(data)
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
### Reglas
|
|
309
|
-
|
|
310
|
-
- **Gate pre-commit**: agregar `ruff check --select=F841` al pre-commit hook del proyecto. No bloquea en un repo grande donde F841 preexiste, pero sí bloquea en los archivos modificados por el commit actual.
|
|
311
|
-
- **Revisión explícita post-refactor grande**: tras extraer 5+ métodos o mover 5+ bloques, correr `ruff --select=F401,F841` sobre los archivos tocados.
|
|
312
|
-
- **NO silenciar con `# noqa: F841` sin justificación** — la excepción legítima (side effect intencional con nombre `_`) es tan rara que vale la pena documentarla; silenciar sin comentario es ocultar deuda.
|
|
313
|
-
- **Incluir en checklist de PR**: "F841 limpio en archivos tocados" como item explícito.
|
|
314
|
-
|
|
315
|
-
### Aplicabilidad
|
|
316
|
-
|
|
317
|
-
Detectable en:
|
|
318
|
-
|
|
319
|
-
- Python: `ruff --select=F841`, `flake8 --select=F841`.
|
|
320
|
-
- TypeScript/JavaScript: `eslint --rule 'no-unused-vars: error'`.
|
|
321
|
-
- Go: el compilador lo hace obligatorio (`declared but not used`).
|
|
322
|
-
- Rust: warning `unused_variables`, promovido a error con `#![deny(unused_variables)]`.
|
|
297
|
+
Variables asignadas pero no usadas (F841) post-refactor delatan un refactor
|
|
298
|
+
incompleto: confunden al lector, ocultan side effects muertos e invalidan la
|
|
299
|
+
cobertura. Patrón operativo completo (gate pre-commit, anti-patrón, detección
|
|
300
|
+
por lenguaje): [recursos/variables-residuales-post-refactor.md](recursos/variables-residuales-post-refactor.md).
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Soluciones nativas de plataforma
|
|
2
|
+
|
|
3
|
+
Recurso de `prevencion-sobreingenieria` — peldaño 4 de la escalera de decisión.
|
|
4
|
+
La primera pregunta del ingeniero perezoso-eficiente: *¿la plataforma ya hace
|
|
5
|
+
esto?* Antes de instalar un paquete o escribir un componente, revisar aquí.
|
|
6
|
+
La plataforma viaja gratis con la app, no se rompe en updates, y la escribió
|
|
7
|
+
gente cuyo trabajo es exactamente ese problema.
|
|
8
|
+
|
|
9
|
+
Adaptado de [ponytail](https://github.com/DietrichGebert/ponytail)
|
|
10
|
+
`docs/platform-native.md` (MIT, DietrichGebert).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Elementos HTML
|
|
15
|
+
|
|
16
|
+
| Crees que necesitas | La plataforma ya tiene |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Lib de date picker | `<input type="date">` |
|
|
19
|
+
| Lib de time picker | `<input type="time">` |
|
|
20
|
+
| Lib de color picker | `<input type="color">` |
|
|
21
|
+
| Lib de range slider | `<input type="range">` |
|
|
22
|
+
| Componente de progress bar | `<progress value="70" max="100">` |
|
|
23
|
+
| Componente meter/gauge | `<meter value="0.7">` |
|
|
24
|
+
| Lib de modal/diálogo | `<dialog>` + `dialog.showModal()` |
|
|
25
|
+
| Componente acordeón/FAQ | `<details><summary>Título</summary>…</details>` |
|
|
26
|
+
| Lib de tooltips | atributo `title` + CSS `::before`/`::after` |
|
|
27
|
+
| Dropdown con búsqueda | `<input list="id"> <datalist id="id">` |
|
|
28
|
+
| Textarea auto-crecible | `field-sizing: content` (CSS) |
|
|
29
|
+
| Header sticky | `position: sticky; top: 0` (CSS) |
|
|
30
|
+
|
|
31
|
+
## Capacidades CSS (donde se suele usar JS)
|
|
32
|
+
|
|
33
|
+
| Crees que necesitas JS para | CSS ya tiene |
|
|
34
|
+
|---|---|
|
|
35
|
+
| Font size responsivo | `font-size: clamp(1rem, 2.5vw, 2rem)` |
|
|
36
|
+
| Espaciado fluido | `padding: clamp(1rem, 5vw, 3rem)` |
|
|
37
|
+
| Dark mode | `@media (prefers-color-scheme: dark)` |
|
|
38
|
+
| Reduced motion | `@media (prefers-reduced-motion: reduce)` |
|
|
39
|
+
| Layout responsivo sin breakpoints | `grid-template-columns: repeat(auto-fill, minmax(250px, 1fr))` |
|
|
40
|
+
| Responsive a nivel componente | `@container` queries |
|
|
41
|
+
| Design tokens / theming global | Custom properties (`--color-primario: #7c3aed`) |
|
|
42
|
+
| Smooth scroll | `scroll-behavior: smooth` |
|
|
43
|
+
| Carrusel con scroll-snap | `scroll-snap-type: x mandatory` + `scroll-snap-align: start` |
|
|
44
|
+
| Aspect ratio | `aspect-ratio: 16 / 9` |
|
|
45
|
+
| Truncar texto con elipsis | `overflow: hidden; text-overflow: ellipsis; white-space: nowrap` |
|
|
46
|
+
| Clamp multilínea | `-webkit-line-clamp: 3` |
|
|
47
|
+
| Aislamiento de estilos por capas | `@layer base, components, utilities` |
|
|
48
|
+
| Selectores anidados | CSS nesting nativo (sin preprocesador) |
|
|
49
|
+
| Selector de padre | `:has(input:checked)` |
|
|
50
|
+
|
|
51
|
+
## JavaScript / APIs del navegador
|
|
52
|
+
|
|
53
|
+
| Crees que necesitas | La plataforma ya tiene |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `query-string` / `qs` | `new URLSearchParams(location.search)` |
|
|
56
|
+
| `lodash.clonedeep` | `structuredClone(obj)` |
|
|
57
|
+
| `lodash.groupby` | `Object.groupBy(arr, fn)` |
|
|
58
|
+
| `numeral` / `accounting` | `new Intl.NumberFormat("es-MX", { style: "currency", currency: "MXN" })` |
|
|
59
|
+
| `date-fns` format | `new Intl.DateTimeFormat("es-MX", { dateStyle: "long" }).format(fecha)` |
|
|
60
|
+
| Tiempo relativo | `new Intl.RelativeTimeFormat("es", { numeric: "auto" }).format(-3, "day")` |
|
|
61
|
+
| Plurales i18n | `new Intl.PluralRules("es-MX").select(cantidad)` |
|
|
62
|
+
| `clipboard.js` | `navigator.clipboard.writeText(texto)` |
|
|
63
|
+
| `uuid` (v4) | `crypto.randomUUID()` |
|
|
64
|
+
| Lib de infinite scroll | `new IntersectionObserver(cb).observe(sentinela)` |
|
|
65
|
+
| Lib de resize listener | `new ResizeObserver(cb).observe(elemento)` |
|
|
66
|
+
| Watcher de mutaciones DOM | `new MutationObserver(cb).observe(el, opciones)` |
|
|
67
|
+
| Check de conectividad | `navigator.onLine` + eventos `online`/`offline` |
|
|
68
|
+
| Lib de share sheet | `navigator.share({ title, text, url })` |
|
|
69
|
+
| `store.js` / `localForage` (caso simple) | `localStorage.setItem(clave, JSON.stringify(valor))` |
|
|
70
|
+
| Abortar fetch por timeout | `AbortSignal.timeout(5000)` pasado a `fetch` |
|
|
71
|
+
| Event bus custom | `new EventTarget()` / `dispatchEvent(new CustomEvent("x", { detail }))` |
|
|
72
|
+
|
|
73
|
+
**Debounce en 3 líneas** (sin dependencia):
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
// simplificado: debounce de una sola instancia; lib si se necesitan N independientes
|
|
77
|
+
let t;
|
|
78
|
+
const debounce = (fn, ms) => (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Swift / SwiftUI
|
|
82
|
+
|
|
83
|
+
| Crees que necesitas | La plataforma ya tiene |
|
|
84
|
+
|---|---|
|
|
85
|
+
| Lib de date/time picker | `DatePicker` |
|
|
86
|
+
| Lib de color picker | `ColorPicker` |
|
|
87
|
+
| Search bar + filtrado | `.searchable(text:)` |
|
|
88
|
+
| Lib de pull-to-refresh | `.refreshable { }` |
|
|
89
|
+
| Swipe-to-delete / acciones de fila | `.swipeActions { }` |
|
|
90
|
+
| Carga async de imágenes + caché | `AsyncImage` |
|
|
91
|
+
| Lib de gráficas | Swift Charts (`import Charts`) |
|
|
92
|
+
| Render de Markdown | `Text(...)` markdown / `AttributedString(markdown:)` |
|
|
93
|
+
| Share sheet | `ShareLink` |
|
|
94
|
+
| Spinner de carga | `ProgressView()` |
|
|
95
|
+
| Photo picker | `PhotosPicker` |
|
|
96
|
+
| JSON (SwiftyJSON) | `Codable` + `JSONDecoder`/`JSONEncoder` |
|
|
97
|
+
| HTTP simple (Alamofire) | `URLSession` async/await |
|
|
98
|
+
| Crypto (CryptoSwift) | `CryptoKit` |
|
|
99
|
+
| Persistencia / ORM | `SwiftData`, o `@AppStorage` para key-values |
|
|
100
|
+
| Logging | `Logger` (`os.log`) |
|
|
101
|
+
|
|
102
|
+
## Stdlib de Node.js
|
|
103
|
+
|
|
104
|
+
| Crees que necesitas | Node ya tiene |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `mkdirp` / `make-dir` | `fs.mkdirSync(ruta, { recursive: true })` |
|
|
107
|
+
| `rimraf` | `fs.rmSync(ruta, { recursive: true, force: true })` |
|
|
108
|
+
| `uuid` (v4) | `crypto.randomUUID()` |
|
|
109
|
+
| `object-assign` | `Object.assign()` / spread |
|
|
110
|
+
| `array-uniq` | `[...new Set(arr)]` |
|
|
111
|
+
| `array-flatten` / `flat` | `arr.flat(profundidad)` |
|
|
112
|
+
| `path-exists` | `fs.existsSync(ruta)` |
|
|
113
|
+
| `load-json-file` | `JSON.parse(fs.readFileSync(ruta, "utf8"))` |
|
|
114
|
+
| `write-json-file` | `fs.writeFileSync(ruta, JSON.stringify(obj, null, 2))` |
|
|
115
|
+
| `pkg-dir` | `path.resolve(__dirname, "..")` / `import.meta.dirname` |
|
|
116
|
+
|
|
117
|
+
## Stdlib de Python
|
|
118
|
+
|
|
119
|
+
| Crees que necesitas | Python ya tiene |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `python-dateutil` (parsing básico) | `datetime.fromisoformat()` (3.7+) |
|
|
122
|
+
| `pytz` | `zoneinfo.ZoneInfo("America/Mexico_City")` (3.9+) |
|
|
123
|
+
| `attrs` (data classes simples) | `@dataclass` |
|
|
124
|
+
| `pathlib2` | `pathlib.Path` (3.4+) |
|
|
125
|
+
| `simplejson` (uso básico) | `json` (stdlib) |
|
|
126
|
+
| `requests` (GET simple) | `urllib.request.urlopen(url)`; `requests` para algo real |
|
|
127
|
+
| `click` (comando único) | `argparse` (stdlib) |
|
|
128
|
+
| `mergedeep` | `dict \| otro_dict` (3.9+) |
|
|
129
|
+
| `more-itertools` (básico) | `itertools`: `chain`, `islice`, `groupby`, `product` |
|
|
130
|
+
| `toolz` (básico) | `functools`: `lru_cache`, `partial`, `reduce` |
|
|
131
|
+
|
|
132
|
+
## Base de datos
|
|
133
|
+
|
|
134
|
+
| Crees que necesitas código de app para | La BD ya tiene |
|
|
135
|
+
|---|---|
|
|
136
|
+
| Paginación | `LIMIT 20 OFFSET 40` |
|
|
137
|
+
| Totales acumulados | `SUM(...) OVER (ORDER BY fecha)` (window function) |
|
|
138
|
+
| Rank por grupo | `RANK() OVER (PARTITION BY categoria ORDER BY score DESC)` |
|
|
139
|
+
| Deduplicación | `SELECT DISTINCT` / `ON CONFLICT DO NOTHING` |
|
|
140
|
+
| Recorrido de árboles | CTE recursivo (`WITH RECURSIVE`) |
|
|
141
|
+
| Full-text search (básico) | `tsvector` / `MATCH AGAINST` / `FTS5` |
|
|
142
|
+
| Almacenar + consultar JSON | `jsonb` (Postgres) / `JSON_EXTRACT` (SQLite/MySQL) |
|
|
143
|
+
| Generación de UUID | `gen_random_uuid()` (Postgres) / `UUID()` (MySQL) |
|
|
144
|
+
| Timestamps de insert/update | `DEFAULT now()` + trigger / `ON UPDATE CURRENT_TIMESTAMP` |
|
|
145
|
+
| Unicidad | constraint `UNIQUE`, no checks en la app |
|
|
146
|
+
| Integridad referencial | `FOREIGN KEY`, no checks en la app |
|
|
147
|
+
| Rangos de valores | `CHECK (precio > 0)`, no validación en la app |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## El patrón
|
|
152
|
+
|
|
153
|
+
En todas las capas es el mismo ciclo:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
El equipo de la plataforma pasa años resolviendo el problema.
|
|
157
|
+
Alguien lo envuelve en un paquete.
|
|
158
|
+
Instalas el wrapper.
|
|
159
|
+
El wrapper queda sin mantenimiento.
|
|
160
|
+
Terminas debugueando el wrapper.
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Sáltate el wrapper. Cuando la solución nativa es genuinamente insuficiente
|
|
164
|
+
(soporte de navegadores viejos, edge cases que no cubre, ergonomía que importa
|
|
165
|
+
a escala), la librería se gana su lugar — instálala entonces, no antes
|
|
166
|
+
(y verifica su vigencia con Context7, regla `usar-context7.md`).
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Variables residuales post-refactor son code smell
|
|
2
|
+
|
|
3
|
+
Recurso de `prevencion-sobreingenieria`. Extraído del SKILL.md en v1.3.0 para
|
|
4
|
+
respetar el límite de 300 líneas; el contenido es normativo sin cambios.
|
|
5
|
+
|
|
6
|
+
## Regla
|
|
7
|
+
|
|
8
|
+
Variables asignadas pero no usadas (F841 en ruff/flake8) **post-refactor** son
|
|
9
|
+
casi siempre residuos de una versión anterior del código que el refactor no
|
|
10
|
+
limpió. Se parecen a "variables temporales del desarrollador" pero delatan un
|
|
11
|
+
refactor incompleto — la lógica que las usaba se movió o eliminó, pero las
|
|
12
|
+
asignaciones quedaron.
|
|
13
|
+
|
|
14
|
+
## Por qué son peligrosas
|
|
15
|
+
|
|
16
|
+
- **Confunden al próximo lector**: asumirá que la variable se usa más abajo.
|
|
17
|
+
- **Inflan el diff cognitivo** del refactor real.
|
|
18
|
+
- **Ocultan bugs sutiles**: el valor asignado puede tener side effects (llamada
|
|
19
|
+
a función costosa o con efecto en estado) que ya no aporta nada al flujo.
|
|
20
|
+
- **Invalidan la métrica de cobertura**: líneas "cubiertas" por tests que en
|
|
21
|
+
realidad no afectan el resultado.
|
|
22
|
+
|
|
23
|
+
## Patrón operativo
|
|
24
|
+
|
|
25
|
+
Después de **cualquier refactor** que modifique el flujo de una función (extraer
|
|
26
|
+
método, inlinear función, cambiar el shape de un return), correr:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
ruff check --select=F841 ruta/al/modulo.py
|
|
30
|
+
# o para todo el proyecto
|
|
31
|
+
ruff check --select=F841 .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Cada F841 detectado post-refactor es candidato 1 de 2:
|
|
35
|
+
|
|
36
|
+
1. **Variable muerta** (caso común post-refactor): eliminarla.
|
|
37
|
+
2. **Variable viva pero mal nombrada** (caso raro): si el valor se usa como
|
|
38
|
+
side effect intencional (ej: evaluar una propiedad para cachear), renombrar
|
|
39
|
+
con prefijo `_` y comentar el propósito.
|
|
40
|
+
|
|
41
|
+
## Anti-patrón típico
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
# Antes del refactor: había un if que usaba exito_key
|
|
45
|
+
def procesar(data):
|
|
46
|
+
exito, exito_key = intentar(data)
|
|
47
|
+
if exito_key == "via_rapida":
|
|
48
|
+
return procesar_rapido(data)
|
|
49
|
+
return procesar_normal(data, exito_key)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Después de refactor que eliminó la rama "via_rapida"
|
|
53
|
+
def procesar(data):
|
|
54
|
+
exito, exito_key = intentar(data) # ← F841: exito_key asignado, no usado
|
|
55
|
+
return procesar_normal(data) # ← también exito residual
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Patrón canónico
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
# Post-refactor limpio
|
|
62
|
+
def procesar(data):
|
|
63
|
+
_ = intentar(data) # si el side effect de intentar() importa
|
|
64
|
+
return procesar_normal(data)
|
|
65
|
+
|
|
66
|
+
# o mejor: si ni siquiera el side effect importa
|
|
67
|
+
def procesar(data):
|
|
68
|
+
return procesar_normal(data)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Reglas
|
|
72
|
+
|
|
73
|
+
- **Gate pre-commit**: agregar `ruff check --select=F841` al pre-commit hook del proyecto. No bloquea en un repo grande donde F841 preexiste, pero sí bloquea en los archivos modificados por el commit actual.
|
|
74
|
+
- **Revisión explícita post-refactor grande**: tras extraer 5+ métodos o mover 5+ bloques, correr `ruff --select=F401,F841` sobre los archivos tocados.
|
|
75
|
+
- **NO silenciar con `# noqa: F841` sin justificación** — la excepción legítima (side effect intencional con nombre `_`) es tan rara que vale la pena documentarla; silenciar sin comentario es ocultar deuda.
|
|
76
|
+
- **Incluir en checklist de PR**: "F841 limpio en archivos tocados" como item explícito.
|
|
77
|
+
|
|
78
|
+
## Aplicabilidad
|
|
79
|
+
|
|
80
|
+
Detectable en:
|
|
81
|
+
|
|
82
|
+
- Python: `ruff --select=F841`, `flake8 --select=F841`.
|
|
83
|
+
- TypeScript/JavaScript: `eslint --rule 'no-unused-vars: error'`.
|
|
84
|
+
- Go: el compilador lo hace obligatorio (`declared but not used`).
|
|
85
|
+
- Rust: warning `unused_variables`, promovido a error con `#![deny(unused_variables)]`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tdd-workflow
|
|
3
3
|
description: Flujo completo de Test-Driven Development. Ciclo RED (el test falla) → GREEN (implementación mínima) → REFACTOR (limpieza). Incluye cobertura mínima obligatoria, tests de frontera, factories, fixtures y estrategias para diferentes tipos de código (APIs, services, componentes Angular).
|
|
4
|
-
version: "1.2.
|
|
4
|
+
version: "1.2.3"
|
|
5
5
|
herramientasPermitidas: [Read, Bash]
|
|
6
6
|
evolvable: true # default para skill estandar
|
|
7
7
|
exclusiones:
|
|
@@ -339,6 +339,8 @@ pytest --cov=src/services --cov-fail-under=90
|
|
|
339
339
|
|
|
340
340
|
## Gotchas / Errores comunes no obvios
|
|
341
341
|
|
|
342
|
+
**Tests que dependen de que N llamadas caigan en el MISMO segundo (IDs con timestamp, sufijos de colisión) son flaky por frontera de segundo — congelar el reloj**: si el SUT genera identificadores tipo `tipo-YYMMDD-HHMMSS` y el test llama 3 veces esperando sufijos de colisión `-2`/`-3`, cruzar el límite de segundo entre llamadas hace que la 3ª obtenga timestamp nuevo sin colisión — falla intermitente que puede tumbar un `npm publish` (caso real swl-ses 2026-07-03: `loop-telemetry`). Causa: el SUT toma el reloj de pared internamente; el test no controla el tiempo. Fix (Node): `t.mock.timers.enable({ apis: ['Date'], now: new Date('2026-01-01T12:00:00Z') })` de `node:test` — el equivalente a freezegun; se restaura solo al terminar el test. En Python: `freezegun.freeze_time`. NUNCA "reintentar el test" ni ampliar ventanas: congelar el reloj hace la colisión determinista por construcción.
|
|
343
|
+
|
|
342
344
|
**El ciclo TDD se rompe cuando el test en fase RED pasa sin implementación porque la función ya existe con otro nombre en el módulo y Python la importa silenciosamente desde un namespace diferente**: escribir `from app.services import calcular_descuento` en el test cuando `calcular_descuento` ya existe en `app.utils` (importada en `__init__.py`) hace que el test pase en RED sin error, invalidando el ciclo. Causa: los imports con `from app.services import *` en `__init__.py` pueden re-exportar funciones de submódulos, haciendo que el test encuentre una implementación inesperada. Fix: verificar con `python -c "from app.services import calcular_descuento; print(calcular_descuento.__module__)"` que el símbolo viene del módulo correcto. Usar imports explícitos en los tests (`from app.services.descuentos import calcular_descuento`) en lugar de imports de paquete.
|
|
343
345
|
|
|
344
346
|
**`pytest.mark.asyncio` con `asyncio_mode = "auto"` en `pytest.ini` hace que fixtures síncronos que retornan coroutines sean llamados sin `await`, causando que el fixture entregue un objeto coroutine en lugar del valor esperado**: un fixture `def cliente_premium(db_session)` que retorna `ClienteFactory.build(tipo="premium")` funciona, pero si accidentalmente se define como `async def cliente_premium(db_session)` y se usa en un test síncrono, pytest lo trata como fixture síncrono y el test recibe el objeto coroutine. Causa: la mezcla de fixtures `async def` y `def` en el mismo `conftest.py` con `asyncio_mode = "auto"` puede crear comportamientos inesperados dependiendo de la versión de `pytest-asyncio`. Fix: en proyectos async, definir TODOS los fixtures relevantes como `async def` explícitamente y verificar que el test use `@pytest.mark.asyncio` o tenga el modo auto configurado correctamente.
|
package/hooks/audit-trail.js
CHANGED
|
@@ -33,10 +33,10 @@ const MERKLE_FILE = path.join(CWD, '.planning', 'audit-merkle.jsonl');
|
|
|
33
33
|
// Herramientas excluidas del audit (muy ruidosas, bajo valor de auditoría)
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
]);
|
|
36
|
+
// Vacío desde 2026-07-03: los dos hooks excluidos (linea-estado,
|
|
37
|
+
// monitor-contexto) se eliminaron del sistema. El set se conserva como
|
|
38
|
+
// punto de extensión para futuras exclusiones.
|
|
39
|
+
const HERRAMIENTAS_EXCLUIDAS = new Set([]);
|
|
40
40
|
|
|
41
41
|
// ---------------------------------------------------------------------------
|
|
42
42
|
// Helpers
|
|
@@ -375,6 +375,11 @@ function verificarTodosSinTicket(rutaRelativa, contenido) {
|
|
|
375
375
|
// Construir palabra clave dinámicamente para evitar auto-detección
|
|
376
376
|
const keyword = 'TO' + 'DO';
|
|
377
377
|
const patronSinTicket = new RegExp(`\\b${keyword}\\b(?!\\s*[:(]\\s*(?:#\\d+|[A-Z]+-\\d+|https?:\\/\\/))`);
|
|
378
|
+
// En Markdown, "TODO" solo cuenta como marcador con sintaxis explícita
|
|
379
|
+
// (TODO: / TODO() — la palabra española "TODO" en prosa ("en TODO destino",
|
|
380
|
+
// "tumbaba TODO el release") nunca la lleva. La blacklist de verbos previa
|
|
381
|
+
// era frágil: cada frase nueva la esquivaba (falsos positivos 2026-07-03).
|
|
382
|
+
const patronSinTicketMd = new RegExp(`\\b${keyword}\\s*[:(](?!\\s*(?:#\\d+|[A-Z]+-\\d+|https?:\\/\\/))`);
|
|
378
383
|
const patronConTicket = new RegExp(`\\b${keyword}\\s*[:(]\\s*(?:#\\d+|[A-Z]+-\\d+|https?:\\/\\/)`);
|
|
379
384
|
const patronNosec = /\/\/\s*nosec/;
|
|
380
385
|
|
|
@@ -391,15 +396,12 @@ function verificarTodosSinTicket(rutaRelativa, contenido) {
|
|
|
391
396
|
if (dentroDeCodigoMd) continue;
|
|
392
397
|
// Excluir líneas marcadas con // nosec o <!-- nosec -->
|
|
393
398
|
if (patronNosec.test(linea) || /<!--\s*nosec\s*-->/.test(linea)) continue;
|
|
394
|
-
// En Markdown, excluir "TODO" dentro de backticks (código inline)
|
|
399
|
+
// En Markdown, excluir "TODO" dentro de backticks (código inline)
|
|
395
400
|
if (esMd && /`[^`]*TODO[^`]*`/.test(linea)) continue;
|
|
396
|
-
if (esMd && /\b(capturar|cargar|cubrir|cumplir|verificar|pasar|incluir|tener)\s+TODO\b/i.test(linea)) continue;
|
|
397
|
-
// En Markdown, excluir líneas descriptivas que mencionan TODO como concepto (ej: "detecta TODO sin ticket")
|
|
398
|
-
if (esMd && /TODO\s+sin\s+ticket/i.test(linea)) continue;
|
|
399
401
|
// Si tiene referencia a ticket, es válido
|
|
400
402
|
if (patronConTicket.test(linea)) continue;
|
|
401
|
-
// Si no tiene referencia, es una violación
|
|
402
|
-
if (patronSinTicket.test(linea)) {
|
|
403
|
+
// Si no tiene referencia, es una violación (en .md solo el marcador explícito)
|
|
404
|
+
if ((esMd ? patronSinTicketMd : patronSinTicket).test(linea)) {
|
|
403
405
|
const tag = '[TO' + 'DO]';
|
|
404
406
|
mensajes.push(
|
|
405
407
|
` ${tag} ${rutaRelativa}:${i + 1} — sin referencia a ticket/issue`
|
|
@@ -1009,10 +1011,11 @@ function ejecutarCoverage(fuentes, runner) {
|
|
|
1009
1011
|
|
|
1010
1012
|
let inputRaw = '';
|
|
1011
1013
|
|
|
1012
|
-
// Solo enganchar stdin cuando el hook
|
|
1013
|
-
//
|
|
1014
|
-
//
|
|
1015
|
-
|
|
1014
|
+
// Solo enganchar stdin cuando el hook corre como entrypoint: ejecución directa
|
|
1015
|
+
// O vía el wrapper de settings.json `node -e "require('./hooks/...')"`, donde
|
|
1016
|
+
// require.main es undefined. Al hacer `require()` desde un test, require.main
|
|
1017
|
+
// es el runner (definido y ≠ este módulo) → no engancha stdin.
|
|
1018
|
+
if (!require.main || require.main === module) {
|
|
1016
1019
|
|
|
1017
1020
|
process.stdin.on('data', chunk => {
|
|
1018
1021
|
inputRaw += chunk;
|
|
@@ -1182,7 +1185,7 @@ process.stdin.on('end', () => {
|
|
|
1182
1185
|
}
|
|
1183
1186
|
});
|
|
1184
1187
|
|
|
1185
|
-
} // fin
|
|
1188
|
+
} // fin gate de entrypoint (directo o wrapper node -e)
|
|
1186
1189
|
|
|
1187
1190
|
// ─── Coverage diferencial opt-in (gate G2-cov, Fase 11) ──────────────────────
|
|
1188
1191
|
//
|
|
@@ -1265,6 +1268,7 @@ module.exports = {
|
|
|
1265
1268
|
extraerMensajeCommit,
|
|
1266
1269
|
esPrefijoExentoDeTests,
|
|
1267
1270
|
verificarTestPresence,
|
|
1271
|
+
verificarTodosSinTicket,
|
|
1268
1272
|
detectarRunnerCoverage,
|
|
1269
1273
|
coverageDiferencial,
|
|
1270
1274
|
};
|
|
@@ -134,8 +134,9 @@ function procesarTurno(data, baseDir) {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
// Listeners de stdin SOLO en modo
|
|
138
|
-
|
|
137
|
+
// Listeners de stdin SOLO en modo entrypoint: ejecución directa o wrapper
|
|
138
|
+
// `node -e "require(...)"` (require.main undefined). Evita colgar los tests.
|
|
139
|
+
if (!require.main || require.main === module) {
|
|
139
140
|
let inputRaw = '';
|
|
140
141
|
process.stdin.on('data', (c) => { inputRaw += c; });
|
|
141
142
|
process.stdin.on('end', () => {
|
|
@@ -138,8 +138,9 @@ function inspeccionar(baseDir) {
|
|
|
138
138
|
return { encolados: n };
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
// Listeners de stdin SOLO en modo
|
|
142
|
-
|
|
141
|
+
// Listeners de stdin SOLO en modo entrypoint: ejecución directa o wrapper
|
|
142
|
+
// `node -e "require(...)"` (require.main undefined). Evita colgar los tests.
|
|
143
|
+
if (!require.main || require.main === module) {
|
|
143
144
|
let inputRaw = '';
|
|
144
145
|
process.stdin.on('data', (c) => { inputRaw += c; });
|
|
145
146
|
process.stdin.on('end', () => {
|
|
@@ -139,6 +139,7 @@ function main() {
|
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
// Entrypoint directo o wrapper `node -e "require(...)"` (require.main undefined).
|
|
143
|
+
if (!require.main || require.main === module) main();
|
|
143
144
|
|
|
144
145
|
module.exports = { debeInyectar, registrarInyeccion, construirBloque, THROTTLE_MS, ARCHIVO_ESTADO };
|