@saulwade/swl-ses 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -3
- package/README.md +5 -5
- package/agentes/orquestador-swl.md +89 -1
- package/agentes/revisor-codigo-swl.md +34 -10
- package/agentes/revisor-seguridad-swl.md +7 -0
- package/agentes/tdd-qa-swl.md +23 -2
- package/comandos/swl/autoresearch.md +102 -6
- package/comandos/swl/metricas.md +34 -0
- package/comandos/swl/nemesis.md +42 -1
- package/comandos/swl/planear-fase.md +8 -0
- package/comandos/swl/predecir.md +139 -0
- package/comandos/swl/verificar.md +50 -7
- package/habilidades/angular-moderno/SKILL.md +44 -1
- package/habilidades/autoresearch/SKILL.md +15 -1
- package/habilidades/calidad-mutation-testing/SKILL.md +170 -0
- package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
- package/habilidades/checklist-seguridad/SKILL.md +29 -1
- package/habilidades/checklist-seguridad/recursos/stride-cobertura.md +60 -0
- package/habilidades/css-moderno/SKILL.md +3 -1
- package/habilidades/fastapi-experto/SKILL.md +56 -5
- package/habilidades/patrones-python/SKILL.md +8 -5
- package/habilidades/proceso-debate-adversarial/SKILL.md +164 -0
- package/habilidades/proceso-debate-adversarial/recursos/personas.md +105 -0
- package/habilidades/proceso-dynamic-workflows/SKILL.md +138 -0
- package/habilidades/proceso-dynamic-workflows/recursos/template-adversarial-verify.js +65 -0
- package/habilidades/proceso-dynamic-workflows/recursos/template-triage.js +65 -0
- package/habilidades/tdd-workflow/SKILL.md +14 -1
- package/habilidades/tdd-workflow/recursos/gherkin-bdd.md +111 -0
- package/hooks/contexto-iteracion.js +144 -0
- package/hooks/lib/loop-telemetry.js +321 -0
- package/hooks/notificacion-telegram.js +11 -3
- package/llms.txt +29 -0
- package/manifiestos/hooks-config.json +10 -1
- package/manifiestos/modulos.json +7 -1
- package/manifiestos/skills-lock.json +45 -24
- package/package.json +4 -3
- package/plugin.json +5 -2
- package/reglas/arquitectura.evolved.json +7 -0
- package/reglas/arquitectura.md +65 -0
- package/reglas/seguridad.evolved.json +7 -0
- package/reglas/seguridad.md +144 -0
- package/scripts/generar-inventario.js +64 -1
- package/scripts/instalador.js +32 -2
- package/scripts/smoke-test.js +24 -2
|
@@ -301,6 +301,15 @@
|
|
|
301
301
|
"maxConsecutiveFailures": 5,
|
|
302
302
|
"degradeOnFailure": "skip"
|
|
303
303
|
},
|
|
304
|
+
"contexto-iteracion.js": {
|
|
305
|
+
"event": "UserPromptSubmit",
|
|
306
|
+
"matcher": "",
|
|
307
|
+
"description": "Anti-context-rot para loops iterativos: inyecta el estado de la corrida activa en .planning/loops/ (iteración, últimas filas del TSV, plateau/recomendación de trayectoria). Throttled a 1 inyección cada 4 min por corrida. Opt-out: SWL_CONTEXTO_ITERACION=0.",
|
|
308
|
+
"blocking": false,
|
|
309
|
+
"async": true,
|
|
310
|
+
"maxConsecutiveFailures": 5,
|
|
311
|
+
"degradeOnFailure": "skip"
|
|
312
|
+
},
|
|
304
313
|
"guardrail-modelo.js": {
|
|
305
314
|
"event": "PreToolUse",
|
|
306
315
|
"matcher": "Task|Agent",
|
|
@@ -395,7 +404,7 @@
|
|
|
395
404
|
"notificacion-telegram-subagent.js": {
|
|
396
405
|
"event": "SubagentStop",
|
|
397
406
|
"matcher": "",
|
|
398
|
-
"description": "Envía notificación Telegram cuando un subagente termina su turno. Best-effort: siempre exit 0.
|
|
407
|
+
"description": "Envía notificación Telegram cuando un subagente termina su turno. INERTE por default: SubagentStop no está en los eventos default del script compartido porque duplica la notificación del Stop final (mismo cuerpo, dos mensajes). Activar con CLAUDE_NOTIFY_EVENTS=Stop,Notification,SubagentStop. Best-effort: siempre exit 0. Opt-out global: SWL_NOTIFICACIONES_TELEGRAM=0.",
|
|
399
408
|
"blocking": false,
|
|
400
409
|
"async": true,
|
|
401
410
|
"maxConsecutiveFailures": 5,
|
package/manifiestos/modulos.json
CHANGED
|
@@ -244,9 +244,12 @@
|
|
|
244
244
|
"habilidades/proceso-ddia-fundamentos",
|
|
245
245
|
"habilidades/proceso-ddia-streaming",
|
|
246
246
|
"habilidades/proceso-discovery-machote",
|
|
247
|
+
"habilidades/proceso-dynamic-workflows",
|
|
247
248
|
"habilidades/proceso-modular-split",
|
|
248
249
|
"habilidades/agent-deep-links",
|
|
249
|
-
"habilidades/changelog-generator"
|
|
250
|
+
"habilidades/changelog-generator",
|
|
251
|
+
"habilidades/proceso-debate-adversarial",
|
|
252
|
+
"habilidades/calidad-mutation-testing"
|
|
250
253
|
],
|
|
251
254
|
"targets": [
|
|
252
255
|
"claude",
|
|
@@ -839,6 +842,7 @@
|
|
|
839
842
|
"comandos/swl/auditar-deps.md",
|
|
840
843
|
"comandos/swl/crear-skill.md",
|
|
841
844
|
"comandos/swl/autoresearch.md",
|
|
845
|
+
"comandos/swl/predecir.md",
|
|
842
846
|
"comandos/swl/contexto.md",
|
|
843
847
|
"comandos/swl/sesiones.md",
|
|
844
848
|
"comandos/swl/instintos.md",
|
|
@@ -1031,6 +1035,7 @@
|
|
|
1031
1035
|
"hooks/validar-intent-spec.js",
|
|
1032
1036
|
"hooks/sugerir-regenerar-inventario.js",
|
|
1033
1037
|
"hooks/sugerir-contribuir.js",
|
|
1038
|
+
"hooks/contexto-iteracion.js",
|
|
1034
1039
|
"hooks/_run-hook.sh",
|
|
1035
1040
|
"hooks/lib/fingerprint-id.js",
|
|
1036
1041
|
"hooks/lib/token-budget.js",
|
|
@@ -1078,6 +1083,7 @@
|
|
|
1078
1083
|
"hooks/lib/taint-tracker.js",
|
|
1079
1084
|
"hooks/lib/resource-quota.js",
|
|
1080
1085
|
"hooks/lib/merkle-audit.js",
|
|
1086
|
+
"hooks/lib/loop-telemetry.js",
|
|
1081
1087
|
"scripts/lib/scoring-instintos.js",
|
|
1082
1088
|
"scripts/lib/budget-enforcer.js",
|
|
1083
1089
|
"scripts/lib/semantic-search.js",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"lockfileVersion": 1,
|
|
3
|
-
"generatedAt": "2026-
|
|
4
|
-
"skillsCount":
|
|
5
|
-
"lockHash": "sha256:
|
|
3
|
+
"generatedAt": "2026-06-11T04:41:07.917Z",
|
|
4
|
+
"skillsCount": 181,
|
|
5
|
+
"lockHash": "sha256:2703b04d5710f9b16609e7f35e3c46992bd029bae06e792d9c3a3695724ee0cc",
|
|
6
6
|
"skills": [
|
|
7
7
|
{
|
|
8
8
|
"nombre": "accesibilidad-a11y",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
{
|
|
50
50
|
"nombre": "angular-moderno",
|
|
51
51
|
"path": "habilidades/angular-moderno/SKILL.md",
|
|
52
|
-
"hash": "sha256:
|
|
53
|
-
"bytes":
|
|
54
|
-
"version": "\"1.0.
|
|
52
|
+
"hash": "sha256:1e0ec49d49147fa6e8c67286efb4662edebb8c5150d34e73c85c0ce3d845f52a",
|
|
53
|
+
"bytes": 10743,
|
|
54
|
+
"version": "\"1.0.1\""
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
"nombre": "api-rest-diseno",
|
|
@@ -98,9 +98,9 @@
|
|
|
98
98
|
{
|
|
99
99
|
"nombre": "autoresearch",
|
|
100
100
|
"path": "habilidades/autoresearch/SKILL.md",
|
|
101
|
-
"hash": "sha256:
|
|
102
|
-
"bytes":
|
|
103
|
-
"version": "\"1.
|
|
101
|
+
"hash": "sha256:2d60940136558246ac49eb764ba31e9dc8f9be58558d95a950f069e239493c17",
|
|
102
|
+
"bytes": 12826,
|
|
103
|
+
"version": "\"1.1.0\""
|
|
104
104
|
},
|
|
105
105
|
{
|
|
106
106
|
"nombre": "azure-cloud",
|
|
@@ -249,6 +249,13 @@
|
|
|
249
249
|
"bytes": 11840,
|
|
250
250
|
"version": "\"1.0.0\""
|
|
251
251
|
},
|
|
252
|
+
{
|
|
253
|
+
"nombre": "calidad-mutation-testing",
|
|
254
|
+
"path": "habilidades/calidad-mutation-testing/SKILL.md",
|
|
255
|
+
"hash": "sha256:aa8e10f5b4bc8ed9c2faefb55d5db881f5009537e11c6b12eedd4e07dc25eabc",
|
|
256
|
+
"bytes": 9213,
|
|
257
|
+
"version": "\"1.0.0\""
|
|
258
|
+
},
|
|
252
259
|
{
|
|
253
260
|
"nombre": "changelog-generator",
|
|
254
261
|
"path": "habilidades/changelog-generator/SKILL.md",
|
|
@@ -266,9 +273,9 @@
|
|
|
266
273
|
{
|
|
267
274
|
"nombre": "checklist-seguridad",
|
|
268
275
|
"path": "habilidades/checklist-seguridad/SKILL.md",
|
|
269
|
-
"hash": "sha256:
|
|
270
|
-
"bytes":
|
|
271
|
-
"version": "\"1.
|
|
276
|
+
"hash": "sha256:2677b17fb658cd596b7f28da51df76579cbe9288ac910ec8bf239e3b74accefe",
|
|
277
|
+
"bytes": 19379,
|
|
278
|
+
"version": "\"1.2.0\""
|
|
272
279
|
},
|
|
273
280
|
{
|
|
274
281
|
"nombre": "checkpoints-verificacion",
|
|
@@ -343,9 +350,9 @@
|
|
|
343
350
|
{
|
|
344
351
|
"nombre": "css-moderno",
|
|
345
352
|
"path": "habilidades/css-moderno/SKILL.md",
|
|
346
|
-
"hash": "sha256:
|
|
347
|
-
"bytes":
|
|
348
|
-
"version": "\"1.
|
|
353
|
+
"hash": "sha256:8fc832b069c29a983ca26661bc3140211028e998562f98b749fb6054a6793b2a",
|
|
354
|
+
"bytes": 8323,
|
|
355
|
+
"version": "\"1.1.0\""
|
|
349
356
|
},
|
|
350
357
|
{
|
|
351
358
|
"nombre": "datos-etl",
|
|
@@ -511,9 +518,9 @@
|
|
|
511
518
|
{
|
|
512
519
|
"nombre": "fastapi-experto",
|
|
513
520
|
"path": "habilidades/fastapi-experto/SKILL.md",
|
|
514
|
-
"hash": "sha256:
|
|
515
|
-
"bytes":
|
|
516
|
-
"version": "\"1.3.
|
|
521
|
+
"hash": "sha256:2e7e158b6ac54d9aec9b2e3932e9c4e5e17e43525530cad21fc30252ee2279bf",
|
|
522
|
+
"bytes": 25977,
|
|
523
|
+
"version": "\"1.3.1\""
|
|
517
524
|
},
|
|
518
525
|
{
|
|
519
526
|
"nombre": "feynman-auditor-swl",
|
|
@@ -833,9 +840,9 @@
|
|
|
833
840
|
{
|
|
834
841
|
"nombre": "patrones-python",
|
|
835
842
|
"path": "habilidades/patrones-python/SKILL.md",
|
|
836
|
-
"hash": "sha256:
|
|
837
|
-
"bytes":
|
|
838
|
-
"version": "\"1.
|
|
843
|
+
"hash": "sha256:fb6f9a3727971a7eb9f93b3d6b79e588b55631b6353076c4144cc5b0dfe084e1",
|
|
844
|
+
"bytes": 14155,
|
|
845
|
+
"version": "\"1.4.2\""
|
|
839
846
|
},
|
|
840
847
|
{
|
|
841
848
|
"nombre": "perfil-usuario",
|
|
@@ -935,6 +942,13 @@
|
|
|
935
942
|
"bytes": 9497,
|
|
936
943
|
"version": null
|
|
937
944
|
},
|
|
945
|
+
{
|
|
946
|
+
"nombre": "proceso-debate-adversarial",
|
|
947
|
+
"path": "habilidades/proceso-debate-adversarial/SKILL.md",
|
|
948
|
+
"hash": "sha256:f665c9690c07bde6d547f70d9c782d10bfa07f90d425760d218951ae580e12bc",
|
|
949
|
+
"bytes": 8999,
|
|
950
|
+
"version": "\"1.0.0\""
|
|
951
|
+
},
|
|
938
952
|
{
|
|
939
953
|
"nombre": "proceso-discovery-machote",
|
|
940
954
|
"path": "habilidades/proceso-discovery-machote/SKILL.md",
|
|
@@ -942,6 +956,13 @@
|
|
|
942
956
|
"bytes": 10120,
|
|
943
957
|
"version": "\"1.0.0\""
|
|
944
958
|
},
|
|
959
|
+
{
|
|
960
|
+
"nombre": "proceso-dynamic-workflows",
|
|
961
|
+
"path": "habilidades/proceso-dynamic-workflows/SKILL.md",
|
|
962
|
+
"hash": "sha256:56b4410405d6060f8a0bcc28736799eead5748b208a7a1abc5a8eca1d2e540c8",
|
|
963
|
+
"bytes": 8709,
|
|
964
|
+
"version": null
|
|
965
|
+
},
|
|
945
966
|
{
|
|
946
967
|
"nombre": "proceso-intent-engineering",
|
|
947
968
|
"path": "habilidades/proceso-intent-engineering/SKILL.md",
|
|
@@ -1141,9 +1162,9 @@
|
|
|
1141
1162
|
{
|
|
1142
1163
|
"nombre": "tdd-workflow",
|
|
1143
1164
|
"path": "habilidades/tdd-workflow/SKILL.md",
|
|
1144
|
-
"hash": "sha256:
|
|
1145
|
-
"bytes":
|
|
1146
|
-
"version": "\"1.0
|
|
1165
|
+
"hash": "sha256:ee2820285c1db7dff1b630c01ed28493a0bad1dfc2df0bbe7a31bf28de1d6894",
|
|
1166
|
+
"bytes": 31969,
|
|
1167
|
+
"version": "\"1.1.0\""
|
|
1147
1168
|
},
|
|
1148
1169
|
{
|
|
1149
1170
|
"nombre": "terraform-experto",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saulwade/swl-ses",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot con 61 agentes,
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot con 61 agentes, 181 habilidades, 45 comandos, 71 reglas y 45 hooks. Soporta 11 lenguajes y 7 runtimes: Claude Code, OpenClaude, OpenCode, Gemini CLI, Cursor, Codex CLI (soporte completo); GitHub Copilot (soporte parcial). 100% en espanol (Mexico). Multi-target install (--target CSV / --all-runtimes), autoconfig MCP en Cursor/Codex con --with-mcp, agentes Codex en TOML, hooks Cursor (17 eventos) y Codex (6 eventos). Gateway bidireccional con relay Telegram y auditoria profunda Nemesis con loop evaluator-optimizer opt-in (ADR-0021) y 8 tools ejecutables. v1.8.0 unifica los directorios runtime de .planning/ al ingles (evolution/, auto-evolution/, user-profile/, archive/), manteniendo fases/ en espanol, con guard validar-planning-paths y allowlist canonica (ADR-0031). Hereda de v1.7.4: skill calidad-anti-patrones-universales + scripts/lib/pr-analyzer.js + 3 sub-secciones en estilo-sin-ai-isms.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"swl-ses": "bin/swl-ses.js",
|
|
7
7
|
"swl-telegram-bot": "bin/swl-telegram-bot.js",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"schemas",
|
|
25
25
|
"_userland",
|
|
26
26
|
"plugin.json",
|
|
27
|
-
"CLAUDE.md"
|
|
27
|
+
"CLAUDE.md",
|
|
28
|
+
"llms.txt"
|
|
28
29
|
],
|
|
29
30
|
"scripts": {
|
|
30
31
|
"postinstall": "echo '\n swl-software-engineering-system instalado.\n Ejecuta: npx swl-ses init\n'",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swl-ses",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot. 61 agentes,
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot. 61 agentes, 181 habilidades, 45 comandos, 71 reglas y 45 hooks. 69 librerias. 11 lenguajes. Soporta Claude Code, Copilot, OpenCode, Codex y Gemini CLI. Loop evaluator-optimizer en /swl:nemesis (ADR-0021). v1.8.0 unifica los directorios runtime de .planning/ al ingles (evolution/, auto-evolution/, user-profile/, archive/), manteniendo fases/ en espanol, con guard validar-planning-paths y allowlist canonica (ADR-0031). Hereda de v1.7.4: skill calidad-anti-patrones-universales + scripts/lib/pr-analyzer.js + 3 sub-secciones en estilo-sin-ai-isms.",
|
|
5
5
|
"author": "Saul Wade Leon",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "https://github.com/saul-wade/swl-ses",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"habilidades/build-errors-swift",
|
|
42
42
|
"habilidades/build-errors-typescript",
|
|
43
43
|
"habilidades/calidad-anti-patrones-universales",
|
|
44
|
+
"habilidades/calidad-mutation-testing",
|
|
44
45
|
"habilidades/changelog-generator",
|
|
45
46
|
"habilidades/checklist-calidad",
|
|
46
47
|
"habilidades/checklist-seguridad",
|
|
@@ -139,7 +140,9 @@
|
|
|
139
140
|
"habilidades/proceso-confianza-pre-implementacion",
|
|
140
141
|
"habilidades/proceso-ddia-fundamentos",
|
|
141
142
|
"habilidades/proceso-ddia-streaming",
|
|
143
|
+
"habilidades/proceso-debate-adversarial",
|
|
142
144
|
"habilidades/proceso-discovery-machote",
|
|
145
|
+
"habilidades/proceso-dynamic-workflows",
|
|
143
146
|
"habilidades/proceso-intent-engineering",
|
|
144
147
|
"habilidades/proceso-modular-split",
|
|
145
148
|
"habilidades/prompt-engineering",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"evolved": true,
|
|
3
|
+
"evolved-from": "1.8.0",
|
|
4
|
+
"evolved-at": "2026-06-04",
|
|
5
|
+
"evolved-by": "evolucionar",
|
|
6
|
+
"evolved-note": "PE-010 sección nueva 'Tablas append-only — excepción documentada a audit columns nullable=False'. Patrón portable a logs estructurados, audit trails, event sourcing, telemetría. Origen: OIC v1.5 Slice 1 2026-06-04 (BitacoraError)."
|
|
7
|
+
}
|
package/reglas/arquitectura.md
CHANGED
|
@@ -274,6 +274,71 @@ Documentar en un ADR qué patrón se usa y por qué.
|
|
|
274
274
|
|
|
275
275
|
---
|
|
276
276
|
|
|
277
|
+
## Tablas append-only — excepción documentada a "audit columns nullable=False"
|
|
278
|
+
|
|
279
|
+
Algunas tablas son **append-only por diseño**: solo se crean filas (INSERT) y
|
|
280
|
+
eventualmente se eliminan por purga (DELETE). Nunca se actualizan. Ejemplos
|
|
281
|
+
típicos: logs de errores (`bitacora_error`, `error_log`), audit trails inmutables,
|
|
282
|
+
telemetría, métricas históricas, event sourcing event store, outbox pattern.
|
|
283
|
+
|
|
284
|
+
Para estas tablas la regla general del proyecto "`created_by`/`updated_by`
|
|
285
|
+
`nullable=False` en tablas transaccionales" **NO aplica**. Documentar la
|
|
286
|
+
excepción explícitamente:
|
|
287
|
+
|
|
288
|
+
1. En el **docstring de la clase modelo** (Python/Java/C#):
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
class BitacoraError(Base):
|
|
292
|
+
"""
|
|
293
|
+
Registro inmutable de un error backend o frontend.
|
|
294
|
+
|
|
295
|
+
Tabla append-only: solo se crean filas (INSERT) y se purgan por antigüedad
|
|
296
|
+
(DELETE). Nunca se actualizan. Esta invariante es fundamental para que el
|
|
297
|
+
log de errores sea un audit trail confiable.
|
|
298
|
+
|
|
299
|
+
Excepción a regla "audit columns nullable=False":
|
|
300
|
+
La convención del proyecto exige `created_by` y `updated_by` NOT NULL
|
|
301
|
+
en tablas transaccionales. BitacoraError queda exenta porque:
|
|
302
|
+
(a) Es un log del sistema, no una entidad de negocio modificable.
|
|
303
|
+
(b) No tiene `updated_at` ni `updated_by` — no hay concepto de
|
|
304
|
+
"última modificación" en una tabla append-only.
|
|
305
|
+
(c) `usuario_id` es nullable de forma intencional: los errores de
|
|
306
|
+
sesiones no autenticadas (401 antes del login) no tienen usuario.
|
|
307
|
+
"""
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
2. En el **comentario del DDL SQL**:
|
|
311
|
+
|
|
312
|
+
```sql
|
|
313
|
+
COMMENT ON TABLE bitacora_error IS
|
|
314
|
+
'Errores backend y frontend persistidos para diagnóstico. '
|
|
315
|
+
'Append-only: solo INSERT y DELETE (purga). '
|
|
316
|
+
'Sin updated_at/updated_by por diseño (excepción documentada en CONTEXTO).';
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
3. En el **GRANT** del rol de aplicación, omitir explícitamente `UPDATE`:
|
|
320
|
+
|
|
321
|
+
```sql
|
|
322
|
+
-- INSERT: para que el sistema cree entradas.
|
|
323
|
+
-- SELECT: para que el admin las consulte.
|
|
324
|
+
-- DELETE: para que el job de purga elimine antiguas.
|
|
325
|
+
-- NO UPDATE: la tabla es append-only por diseño.
|
|
326
|
+
GRANT SELECT, INSERT, DELETE ON bitacora_error TO ine_user;
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Criterios para considerar una tabla append-only legítima**:
|
|
330
|
+
|
|
331
|
+
- Toda fila representa un evento ocurrido en un momento específico (immutable in time).
|
|
332
|
+
- No hay caso de negocio que requiera modificar la fila después de creada.
|
|
333
|
+
- La única forma de "corregir" un dato erróneo es insertar una fila correctora
|
|
334
|
+
posterior (event sourcing) o purgar y re-insertar (telemetría).
|
|
335
|
+
- El borrado se hace por política de retención (antigüedad), no por revocación.
|
|
336
|
+
|
|
337
|
+
**Origen del patrón**: OIC v1.5 Slice 1 (2026-06-04). Patrón portable a cualquier
|
|
338
|
+
proyecto con logs estructurados, audit trails o event sourcing.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
277
342
|
## Split modular con compositor por herencia múltiple
|
|
278
343
|
|
|
279
344
|
Cuando un módulo backend (router, service, repository) supera ~1500 LOC en un
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"evolved": true,
|
|
3
|
+
"evolved-from": "1.8.0",
|
|
4
|
+
"evolved-at": "2026-06-04",
|
|
5
|
+
"evolved-by": "evolucionar",
|
|
6
|
+
"evolved-note": "PE-003 (verificación de flags antes de asumir servicios externos LDAP/Redis/S3) + PE-004 (sanitización PII centralizada en handlers) + PE-007 (generación de passwords legibles sin chars ambiguos). Origen: OIC v1.5 2026-06-04, bug histórico manuel.monteagudo + Slice 2 v1.5 Observabilidad."
|
|
7
|
+
}
|
package/reglas/seguridad.md
CHANGED
|
@@ -141,6 +141,147 @@ vulnerabilidad del OWASP Top 10. Las más frecuentes en este stack:
|
|
|
141
141
|
|
|
142
142
|
---
|
|
143
143
|
|
|
144
|
+
## Verificación obligatoria de flags de configuración antes de asumir servicios externos
|
|
145
|
+
|
|
146
|
+
Toda rama de código que asuma un servicio externo opcional (LDAP, Redis, S3, SMTP,
|
|
147
|
+
OIDC, Auth0, etc.) DEBE verificar el flag de configuración del proyecto ANTES de
|
|
148
|
+
tomar la decisión. Sin esta verificación, una configuración no esperada deja a los
|
|
149
|
+
usuarios atrapados en flujos rotos sin error visible.
|
|
150
|
+
|
|
151
|
+
**Anti-patrón** (caso real OIC 2026-06-04, dos bugs históricos en el mismo flujo):
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# MAL — asume LDAP siempre habilitado
|
|
155
|
+
if rol == "ADMIN":
|
|
156
|
+
hashed = hash_password(body.password)
|
|
157
|
+
else:
|
|
158
|
+
# "Para usuarios LDAP, no se almacena contraseña funcional local."
|
|
159
|
+
hashed = hash_password(uuid.uuid4().hex)
|
|
160
|
+
# ↑ Si AUTH_LDAP_ENABLED=false (caso real), el usuario queda con un hash
|
|
161
|
+
# UUID dummy que ninguna password real puede igualar → 401 perpetuo.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# MAL — rechaza no-ADMIN sin verificar si LDAP existe siquiera
|
|
166
|
+
if rol != "ADMIN":
|
|
167
|
+
raise HTTPException(400, "Los usuarios no ADMIN gestionan su contraseña en LDAP")
|
|
168
|
+
# ↑ Sin LDAP habilitado los usuarios no tienen forma de cambiar password.
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Patrón correcto**:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# BIEN — siempre verifica el flag antes de asumir el servicio
|
|
175
|
+
if rol == "ADMIN":
|
|
176
|
+
hashed = hash_password(body.password)
|
|
177
|
+
else:
|
|
178
|
+
if body.password:
|
|
179
|
+
hashed = hash_password(body.password)
|
|
180
|
+
elif not settings.AUTH_LDAP_ENABLED:
|
|
181
|
+
# Sin LDAP: generar password funcional para que el usuario pueda entrar
|
|
182
|
+
temp = _generar_temp_password()
|
|
183
|
+
hashed = hash_password(temp)
|
|
184
|
+
# devolver `temp` en la respuesta del POST UNA SOLA VEZ
|
|
185
|
+
else:
|
|
186
|
+
# LDAP habilitado: dummy OK porque el usuario se autentica fuera
|
|
187
|
+
hashed = hash_password(uuid.uuid4().hex)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Detección temprana en code review**: cualquier comentario `"para LDAP"`,
|
|
191
|
+
`"para Redis"`, `"para S3"` debe acompañarse del check del flag correspondiente.
|
|
192
|
+
Si el comentario está solo, es bug latente — el código asume un mundo que el
|
|
193
|
+
deployment puede no tener.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Sanitización PII centralizada en handlers vs refactorear N sitios
|
|
198
|
+
|
|
199
|
+
Cuando un sistema persiste logs estructurados (audit trails, error logs,
|
|
200
|
+
telemetría) y existen N sitios donde el código emite `logger.error(..., extra={...})`
|
|
201
|
+
con potencial PII en los valores, **siempre preferir sanitización centralizada en
|
|
202
|
+
el handler** sobre refactorear cada sitio individualmente.
|
|
203
|
+
|
|
204
|
+
**Decisión arquitectónica** (caso OIC v1.5 Slice 2, 2026-06-04):
|
|
205
|
+
|
|
206
|
+
| Opción | Esfuerzo | Cobertura futura | Mantenimiento |
|
|
207
|
+
|---|---|---|---|
|
|
208
|
+
| Refactorear N sitios uno por uno | O(N) turnos | Solo lo refactorizado | Cada `logger.error` nuevo requiere auditoría |
|
|
209
|
+
| Sanitización centralizada en handler | O(1) turno | Todos los sitios + futuros automáticamente | Cero auditoría manual al agregar nuevos `logger.error` |
|
|
210
|
+
|
|
211
|
+
**Patrón portable**:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
import re
|
|
215
|
+
|
|
216
|
+
_PATRON_SENSIBLE = re.compile(
|
|
217
|
+
r'(password|token|secret|jwt|authorization|cookie|api[_-]?key)([=:])([^&\s"\']+)',
|
|
218
|
+
re.IGNORECASE,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def sanitizar_url(url: str | None) -> str | None:
|
|
222
|
+
"""Redacta query-params sensibles preservando key y separador."""
|
|
223
|
+
if not url:
|
|
224
|
+
return url
|
|
225
|
+
return _PATRON_SENSIBLE.sub(r"\1\2<redacted>", url)
|
|
226
|
+
|
|
227
|
+
def sanitizar_valor(valor: Any) -> Any:
|
|
228
|
+
"""Sanitiza recursivamente strings dentro de dict/list/tuple."""
|
|
229
|
+
if isinstance(valor, str):
|
|
230
|
+
return _PATRON_SENSIBLE.sub(r"\1\2<redacted>", valor)
|
|
231
|
+
if isinstance(valor, dict):
|
|
232
|
+
return {k: sanitizar_valor(v) for k, v in valor.items()}
|
|
233
|
+
if isinstance(valor, (list, tuple)):
|
|
234
|
+
return [sanitizar_valor(item) for item in valor]
|
|
235
|
+
return valor
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Regla del regex**:
|
|
239
|
+
- Grupo 1 (key) y grupo 2 (`=` o `:`) se preservan → mantiene legibilidad
|
|
240
|
+
(`password=<redacted>` en vez de `<redacted>`).
|
|
241
|
+
- Grupo 3 (valor) se reemplaza por `<redacted>`.
|
|
242
|
+
- Boundary del valor: `&`, whitespace, comilla, paréntesis.
|
|
243
|
+
- `api_key` y `api-key` ambos matchean con `api[_-]?key`.
|
|
244
|
+
|
|
245
|
+
**Aplicabilidad**: handlers de logging custom, middleware HTTP que redactan headers,
|
|
246
|
+
sanitización de cookies en logs, redactor de stack traces, error reporters
|
|
247
|
+
(Sentry-style). Defensa en profundidad sin tocar el código del caller.
|
|
248
|
+
|
|
249
|
+
**Limitación documentada**: el regex opera sobre strings; NO sanitiza claves cuyo
|
|
250
|
+
nombre no esté en la lista (no detecta `contraseña` en español, `pwd`, `mot_de_passe`).
|
|
251
|
+
Mantener el regex extensible vía configuración si el proyecto opera en múltiples idiomas.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Generación de tokens y passwords legibles (sin caracteres ambiguos)
|
|
256
|
+
|
|
257
|
+
Para passwords temporales (reset por admin, primer login, recuperación) que el
|
|
258
|
+
usuario debe transcribir verbalmente o desde papel, usar alfabeto SIN caracteres
|
|
259
|
+
ambiguos: `0` (cero) vs `O` (o mayúscula), `1` (uno) vs `l` (L minúscula) vs `I`
|
|
260
|
+
(i mayúscula).
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
import secrets
|
|
264
|
+
|
|
265
|
+
# 54 chars del alfabeto seguro (sin 0, O, 1, l, I)
|
|
266
|
+
_ALFABETO_LEGIBLE = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
|
267
|
+
|
|
268
|
+
def generar_password_temporal(longitud: int = 12) -> str:
|
|
269
|
+
"""12 chars → ~71 bits de entropía. Suficiente para temporal que debe
|
|
270
|
+
cambiarse en primer login. NUNCA persistir en texto plano; el caller
|
|
271
|
+
la hashea con bcrypt y la devuelve UNA SOLA VEZ en la respuesta del POST."""
|
|
272
|
+
return "".join(secrets.choice(_ALFABETO_LEGIBLE) for _ in range(longitud))
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**NO usar** `secrets.token_urlsafe()` para passwords visibles al humano: incluye
|
|
276
|
+
`-` y `_` que pueden confundir al transcribir. Reservar `token_urlsafe()` para
|
|
277
|
+
tokens de sesión, CSRF, magic links — strings que no se leen ni transcriben.
|
|
278
|
+
|
|
279
|
+
**Para tokens de uso programático** (API keys, session IDs, refresh tokens):
|
|
280
|
+
`secrets.token_urlsafe(32)` (32 bytes → 256 bits) o `secrets.token_hex(32)`. Sin
|
|
281
|
+
restricción de alfabeto porque el usuario nunca los teclea manualmente.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
144
285
|
## Checklist antes de merge a main
|
|
145
286
|
|
|
146
287
|
- [ ] No hay secrets hardcodeados (revisar con `git grep -i "password\|secret\|token\|key"`)
|
|
@@ -149,3 +290,6 @@ vulnerabilidad del OWASP Top 10. Las más frecuentes en este stack:
|
|
|
149
290
|
- [ ] Sin uso de `eval`, `exec`, `shell=True`
|
|
150
291
|
- [ ] Logs sin datos sensibles
|
|
151
292
|
- [ ] Dependencias nuevas auditadas
|
|
293
|
+
- [ ] Cualquier rama que asuma servicio externo (LDAP/Redis/S3) verifica su flag de config
|
|
294
|
+
- [ ] Sanitización PII centralizada en handlers (no diseminada por N sitios)
|
|
295
|
+
- [ ] Passwords temporales generadas con alfabeto sin chars ambiguos
|
|
@@ -33,7 +33,8 @@ const args = new Set(process.argv.slice(2));
|
|
|
33
33
|
const DRY = args.has('--dry-run');
|
|
34
34
|
const SOLO_INV = args.has('--inventario');
|
|
35
35
|
const SOLO_SAL = args.has('--salud');
|
|
36
|
-
const
|
|
36
|
+
const SOLO_LLMS = args.has('--llms');
|
|
37
|
+
const AMBOS = !SOLO_INV && !SOLO_SAL && !SOLO_LLMS;
|
|
37
38
|
|
|
38
39
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
39
40
|
|
|
@@ -427,6 +428,57 @@ function generarSalud() {
|
|
|
427
428
|
return lines.join('\n');
|
|
428
429
|
}
|
|
429
430
|
|
|
431
|
+
// ── llms.txt (convención llmstxt.org) ─────────────────────────────────
|
|
432
|
+
// Índice raíz LLM-legible para descubrimiento externo por herramientas/agentes
|
|
433
|
+
// que esperan el estándar. Cifras sincronizadas con INVENTARIO (mismo cómputo)
|
|
434
|
+
// para que el gate de release no detecte drift. Adoptado de obsidian-second-brain
|
|
435
|
+
// (análisis temp/ 2026-06-05); el resto de ese repo era redundante con swl-ses.
|
|
436
|
+
function generarLlmsTxt() {
|
|
437
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(RAIZ, 'package.json'), 'utf8'));
|
|
438
|
+
const agentes = recolectarAgentes().length;
|
|
439
|
+
const skills = recolectarSkills().length;
|
|
440
|
+
const hooks = recolectarHooks().length;
|
|
441
|
+
const comandos = contarArchivos(path.join(RAIZ, 'comandos', 'swl'), '.md');
|
|
442
|
+
const reglasBase = contarArchivos(path.join(RAIZ, 'reglas'), '.md');
|
|
443
|
+
const reglasLang = fs.readdirSync(path.join(RAIZ, 'reglas', 'lenguajes'))
|
|
444
|
+
.filter(d => fs.statSync(path.join(RAIZ, 'reglas', 'lenguajes', d)).isDirectory())
|
|
445
|
+
.reduce((acc, d) => acc + contarArchivos(path.join(RAIZ, 'reglas', 'lenguajes', d), '.md'), 0);
|
|
446
|
+
|
|
447
|
+
const L = [];
|
|
448
|
+
L.push('# swl-ses (@saulwade/swl-ses)');
|
|
449
|
+
L.push('');
|
|
450
|
+
L.push(`> Sistema de ingeniería de software auto-evolutivo multi-runtime polyglot (SDLC completo), distribuido como paquete npm y plugin de Claude Code. ${agentes} agentes, ${skills} habilidades, ${comandos} comandos, ${reglasBase} reglas base y ${hooks} hooks. Soporta 11 lenguajes y 7 runtimes (Claude Code, OpenClaude, OpenCode, Gemini, Cursor, Codex, Copilot). Versión ${pkg.version}.`);
|
|
451
|
+
L.push('');
|
|
452
|
+
L.push('Archivo generado por `node scripts/generar-inventario.js` — no editar a mano. Las cifras se sincronizan con INVENTARIO.md en cada regeneración.');
|
|
453
|
+
L.push('');
|
|
454
|
+
L.push('## Documentación');
|
|
455
|
+
L.push('');
|
|
456
|
+
L.push('- [README](README.md): overview público y quickstart');
|
|
457
|
+
L.push('- [Manual de uso](MANUAL_USO.md): manual operacional completo');
|
|
458
|
+
L.push('- [Comandos](COMANDOS.md): referencia detallada de cada comando `/swl:*`');
|
|
459
|
+
L.push('- [Agentes](AGENTS.md): catálogo de agentes con capacidades');
|
|
460
|
+
L.push('- [Inventario](INVENTARIO.md): conteos oficiales de todos los componentes');
|
|
461
|
+
L.push('- [Instalación](INSTALACION.md): instalación, perfiles y configuración');
|
|
462
|
+
L.push('- [Instrucciones del proyecto](CLAUDE.md): convenciones, stack y reglas');
|
|
463
|
+
L.push('');
|
|
464
|
+
L.push('## Componentes');
|
|
465
|
+
L.push('');
|
|
466
|
+
L.push(`- ${agentes} agentes especializados en \`agentes/\` (orquestación, implementación por stack, revisión, calidad, diseño)`);
|
|
467
|
+
L.push(`- ${skills} habilidades cargables bajo demanda en \`habilidades/\` (conocimiento operacional con divulgación progresiva)`);
|
|
468
|
+
L.push(`- ${comandos} comandos \`/swl:*\` en \`comandos/swl/\` (ciclo GSD, calidad, release, diagnóstico)`);
|
|
469
|
+
L.push(`- ${reglasBase} reglas base + ${reglasLang} reglas por lenguaje en \`reglas/\` (políticas obligatorias por matcher)`);
|
|
470
|
+
L.push(`- ${hooks} hooks en \`hooks/\` (telemetría, validación, seguridad; zero-deps, escrituras atómicas)`);
|
|
471
|
+
L.push('');
|
|
472
|
+
L.push('## Opcional');
|
|
473
|
+
L.push('');
|
|
474
|
+
L.push('- [CHANGELOG](CHANGELOG.md): historial de versiones');
|
|
475
|
+
L.push('- [Índice de ADRs](.planning/adrs/README.md): decisiones de arquitectura');
|
|
476
|
+
L.push('- [Variables de entorno](docs/variables-entorno.md): configuración opt-in');
|
|
477
|
+
L.push('');
|
|
478
|
+
|
|
479
|
+
return L.join('\n');
|
|
480
|
+
}
|
|
481
|
+
|
|
430
482
|
// ── Ejecutar ──────────────────────────────────────────────────────────
|
|
431
483
|
|
|
432
484
|
if (AMBOS || SOLO_INV) {
|
|
@@ -450,3 +502,14 @@ if (AMBOS || SOLO_SAL) {
|
|
|
450
502
|
console.log('SALUD.md generado correctamente.');
|
|
451
503
|
}
|
|
452
504
|
}
|
|
505
|
+
|
|
506
|
+
if (AMBOS || SOLO_LLMS) {
|
|
507
|
+
const llms = generarLlmsTxt();
|
|
508
|
+
if (DRY) {
|
|
509
|
+
console.log('\n=== llms.txt (dry-run) ===\n');
|
|
510
|
+
console.log(llms);
|
|
511
|
+
} else {
|
|
512
|
+
atomicWriteSync(path.join(RAIZ, 'llms.txt'), llms, 'utf8');
|
|
513
|
+
console.log('llms.txt generado correctamente.');
|
|
514
|
+
}
|
|
515
|
+
}
|
package/scripts/instalador.js
CHANGED
|
@@ -210,10 +210,30 @@ async function install(opciones) {
|
|
|
210
210
|
// 2b. Filtrar reglas de lenguaje por stack detectado
|
|
211
211
|
// El stack se detecta siempre que: hay reglas de lenguaje en el perfil, O
|
|
212
212
|
// se ejecuta con --force (actualización) para poder limpiar las ya instaladas.
|
|
213
|
+
// FIX (thrashing de contexto en subagentes): en scope GLOBAL las reglas
|
|
214
|
+
// por-lenguaje NO se instalan en ~/.claude/rules/. Global no tiene un proyecto
|
|
215
|
+
// que detectar, así que se instalarían los 8 lenguajes (~69K tokens) y
|
|
216
|
+
// saturarían la ventana de todo subagente que herede ~/.claude/rules/. Las
|
|
217
|
+
// reglas por-lenguaje viven project-scoped, donde el stack-filter sí aplica.
|
|
218
|
+
if (esGlobal) {
|
|
219
|
+
const antesGlobal = resolucion.archivos.length;
|
|
220
|
+
resolucion.archivos = resolucion.archivos.filter(
|
|
221
|
+
a => !(a.rutaRelativa && a.rutaRelativa.startsWith('reglas/lenguajes/'))
|
|
222
|
+
);
|
|
223
|
+
const excluidasGlobal = antesGlobal - resolucion.archivos.length;
|
|
224
|
+
if (excluidasGlobal > 0) {
|
|
225
|
+
console.log(
|
|
226
|
+
`\n[stack] Scope global: ${excluidasGlobal} regla(s) por-lenguaje excluidas ` +
|
|
227
|
+
'(se instalan project-scoped, filtradas por stack; no en ~/.claude/rules/ ' +
|
|
228
|
+
'para no saturar el contexto de subagentes).'
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
213
233
|
const tieneReglasLenguaje = resolucion.archivos.some(
|
|
214
234
|
a => a.rutaRelativa && a.rutaRelativa.startsWith('reglas/lenguajes/')
|
|
215
235
|
);
|
|
216
|
-
const necesitaStack = (tieneReglasLenguaje || force) && !allLangs;
|
|
236
|
+
const necesitaStack = (tieneReglasLenguaje || force) && !allLangs && !esGlobal;
|
|
217
237
|
|
|
218
238
|
let stackDetectado = null;
|
|
219
239
|
|
|
@@ -224,7 +244,7 @@ async function install(opciones) {
|
|
|
224
244
|
// FIX v1.6.6: si el usuario pasa --all-langs pero el perfil actual no incluye
|
|
225
245
|
// reglas/lenguajes/, el flag se ignora silenciosamente. Antes la única pista
|
|
226
246
|
// era el conteo final de archivos. Ahora emitimos warning explícito.
|
|
227
|
-
if (allLangs && !tieneReglasLenguaje) {
|
|
247
|
+
if (allLangs && !tieneReglasLenguaje && !esGlobal) {
|
|
228
248
|
console.log(
|
|
229
249
|
'\n[stack] Aviso: --all-langs ignorado — el perfil actual (' +
|
|
230
250
|
(resolucion.perfil || 'core') +
|
|
@@ -269,6 +289,16 @@ async function install(opciones) {
|
|
|
269
289
|
}
|
|
270
290
|
}
|
|
271
291
|
|
|
292
|
+
// Scope global: purgar TODAS las reglas por-lenguaje preexistentes de
|
|
293
|
+
// ~/.claude/rules/ (instalaciones previas dejaban los 8 lenguajes always-loaded,
|
|
294
|
+
// saturando el contexto de subagentes). Corre en cada install global, no solo --force.
|
|
295
|
+
if (esGlobal && rutas.reglas) {
|
|
296
|
+
const purgaGlobal = limpiarReglasSinStack(rutas.reglas, new Set());
|
|
297
|
+
if (purgaGlobal.eliminados > 0) {
|
|
298
|
+
console.log(`\n[stack] Scope global: ${purgaGlobal.eliminados} subdir(s) de reglas por-lenguaje purgadas de ~/.claude/rules/ (${purgaGlobal.lenguajes.join(', ')}) — viven project-scoped.`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
272
302
|
// 4. Detectar _userland/
|
|
273
303
|
const userlandAgentes = path.join(process.cwd(), '_userland', 'agentes');
|
|
274
304
|
const userlandHabilidades = path.join(process.cwd(), '_userland', 'habilidades');
|