@saulwade/swl-ses 2.3.1 → 2.4.1

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.
Files changed (112) hide show
  1. package/CLAUDE.md +241 -240
  2. package/README.md +597 -597
  3. package/agentes/_intent-spec.md +73 -73
  4. package/agentes/_propose-step.md +90 -90
  5. package/agentes/accesibilidad-wcag-swl.md +690 -690
  6. package/agentes/arquitecto-swl.md +267 -267
  7. package/agentes/auto-evolucion-swl.md +908 -908
  8. package/agentes/backend-api-swl.md +472 -472
  9. package/agentes/backend-csharp-swl.md +420 -420
  10. package/agentes/backend-go-swl.md +390 -390
  11. package/agentes/backend-java-swl.md +281 -281
  12. package/agentes/backend-node-swl.md +479 -479
  13. package/agentes/backend-python-swl.md +605 -605
  14. package/agentes/backend-rust-swl.md +364 -364
  15. package/agentes/backend-workers-swl.md +482 -482
  16. package/agentes/cloud-infra-swl.md +509 -509
  17. package/agentes/consolidador-swl.md +541 -541
  18. package/agentes/datos-swl.md +605 -605
  19. package/agentes/depurador-swl.md +352 -352
  20. package/agentes/devops-ci-swl.md +400 -400
  21. package/agentes/disenador-ui-swl.md +569 -569
  22. package/agentes/documentador-swl.md +345 -345
  23. package/agentes/frontend-angular-swl.md +621 -621
  24. package/agentes/frontend-css-swl.md +716 -716
  25. package/agentes/frontend-react-swl.md +692 -692
  26. package/agentes/frontend-swl.md +496 -496
  27. package/agentes/frontend-tailwind-swl.md +826 -826
  28. package/agentes/gh-fix-ci-swl.md +275 -275
  29. package/agentes/implementador-swl.md +328 -328
  30. package/agentes/investigador-swl.md +432 -432
  31. package/agentes/investigador-ux-swl.md +505 -505
  32. package/agentes/llm-apps-swl.md +278 -278
  33. package/agentes/migrador-swl.md +442 -442
  34. package/agentes/mobile-android-swl.md +511 -511
  35. package/agentes/mobile-cross-swl.md +541 -541
  36. package/agentes/mobile-ios-swl.md +502 -502
  37. package/agentes/mobile-testing-swl.md +302 -302
  38. package/agentes/nemesis-auditor-swl.md +285 -285
  39. package/agentes/notificador-swl.md +918 -918
  40. package/agentes/observabilidad-swl.md +438 -438
  41. package/agentes/orquestador-swl.md +1053 -1026
  42. package/agentes/pagos-swl.md +310 -310
  43. package/agentes/perfilador-usuario-swl.md +321 -321
  44. package/agentes/planificador-swl.md +399 -399
  45. package/agentes/producto-prd-swl.md +589 -589
  46. package/agentes/red-team-swl.md +218 -218
  47. package/agentes/release-manager-swl.md +590 -590
  48. package/agentes/rendimiento-swl.md +713 -713
  49. package/agentes/resolutor-build-swl.md +245 -245
  50. package/agentes/revisor-angular-swl.md +278 -278
  51. package/agentes/revisor-codigo-swl.md +538 -505
  52. package/agentes/revisor-csharp-swl.md +264 -264
  53. package/agentes/revisor-go-swl.md +259 -259
  54. package/agentes/revisor-java-swl.md +257 -257
  55. package/agentes/revisor-kotlin-swl.md +273 -273
  56. package/agentes/revisor-nextjs-swl.md +281 -281
  57. package/agentes/revisor-php-swl.md +271 -271
  58. package/agentes/revisor-react-swl.md +278 -278
  59. package/agentes/revisor-rust-swl.md +346 -346
  60. package/agentes/revisor-seguridad-swl.md +399 -399
  61. package/agentes/revisor-swift-swl.md +268 -268
  62. package/agentes/revisor-typescript-swl.md +346 -346
  63. package/agentes/sre-swl.md +291 -291
  64. package/agentes/tdd-qa-swl.md +393 -393
  65. package/comandos/swl/contexto.md +0 -2
  66. package/comandos/swl/deuda-codigo.md +97 -0
  67. package/habilidades/angular-moderno/SKILL.md +5 -1
  68. package/habilidades/contenedores-docker/SKILL.md +3 -1
  69. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  70. package/habilidades/harness-claude-code/SKILL.md +4 -4
  71. package/habilidades/harness-claude-code/recursos/disciplina-harness-regla.md +6 -5
  72. package/habilidades/legacy-code-rescue/SKILL.md +2 -1
  73. package/habilidades/meta-skills-estandar/SKILL.md +1 -1
  74. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +2 -2
  75. package/habilidades/postgresql-experto/SKILL.md +3 -1
  76. package/habilidades/prevencion-sobreingenieria/SKILL.md +70 -92
  77. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -0
  78. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -0
  79. package/hooks/audit-trail.js +4 -4
  80. package/hooks/calidad-pre-commit.js +15 -11
  81. package/hooks/captura-acciones-post.js +3 -2
  82. package/hooks/captura-acciones-session.js +3 -2
  83. package/hooks/contexto-iteracion.js +2 -1
  84. package/hooks/contexto-subagente.js +68 -0
  85. package/hooks/guardrail-modelo.js +13 -3
  86. package/hooks/inyeccion-contexto.js +12 -12
  87. package/hooks/registro-turnos.js +5 -4
  88. package/hooks/spec-gate.js +2 -1
  89. package/hooks/sugerir-contribuir.js +2 -1
  90. package/hooks/sugerir-regenerar-inventario.js +3 -2
  91. package/hooks/tdd-gate.js +2 -1
  92. package/llms.txt +3 -3
  93. package/manifiestos/canonical-hashes.json +667 -10
  94. package/manifiestos/hook-profiles.json +0 -2
  95. package/manifiestos/hooks-config.json +469 -477
  96. package/manifiestos/invariantes-criticos.json +30 -0
  97. package/manifiestos/modulos.json +1390 -1390
  98. package/manifiestos/skills-lock.json +24 -24
  99. package/package.json +93 -93
  100. package/plugin.json +369 -371
  101. package/schemas/agent-frontmatter.schema.json +3 -1
  102. package/scripts/instalador.js +4 -1
  103. package/scripts/lib/expandir-targets.js +71 -71
  104. package/scripts/lib/preservar-usuario.js +39 -5
  105. package/scripts/lib/toml-merge.js +204 -204
  106. package/scripts/mcp-server/auth.js +105 -105
  107. package/scripts/mcp-server/cache.js +106 -106
  108. package/scripts/validar.js +1 -1
  109. package/scripts/verificar-release.js +51 -0
  110. package/hooks/lib/context-compressor.js +0 -657
  111. package/hooks/linea-estado.js +0 -328
  112. package/hooks/monitor-contexto.js +0 -373
@@ -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.0"
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
- Cuatro principios: pensar antes de codificar, simplicidad primero, cambios
6
- quirúrgicos y ejecución orientada a metas verificables. Cargar antes de
7
- implementar features, refactorizar código o resolver bugs especialmente
8
- cuando el agente tiende a agregar abstracciones especulativas o tocar código
9
- fuera del alcance solicitado.
10
- version: "1.2.0"
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
- Skill derivado de los principios de Andrej Karpathy sobre errores comunes de LLMs
23
- al generar código, adaptado al ecosistema SWL.
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). Cada ejemplo está mapeado a un
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
- ### Regla
244
-
245
- Variables asignadas pero no usadas (F841 en ruff/flake8) **post-refactor** son
246
- casi siempre residuos de una versión anterior del código que el refactor no
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)]`.
@@ -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
- const HERRAMIENTAS_EXCLUIDAS = new Set([
37
- 'linea-estado', // Hook de estado solo UI
38
- 'monitor-contexto', // Interno del sistema
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) o como palabra española
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 se ejecuta directamente (no al hacer
1013
- // `require()` desde un test). Permite testear las funciones puras sin disparar
1014
- // el flujo de stdin.
1015
- if (require.main === module) {
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 if (require.main === module)
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 CLI (evita colgar la suite de tests).
138
- if (require.main === module) {
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 CLI.
142
- if (require.main === module) {
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
- if (require.main === module) main();
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 };
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: contexto-subagente.js
6
+ * Tipo: SubagentStart (aplica a todos los subagentes Task/Agent)
7
+ *
8
+ * El contexto emitido por hooks de SessionStart es solo del hilo padre y
9
+ * NUNCA llega a los subagentes: cada agente spawneado vía Task/Agent corre
10
+ * sin las disciplinas activas de la sesión (patrón documentado en ponytail
11
+ * issue #252 — mismo gap verificado en swl-ses el 2026-07-03: 0 usos de
12
+ * SubagentStart hasta este hook).
13
+ *
14
+ * Re-inyecta a cada subagente un bloque compacto (~120 tokens) con las
15
+ * disciplinas transversales no negociables: idioma es-MX, escalera de código
16
+ * mínimo con guardas intocables, y la convención de marcador `simplificado:`.
17
+ * Deliberadamente mínimo — el costo se paga en CADA spawn de subagente.
18
+ *
19
+ * Opt-out: SWL_CONTEXTO_SUBAGENTE=0 desactiva la inyección.
20
+ * Formato de salida: SubagentStart requiere la forma JSON hookSpecificOutput
21
+ * (stdout crudo se descarta en este evento — a diferencia de SessionStart).
22
+ */
23
+
24
+ /**
25
+ * Construye el bloque de disciplinas que se inyecta al subagente.
26
+ *
27
+ * @returns {string} Contexto compacto en es-MX.
28
+ */
29
+ function construirContexto() {
30
+ return [
31
+ 'DISCIPLINAS SWL ACTIVAS (heredadas de la sesión padre):',
32
+ '- Idioma: español de México en todo output (código, comentarios, commits, reportes).',
33
+ '- Código mínimo — escalera antes de escribir: ¿necesita existir (YAGNI)? →',
34
+ ' ¿ya existe en este codebase? → ¿stdlib? → ¿feature nativa de la plataforma? →',
35
+ ' ¿dependencia ya instalada? → ¿una línea? → solo entonces, el mínimo que funciona.',
36
+ '- Guardas intocables (nunca simplificar): validación en trust boundaries,',
37
+ ' manejo de errores que previene pérdida de datos, seguridad, accesibilidad.',
38
+ '- Simplificación deliberada se marca: `simplificado: <techo>, <trigger de upgrade>`.',
39
+ '- Bug fix = causa raíz: grep de todos los callers antes de parchar uno.',
40
+ 'Detalle completo: Skill("prevencion-sobreingenieria").',
41
+ ].join('\n');
42
+ }
43
+
44
+ /**
45
+ * Emite el contexto en el formato JSON que SubagentStart exige.
46
+ * Nunca falla hacia afuera: un stdout roto no debe reportarse como hook error.
47
+ */
48
+ function main() {
49
+ if (process.env.SWL_CONTEXTO_SUBAGENTE === '0') return;
50
+ try {
51
+ process.stdout.write(JSON.stringify({
52
+ hookSpecificOutput: {
53
+ hookEventName: 'SubagentStart',
54
+ additionalContext: construirContexto(),
55
+ },
56
+ }));
57
+ } catch (_) {
58
+ // Silencioso — no interrumpir el spawn del subagente.
59
+ }
60
+ }
61
+
62
+ // Entrypoint directo o wrapper `node -e "require(...)"` (require.main undefined).
63
+ // No consume stdin: emite y termina — sin listeners, el proceso sale solo.
64
+ if (!require.main || require.main === module) {
65
+ main();
66
+ }
67
+
68
+ module.exports = { construirContexto };