@saulwade/swl-ses 2.4.3 → 2.5.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 (197) hide show
  1. package/CLAUDE.md +194 -241
  2. package/README.md +600 -597
  3. package/agentes/_intent-spec.md +73 -73
  4. package/agentes/_propose-step.md +90 -90
  5. package/agentes/abogado-diablo-swl.md +145 -0
  6. package/agentes/accesibilidad-wcag-swl.md +690 -690
  7. package/agentes/arquitecto-swl.md +267 -267
  8. package/agentes/auto-evolucion-swl.md +908 -908
  9. package/agentes/backend-api-swl.md +1 -1
  10. package/agentes/backend-csharp-swl.md +420 -420
  11. package/agentes/backend-go-swl.md +390 -390
  12. package/agentes/backend-java-swl.md +281 -281
  13. package/agentes/backend-node-swl.md +1 -1
  14. package/agentes/backend-python-swl.md +1 -1
  15. package/agentes/backend-rust-swl.md +364 -364
  16. package/agentes/backend-workers-swl.md +482 -482
  17. package/agentes/cloud-infra-swl.md +509 -509
  18. package/agentes/consolidador-swl.md +541 -541
  19. package/agentes/datos-swl.md +1 -1
  20. package/agentes/depurador-swl.md +352 -352
  21. package/agentes/devops-ci-swl.md +400 -400
  22. package/agentes/disenador-ui-swl.md +569 -569
  23. package/agentes/documentador-swl.md +345 -345
  24. package/agentes/frontend-angular-swl.md +621 -621
  25. package/agentes/frontend-css-swl.md +716 -716
  26. package/agentes/frontend-react-swl.md +692 -692
  27. package/agentes/frontend-swl.md +496 -496
  28. package/agentes/frontend-tailwind-swl.md +826 -826
  29. package/agentes/gh-fix-ci-swl.md +6 -1
  30. package/agentes/implementador-swl.md +1 -1
  31. package/agentes/investigador-swl.md +432 -432
  32. package/agentes/investigador-ux-swl.md +505 -505
  33. package/agentes/llm-apps-swl.md +1 -1
  34. package/agentes/migrador-swl.md +442 -442
  35. package/agentes/mobile-android-swl.md +511 -511
  36. package/agentes/mobile-cross-swl.md +541 -541
  37. package/agentes/mobile-ios-swl.md +502 -502
  38. package/agentes/mobile-testing-swl.md +302 -302
  39. package/agentes/nemesis-auditor-swl.md +285 -285
  40. package/agentes/notificador-swl.md +1 -1
  41. package/agentes/observabilidad-swl.md +438 -438
  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 +10 -1
  50. package/agentes/revisor-angular-swl.md +278 -278
  51. package/agentes/revisor-codigo-swl.md +1 -1
  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 +1 -1
  64. package/agentes/tdd-qa-swl.md +393 -393
  65. package/bin/lib/bot-comandos.js +1 -1
  66. package/bin/swl-ses.js +6 -0
  67. package/comandos/swl/adoptar-proyecto.md +14 -2
  68. package/comandos/swl/configurar-ci.md +8 -1
  69. package/comandos/swl/deuda-codigo.md +97 -97
  70. package/comandos/swl/discutir-fase.md +22 -118
  71. package/comandos/swl/fix.md +118 -0
  72. package/comandos/swl/nuevo-proyecto.md +54 -3
  73. package/comandos/swl/predecir.md +32 -2
  74. package/comandos/swl/seguridad.md +189 -0
  75. package/comandos/swl/status.md +5 -3
  76. package/habilidades/aprendizaje-continuo/SKILL.md +3 -1
  77. package/habilidades/discutir-fase/SKILL.md +84 -81
  78. package/habilidades/discutir-fase/recursos/plantilla-contexto.md +136 -0
  79. package/habilidades/doc-sync/SKILL.md +3 -1
  80. package/habilidades/doubt-driven-review/SKILL.md +15 -1
  81. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  82. package/habilidades/estructura-proyecto-claude/SKILL.md +11 -2
  83. package/habilidades/harness-claude-code/SKILL.md +3 -1
  84. package/habilidades/instalar-sistema/SKILL.md +3 -1
  85. package/habilidades/meta-reglas-extendido/SKILL.md +92 -0
  86. package/habilidades/meta-reglas-extendido/recursos/analisis-previo-tareas-grandes.md +186 -0
  87. package/habilidades/meta-reglas-extendido/recursos/analizar-directorios-antes-de-escribir.md +235 -0
  88. package/habilidades/meta-reglas-extendido/recursos/api-diseno.md +413 -0
  89. package/habilidades/meta-reglas-extendido/recursos/arquitectura.md +491 -0
  90. package/habilidades/meta-reglas-extendido/recursos/arreglar-al-detectar.md +264 -0
  91. package/habilidades/meta-reglas-extendido/recursos/debatir-antes-de-aceptar.md +152 -0
  92. package/habilidades/meta-reglas-extendido/recursos/git-workflow.md +259 -0
  93. package/habilidades/meta-reglas-extendido/recursos/gobernanza.md +291 -0
  94. package/habilidades/meta-reglas-extendido/recursos/memoria-consolidada.md +263 -0
  95. package/habilidades/meta-reglas-extendido/recursos/seguridad-agentes.md +443 -0
  96. package/habilidades/meta-reglas-extendido/recursos/sesiones-paralelas.md +190 -0
  97. package/habilidades/meta-reglas-extendido/recursos/sin-duplicacion-reglas-globales.md +179 -0
  98. package/habilidades/meta-reglas-extendido/recursos/skills-estandar.md +394 -0
  99. package/habilidades/meta-reglas-extendido/recursos/usar-code-review-graph.md +156 -0
  100. package/habilidades/meta-reglas-extendido/recursos/usar-context7.md +236 -0
  101. package/habilidades/meta-reglas-extendido/recursos/usar-sistema-swl.md +253 -0
  102. package/habilidades/meta-reglas-extendido/recursos/verificar-citas-normativas.md +527 -0
  103. package/habilidades/meta-skills-estandar/SKILL.md +3 -1
  104. package/habilidades/nuevo-proyecto/SKILL.md +20 -3
  105. package/habilidades/php-experto/SKILL.md +10 -3
  106. package/habilidades/{filament-admin/SKILL.md → php-experto/recursos/filament-admin.md} +23 -39
  107. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
  108. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
  109. package/habilidades/proceso-debate-adversarial/recursos/personas.md +5 -4
  110. package/habilidades/proceso-ingenieria-requerimientos/SKILL.md +147 -0
  111. package/hooks/check-update.js +19 -10
  112. package/hooks/contexto-subagente.js +68 -68
  113. package/hooks/degradacion-instintos.js +1 -1
  114. package/hooks/extraccion-aprendizajes.js +2 -2
  115. package/hooks/lib/briefing.js +3 -3
  116. package/hooks/lib/nudge-tracker.js +1 -1
  117. package/hooks/lib/otlp-exporter.js +1 -1
  118. package/hooks/lib/webhook-dedup.js +1 -1
  119. package/hooks/session-briefing.js +1 -1
  120. package/llms.txt +6 -6
  121. package/manifiestos/canonical-hashes.json +713 -52
  122. package/manifiestos/hooks-config.json +469 -469
  123. package/manifiestos/invariantes-criticos.json +30 -30
  124. package/manifiestos/modulos.json +168 -135
  125. package/manifiestos/perfiles.json +0 -2
  126. package/manifiestos/skills-lock.json +49 -56
  127. package/package.json +7 -5
  128. package/plantillas/github-workflows/README.md +15 -1
  129. package/plantillas/github-workflows/swl-devsecops.yml +70 -0
  130. package/plugin.json +5 -5
  131. package/reglas/analisis-previo-tareas-grandes.md +30 -156
  132. package/reglas/analizar-directorios-antes-de-escribir.md +30 -211
  133. package/reglas/api-diseno.md +28 -398
  134. package/reglas/arquitectura.md +35 -456
  135. package/reglas/arreglar-al-detectar.md +30 -230
  136. package/reglas/debatir-antes-de-aceptar.md +30 -143
  137. package/reglas/docs.md +7 -0
  138. package/reglas/estilo-codigo.md +9 -0
  139. package/reglas/fragmentos-compartidos.md +6 -0
  140. package/reglas/git-workflow.md +44 -240
  141. package/reglas/gobernanza.md +23 -262
  142. package/reglas/memoria-consolidada.md +34 -228
  143. package/reglas/performance.md +8 -0
  144. package/reglas/pruebas.md +12 -0
  145. package/reglas/seguridad-agentes.md +37 -418
  146. package/reglas/seguridad.md +12 -0
  147. package/reglas/sesiones-paralelas.md +29 -162
  148. package/reglas/sin-duplicacion-reglas-globales.md +25 -166
  149. package/reglas/skills-estandar.md +23 -373
  150. package/reglas/usar-code-review-graph.md +31 -140
  151. package/reglas/usar-context7.md +30 -208
  152. package/reglas/usar-sistema-swl.md +47 -242
  153. package/reglas/verificar-citas-normativas.md +47 -537
  154. package/scripts/actualizar.js +253 -253
  155. package/scripts/audit-tools/auditar-relleno-inventario.js +145 -0
  156. package/scripts/auditar-clases-conocidas.js +106 -0
  157. package/scripts/bootstrap-instintos.js +2 -2
  158. package/scripts/canario-hooks.js +166 -0
  159. package/scripts/cli/configurar-ci.js +2 -1
  160. package/scripts/evidencia-valor.js +93 -0
  161. package/scripts/field-report.js +1 -1
  162. package/scripts/generar-comandos.js +143 -0
  163. package/scripts/generar-inventario.js +236 -23
  164. package/scripts/generar-matriz-lenguajes.js +1 -1
  165. package/scripts/instalador.js +15 -1
  166. package/scripts/lib/configurar-ci.js +10 -3
  167. package/scripts/lib/diary-entry.js +3 -1
  168. package/scripts/lib/drift-detector.js +1 -1
  169. package/scripts/lib/evidencia-valor.js +189 -0
  170. package/scripts/lib/expandir-targets.js +71 -71
  171. package/scripts/lib/frontmatter-md.js +63 -0
  172. package/scripts/lib/parsear-opciones.js +2 -0
  173. package/scripts/lib/prune-componentes.js +180 -0
  174. package/scripts/lib/reglas-globales-conocidas.json +16 -2
  175. package/scripts/lib/scoring-instintos.js +2 -2
  176. package/scripts/lib/toml-merge.js +204 -204
  177. package/scripts/lib/transformadores/claude.js +1 -1
  178. package/scripts/lib/transformadores/codex.js +1 -1
  179. package/scripts/lib/transformadores/copilot.js +1 -1
  180. package/scripts/lib/transformadores/cursor.js +1 -1
  181. package/scripts/lib/transformadores/gemini.js +22 -2
  182. package/scripts/lib/transformadores/opencode.js +1 -1
  183. package/scripts/mcp-server/auth.js +105 -105
  184. package/scripts/mcp-server/cache.js +106 -106
  185. package/scripts/prune.js +102 -0
  186. package/scripts/publicar.js +18 -2
  187. package/scripts/tui/pantallas/inspect.js +175 -175
  188. package/scripts/tui/pantallas/uninstall-wizard.js +210 -210
  189. package/scripts/tui/pantallas/update-wizard.js +234 -234
  190. package/scripts/tui/pantallas/welcome.js +189 -189
  191. package/habilidades/paid-media-tracking/SKILL.md +0 -269
  192. package/habilidades/paid-media-tracking/recursos/auditoria-tracking.md +0 -220
  193. package/habilidades/paid-media-tracking/recursos/google-ads-api.md +0 -215
  194. package/habilidades/tracking-measurement/SKILL.md +0 -239
  195. package/habilidades/tracking-measurement/recursos/consent-mode.md +0 -231
  196. package/habilidades/tracking-measurement/recursos/gtm-datalayer.md +0 -216
  197. package/habilidades/tracking-measurement/recursos/meta-capi.md +0 -262
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * auditar-clases-conocidas.js — Gate de clases de bug ya documentadas.
6
+ *
7
+ * "El conocimiento documentado no previene — solo los gates previenen."
8
+ * Cada clase de este gate mordió al proyecto en producción pese a estar
9
+ * escrita como regla/aprendizaje. El gate la vuelve imposible de reintroducir:
10
+ *
11
+ * C1 — Fecha UTC truncada: toISOString + slice(0,10) fecha artefactos
12
+ * "mañana" desde México (23 instancias corregidas 2026-07-08/09).
13
+ * Correcto: `toLocaleDateString('sv')`. Escape: `// fecha-utc-ok`.
14
+ * (El patrón no se escribe literal aquí: el gate escanea este archivo
15
+ * también — se auto-detectó en el publish v2.5.1.)
16
+ * C2 — Comparación byte a byte en modos --check: comparar texto leído de
17
+ * disco sin normalizar EOL revienta con CRLF tras checkout/reset de
18
+ * git (tumbó el publish v2.5.0). Un script con modo --check que lee
19
+ * texto debe normalizar `\r\n` o comparar estructuras (JSON.parse).
20
+ * Escape: `// eol-ok: <razón>`.
21
+ * C3 — Bloqueo ciego en hooks: exit(2) sin console.error deja al modelo
22
+ * sin la razón del bloqueo (el canal de exit 2 es stderr).
23
+ *
24
+ * Alcance: archivos .js versionados en scripts/, hooks/, bin/, gateway/
25
+ * (tests/ excluido: los tests DEBEN poder citar el patrón para validarlo).
26
+ * Exit: 0 limpio · 1 con hallazgos.
27
+ */
28
+
29
+ const { execFileSync } = require('child_process');
30
+ const fs = require('fs');
31
+ const path = require('path');
32
+
33
+ const RAIZ = path.resolve(__dirname, '..');
34
+
35
+ const RX_UTC = /toISOString\(\)\s*\.\s*(?:slice|substring)\s*\(\s*0\s*,\s*10\s*\)/;
36
+ const RX_ESCAPE_UTC = /fecha-utc-ok/;
37
+ const RX_ESCAPE_EOL = /eol-ok:/;
38
+
39
+ function archivosObjetivo() {
40
+ const salida = execFileSync('git', [
41
+ 'ls-files', '--',
42
+ 'scripts/*.js', 'scripts/**/*.js',
43
+ 'hooks/*.js', 'hooks/**/*.js',
44
+ 'bin/*.js',
45
+ 'gateway/*.js', 'gateway/**/*.js',
46
+ ], { cwd: RAIZ, encoding: 'utf8' });
47
+ return salida.split(/\r?\n/).filter(f => f && !f.includes('vendor/'));
48
+ }
49
+
50
+ function auditarUtc(ruta, lineas, hallazgos) {
51
+ lineas.forEach((linea, i) => {
52
+ if (RX_UTC.test(linea) && !RX_ESCAPE_UTC.test(linea)) {
53
+ hallazgos.push(`[C1 fecha-UTC] ${ruta}:${i + 1} — toISOString().slice(0,10) fecha "mañana" desde UTC-6; usar toLocaleDateString('sv') o marcar \`// fecha-utc-ok\` si la semántica UTC es deliberada`);
54
+ }
55
+ });
56
+ }
57
+
58
+ function auditarEolCheck(ruta, contenido, hallazgos) {
59
+ // Aplica solo a GENERADORES con modo --check (escriben el artefacto y en
60
+ // check comparan lo regenerado contra disco). Un --check selector de
61
+ // sub-checks (verificar-docs) no compara texto por igualdad — no aplica.
62
+ if (!contenido.includes("'--check'") && !contenido.includes('"--check"')) return;
63
+ if (!/atomicWrite|writeFileSync/.test(contenido)) return;
64
+ if (!/readFileSync\([^)]*utf-?8/i.test(contenido)) return;
65
+ const normaliza = contenido.includes('\\r\\n') || contenido.includes('\\r?\\n');
66
+ const comparaEstructura = contenido.includes('JSON.parse');
67
+ const escape = RX_ESCAPE_EOL.test(contenido);
68
+ if (!normaliza && !comparaEstructura && !escape) {
69
+ hallazgos.push(`[C2 EOL] ${ruta} — modo --check que lee texto sin normalizar \\r\\n ni comparar estructuras: reventará con CRLF tras checkout/reset de git (caso publish v2.5.0). Normalizar EOL antes de comparar o marcar \`// eol-ok: <razón>\``);
70
+ }
71
+ }
72
+
73
+ function auditarBloqueoCiego(ruta, contenido, hallazgos) {
74
+ if (!ruta.startsWith('hooks/') || ruta.startsWith('hooks/lib/')) return;
75
+ const usaExit2 = /exit\(2\)|exitCode\s*=\s*2/.test(contenido);
76
+ const escribeStderr = contenido.includes('console.error') || contenido.includes('process.stderr.write');
77
+ if (usaExit2 && !escribeStderr) {
78
+ hallazgos.push(`[C3 bloqueo-ciego] ${ruta} — usa exit 2 sin escribir a stderr (console.error/process.stderr.write): el modelo recibe el bloqueo sin la razón`);
79
+ }
80
+ }
81
+
82
+ function main() {
83
+ const hallazgos = [];
84
+ const archivos = archivosObjetivo();
85
+ for (const ruta of archivos) {
86
+ let contenido;
87
+ try {
88
+ contenido = fs.readFileSync(path.join(RAIZ, ruta), 'utf8');
89
+ } catch { continue; }
90
+ const lineas = contenido.split(/\r?\n/);
91
+ auditarUtc(ruta, lineas, hallazgos);
92
+ auditarEolCheck(ruta, contenido, hallazgos);
93
+ auditarBloqueoCiego(ruta, contenido, hallazgos);
94
+ }
95
+
96
+ if (hallazgos.length === 0) {
97
+ console.log(`[clases-conocidas] OK — ${archivos.length} archivos, 0 reincidencias de clases documentadas (C1 UTC, C2 EOL, C3 bloqueo ciego)`);
98
+ return;
99
+ }
100
+ console.error(`[clases-conocidas] ${hallazgos.length} reincidencia(s) de clases de bug documentadas:`);
101
+ for (const h of hallazgos) console.error(' ' + h);
102
+ process.exitCode = 1;
103
+ }
104
+
105
+ module.exports = { auditarUtc, auditarEolCheck, auditarBloqueoCiego };
106
+ if (require.main === module) main();
@@ -159,7 +159,7 @@ function truncate(s, n) {
159
159
  }
160
160
 
161
161
  function generarInstintos(entradas) {
162
- const hoy = new Date().toISOString().slice(0, 10);
162
+ const hoy = new Date().toLocaleDateString('sv');
163
163
  return entradas.map((e, idx) => {
164
164
  const { confidence, tags } = detectarConfidence(e.seccion);
165
165
  const pattern = truncate(e.titulo, 200);
@@ -190,7 +190,7 @@ function generarInstintos(entradas) {
190
190
  }
191
191
 
192
192
  function serializarYaml(instintos) {
193
- const fechaActual = new Date().toISOString().slice(0, 10);
193
+ const fechaActual = new Date().toLocaleDateString('sv');
194
194
  const header = `# Instintos del Proyecto — SWL-SES
195
195
  # Generado automáticamente por scripts/bootstrap-instintos.js desde .planning/APRENDIZAJES.md
196
196
  # Formato estandarizado v1.0
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * canario-hooks.js — Canario de hooks: prueba que cada hook es viable y visible.
6
+ *
7
+ * Clase que cierra: "hook que funciona perfecto y nadie ve". Dos muertes
8
+ * silenciosas documentadas: (a) check-update requería libs de scripts/lib/ no
9
+ * distribuidas — MODULE_NOT_FOUND tragado por el wrapper en TODA instalación
10
+ * destino desde su creación; (b) avisos emitidos por stderr con exit 0 (canal
11
+ * invisible). La (b) la cubre auditar-clases-conocidas C3; este canario cubre
12
+ * la (a) y la ejecución real.
13
+ *
14
+ * Modo default (rápido — test:all):
15
+ * Por cada hook ejecutable de hooks/*.js, extrae su grafo de requires
16
+ * relativos (transitivo) y verifica que todo archivo que EXISTE en el repo
17
+ * esté registrado en manifiestos/modulos.json — si no, el instalador no lo
18
+ * distribuye y el hook muere en silencio en el destino. Paths de fallback
19
+ * que no existen en el repo (patrón de require tolerante) se omiten.
20
+ *
21
+ * Modo --smoke (test:release):
22
+ * Además EJECUTA cada hook en un sandbox temporal (cwd aislado — las
23
+ * escrituras de telemetría van al sandbox) con un evento JSON mínimo por
24
+ * stdin. FALLA si el hook crashea (exit ≠ 0 y ≠ 2). Timeout (5s) es WARN
25
+ * informativo, no fallo: un hook lento no rompe la sesión (el harness lo
26
+ * corta), pero un hook que crashea al cargar es la clase (a) viva.
27
+ * Reporta tabla de visibilidad (bytes stdout/stderr) para auditoría humana.
28
+ *
29
+ * Exit: 0 limpio · 1 con fallas.
30
+ */
31
+
32
+ const fs = require('fs');
33
+ const os = require('os');
34
+ const path = require('path');
35
+ const { spawnSync } = require('child_process');
36
+
37
+ const RAIZ = path.resolve(__dirname, '..');
38
+ const SMOKE = process.argv.includes('--smoke');
39
+ const RX_REQUIRE = /require\(\s*['"](\.[^'"]+)['"]\s*\)/g;
40
+
41
+ function hooksEjecutables() {
42
+ // Forward slashes SIEMPRE: se comparan contra modulos.json (formato repo).
43
+ return fs.readdirSync(path.join(RAIZ, 'hooks'))
44
+ .filter(f => f.endsWith('.js'))
45
+ .map(f => `hooks/${f}`);
46
+ }
47
+
48
+ function archivosRegistrados() {
49
+ const m = JSON.parse(fs.readFileSync(path.join(RAIZ, 'manifiestos', 'modulos.json'), 'utf8'));
50
+ const set = new Set();
51
+ for (const mod of Object.values(m.modulos || {})) {
52
+ for (const a of mod.archivos || []) set.add(a.replace(/\\/g, '/'));
53
+ }
54
+ return set;
55
+ }
56
+
57
+ /** Resuelve un require relativo a .js real (agrega .js / index.js). */
58
+ function resolverRequire(desdeArchivo, spec) {
59
+ const base = path.resolve(RAIZ, path.dirname(desdeArchivo), spec);
60
+ for (const cand of [base, `${base}.js`, path.join(base, 'index.js'), `${base}.json`]) {
61
+ if (fs.existsSync(cand) && fs.statSync(cand).isFile()) {
62
+ return path.relative(RAIZ, cand).replace(/\\/g, '/');
63
+ }
64
+ }
65
+ return null; // fallback de require tolerante que solo existe en el destino
66
+ }
67
+
68
+ /** Grafo transitivo de requires relativos que existen en el repo. */
69
+ function grafoRequires(archivoInicial) {
70
+ const vistos = new Set();
71
+ const pila = [archivoInicial];
72
+ while (pila.length > 0) {
73
+ const actual = pila.pop();
74
+ if (vistos.has(actual)) continue;
75
+ vistos.add(actual);
76
+ let contenido;
77
+ try { contenido = fs.readFileSync(path.join(RAIZ, actual), 'utf8'); } catch { continue; }
78
+ for (const m of contenido.matchAll(RX_REQUIRE)) {
79
+ const resuelto = resolverRequire(actual, m[1]);
80
+ if (resuelto && !vistos.has(resuelto)) pila.push(resuelto);
81
+ }
82
+ }
83
+ vistos.delete(archivoInicial);
84
+ return [...vistos];
85
+ }
86
+
87
+ function verificarDistribucion(hooks, registrados, fallas) {
88
+ for (const hook of hooks) {
89
+ if (!registrados.has(hook)) {
90
+ fallas.push(`${hook} — el hook mismo no está registrado en modulos.json (no se distribuye)`);
91
+ }
92
+ for (const dep of grafoRequires(hook)) {
93
+ if (dep.endsWith('.json')) continue; // datos empaquetados aparte
94
+ if (!registrados.has(dep)) {
95
+ fallas.push(`${hook} — requiere ${dep}, que existe en el repo pero NO está en modulos.json: en el destino el require lanza MODULE_NOT_FOUND y el wrapper lo traga (clase check-update)`);
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ const EVENTO_MINIMO = JSON.stringify({
102
+ session_id: 'canario-smoke',
103
+ hook_event_name: 'PostToolUse',
104
+ tool_name: 'Write',
105
+ tool_input: { file_path: 'canario.md', content: 'canario' },
106
+ tool_response: { success: true },
107
+ });
108
+
109
+ function smokeEjecutar(hooks, fallas) {
110
+ const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), 'swl-canario-'));
111
+ const tabla = [];
112
+ try {
113
+ for (const hook of hooks) {
114
+ const r = spawnSync(process.execPath, [path.join(RAIZ, hook)], {
115
+ cwd: sandbox,
116
+ input: EVENTO_MINIMO,
117
+ encoding: 'utf8',
118
+ timeout: 5000,
119
+ env: { ...process.env, SWL_CANARIO_SMOKE: '1' },
120
+ });
121
+ const nombre = path.basename(hook);
122
+ if (r.error && r.error.code === 'ETIMEDOUT') {
123
+ tabla.push(` [WARN timeout] ${nombre} — >5s con evento mínimo (no rompe la sesión, pero revisar)`);
124
+ continue;
125
+ }
126
+ const exit = r.status;
127
+ if (exit !== 0 && exit !== 2) {
128
+ const stderr = (r.stderr || '').split(/\r?\n/).find(l => l.trim()) || 'sin stderr';
129
+ fallas.push(`${hook} — crasheó con exit ${exit} ante evento mínimo: ${stderr.slice(0, 140)}`);
130
+ continue;
131
+ }
132
+ const out = (r.stdout || '').length;
133
+ const err = (r.stderr || '').length;
134
+ tabla.push(` [ok exit ${exit}] ${nombre} — stdout:${out}B stderr:${err}B${exit === 0 && out === 0 && err > 0 ? ' ← solo stderr con exit 0: si esto es un aviso al usuario, es invisible (C3-runtime)' : ''}`);
135
+ }
136
+ } finally {
137
+ try { fs.rmSync(sandbox, { recursive: true, force: true }); } catch { /* mejor esfuerzo */ }
138
+ }
139
+ return tabla;
140
+ }
141
+
142
+ function main() {
143
+ const hooks = hooksEjecutables();
144
+ const registrados = archivosRegistrados();
145
+ const fallas = [];
146
+
147
+ verificarDistribucion(hooks, registrados, fallas);
148
+
149
+ let tabla = [];
150
+ if (SMOKE) tabla = smokeEjecutar(hooks, fallas);
151
+
152
+ if (tabla.length > 0) {
153
+ console.log('[canario-hooks] Visibilidad por hook (smoke en sandbox):');
154
+ for (const l of tabla) console.log(l);
155
+ }
156
+ if (fallas.length === 0) {
157
+ console.log(`[canario-hooks] OK — ${hooks.length} hooks: grafo de requires distribuido completo${SMOKE ? ' + smoke sin crashes' : ''}`);
158
+ return;
159
+ }
160
+ console.error(`[canario-hooks] ${fallas.length} falla(s):`);
161
+ for (const f of fallas) console.error(' - ' + f);
162
+ process.exitCode = 1;
163
+ }
164
+
165
+ module.exports = { grafoRequires, resolverRequire };
166
+ if (require.main === module) main();
@@ -6,7 +6,7 @@
6
6
  * relativos al proyecto (rotos downstream; ver docs/invocacion-cli-cross-scope.md).
7
7
  *
8
8
  * Ops (todas imprimen el resultado como JSON para que el comando lo inspeccione):
9
- * init [--no-security] [--no-ci] [--with-release-please] [--dry-run] [--force]
9
+ * init [--no-security] [--no-ci] [--with-release-please] [--with-devsecops] [--dry-run] [--force]
10
10
  * status (default)
11
11
  * uninstall [--confirmar]
12
12
  */
@@ -21,6 +21,7 @@ function configurarCi(opciones = {}) {
21
21
  withSecurity: !opciones['no-security'],
22
22
  withCi: !opciones['no-ci'],
23
23
  withReleasePlease: !!opciones['with-release-please'],
24
+ withDevsecops: !!opciones['with-devsecops'],
24
25
  dryRun: !!opciones['dry-run'],
25
26
  force: !!opciones.force,
26
27
  });
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * evidencia-valor.js — CLI `swl-ses valor`: panel de evidencia de valor del
6
+ * harness en el proyecto downstream actual.
7
+ *
8
+ * Uso:
9
+ * swl-ses valor # panel legible (ventana 30 días)
10
+ * swl-ses valor --dias 90 # ventana distinta
11
+ * swl-ses valor --json # salida JSON para tooling
12
+ *
13
+ * Reglas duras del panel (docs/evidencia-valor.md):
14
+ * - Cada métrica declara su PROCEDENCIA (git / GitHub / hook-event) y su
15
+ * CARÁCTER (evidencia | proxy | contexto). Nunca scores autodeclarados.
16
+ * - Todo se lee LOCAL. El repo madre no lee estos datos.
17
+ */
18
+
19
+ const { panelEvidencia } = require('./lib/evidencia-valor.js');
20
+
21
+ function main() {
22
+ const argv = process.argv.slice(2);
23
+ const json = argv.includes('--json');
24
+ const idx = argv.indexOf('--dias');
25
+ const ventanaDias = idx !== -1 ? parseInt(argv[idx + 1], 10) || 30 : 30;
26
+
27
+ const p = panelEvidencia(process.cwd(), { ventanaDias });
28
+
29
+ if (json) {
30
+ console.log(JSON.stringify(p, null, 2));
31
+ return;
32
+ }
33
+
34
+ console.log(`Evidencia de valor — ventana ${p.ventanaDias} días (${p.generado})`);
35
+ console.log(` ${p.frontera}`);
36
+ console.log('');
37
+
38
+ const i = p.intercepciones;
39
+ console.log('1. Intercepciones de gates [evidencia · hook-event]');
40
+ if (!i.disponible) {
41
+ console.log(` no disponible — ${i.razon}`);
42
+ } else {
43
+ console.log(` intervenciones: ${i.intervenciones} de ${i.totalEventos} eventos`);
44
+ const tipos = Object.entries(i.porTipo).sort((a, b) => b[1] - a[1]).slice(0, 6);
45
+ for (const [t, n] of tipos) console.log(` - ${t}: ${n}`);
46
+ }
47
+ console.log('');
48
+
49
+ const d = p.defectos;
50
+ console.log('2. Presión de defectos [proxy · git log]');
51
+ if (!d.disponible) {
52
+ console.log(` no disponible — ${d.razon}`);
53
+ } else {
54
+ console.log(` commits: ${d.commits} | fix: ${d.fixes} | feat: ${d.feats} | ratio fix/feat: ${d.ratioFixFeat ?? 'n/a'}`);
55
+ if (d.escapes.disponible) {
56
+ console.log(` escapes: ${d.escapes.fixesTrasUltimaVerificacion} fix tras la última verificación (${d.escapes.ultimaVerificacion}) — ${d.escapes.nota}`);
57
+ } else {
58
+ console.log(` escapes: no disponible — ${d.escapes.razon}`);
59
+ }
60
+ }
61
+ console.log('');
62
+
63
+ const dc = p.doraCi;
64
+ console.log('3. DORA-lite + CI [evidencia de tendencia · git + GitHub]');
65
+ if (!dc.disponible) {
66
+ console.log(` no disponible — ${dc.razon}`);
67
+ } else {
68
+ const dora = dc.dora;
69
+ const nd = m => `n/d (${(m && m.razon) || 'sin datos'})`;
70
+ const df = dora.deploymentFrequency;
71
+ console.log(` deployment frequency: ${df && df.disponible !== false && df.valor != null ? `${df.valor} deploys/semana [${df.clasificacion}]` : nd(df)}`);
72
+ const lt = dora.leadTime;
73
+ console.log(` lead time: ${lt && lt.disponible !== false && lt.p50Horas != null ? `p50 ${(+lt.p50Horas).toFixed(1)}h / p95 ${(+lt.p95Horas).toFixed(1)}h [${lt.clasificacion}]` : nd(lt)}`);
74
+ const cfr = dora.changeFailureRate;
75
+ console.log(` change failure rate: ${cfr && cfr.disponible !== false && cfr.valor != null ? `${cfr.valor}% [${cfr.clasificacion || ''}]` : nd(cfr)}`);
76
+ const mt = dora.mttr;
77
+ console.log(` mttr: ${mt && mt.disponible !== false && mt.mttrHoras != null ? `${mt.mttrHoras}h` : nd(mt)}`);
78
+ console.log(' (correlación con la adopción del flujo — no causalidad)');
79
+ }
80
+ console.log('');
81
+
82
+ const u = p.uso;
83
+ console.log('4. Uso del sistema [contexto · telemetría]');
84
+ if (!u.disponible) {
85
+ console.log(` no disponible — ${u.razon}`);
86
+ } else {
87
+ console.log(` invocaciones de skills: ${u.invocaciones} | top: ${u.top5.join(', ')}`);
88
+ }
89
+ console.log('');
90
+ console.log('Compartir agregados con el autor (opt-in): swl-ses field-report — ver docs/evidencia-valor.md');
91
+ }
92
+
93
+ main();
@@ -163,7 +163,7 @@ function generarReporte() {
163
163
  const sorted = (obj) => Object.entries(obj).sort((a, b) => b[1] - a[1]);
164
164
 
165
165
  console.log('# Reporte de Validación en Campo — swl-ses');
166
- console.log(`\n**Fecha**: ${new Date().toISOString().slice(0, 10)}`);
166
+ console.log(`\n**Fecha**: ${new Date().toLocaleDateString('sv')}`);
167
167
  console.log(`**Sesiones analizadas**: ${totales.sesiones}`);
168
168
  console.log(`**Proyectos**: ${totales.proyectos.size}`);
169
169
  console.log(`**Tool calls totales**: ${totales.toolCalls}`);
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * generar-comandos.js — Regenera los bloques de catálogo de comandos /swl:*
6
+ * en COMANDOS.md y AGENTS.md desde el frontmatter de comandos/swl/*.md.
7
+ *
8
+ * La fuente de verdad es el frontmatter (name, description, argument-hint);
9
+ * mantener las tablas a mano acumuló drift recurrente (auditoría 2026-07-08:
10
+ * 6 comandos ausentes en AGENTS.md, flags desactualizados en COMANDOS.md).
11
+ * Solo se reescribe el contenido entre los marcadores:
12
+ *
13
+ * <!-- BEGIN:CATALOGO-COMANDOS ... -->
14
+ * <!-- END:CATALOGO-COMANDOS -->
15
+ *
16
+ * La prosa de decisión (matriz situación→comando, gates) queda fuera de los
17
+ * marcadores y se mantiene a mano.
18
+ *
19
+ * Uso:
20
+ * node scripts/generar-comandos.js # regenera ambos archivos
21
+ * node scripts/generar-comandos.js --check # exit 2 si hay drift (uso CI)
22
+ */
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+ const { leerFrontmatter, resumir } = require('./lib/frontmatter-md.js');
27
+
28
+ const RAIZ = path.resolve(__dirname, '..');
29
+
30
+ let atomicWriteSync;
31
+ try {
32
+ ({ atomicWriteSync } = require(path.join(RAIZ, 'hooks', 'lib', 'atomic-write.js')));
33
+ } catch {
34
+ atomicWriteSync = (p, c, e) => fs.writeFileSync(p, c, e);
35
+ }
36
+
37
+ const CHECK = process.argv.includes('--check');
38
+ const MARCA_INICIO = /<!-- BEGIN:CATALOGO-COMANDOS[^>]*-->/;
39
+ const MARCA_FIN = '<!-- END:CATALOGO-COMANDOS -->';
40
+
41
+ function recolectarComandos() {
42
+ const dir = path.join(RAIZ, 'comandos', 'swl');
43
+ return fs.readdirSync(dir)
44
+ .filter(f => f.endsWith('.md'))
45
+ .map(f => {
46
+ const fm = leerFrontmatter(path.join(dir, f));
47
+ return {
48
+ archivo: f,
49
+ name: fm.name || `swl:${f.replace('.md', '')}`,
50
+ description: fm.description || '',
51
+ argumentHint: fm['argument-hint'] || fm.argument_hint || '—',
52
+ };
53
+ })
54
+ .sort((a, b) => a.name.localeCompare(b.name));
55
+ }
56
+
57
+ /** Tabla completa para COMANDOS.md: comando + argumentos/flags + descripción. */
58
+ function bloqueComandosMd(comandos) {
59
+ const L = [];
60
+ L.push(`Catálogo de los ${comandos.length} comandos, generado desde el frontmatter`);
61
+ L.push('de `comandos/swl/*.md` (fuente de verdad de flags y semántica).');
62
+ L.push('');
63
+ L.push('| Comando | Argumentos y flags | Descripción |');
64
+ L.push('|---------|--------------------|-------------|');
65
+ for (const c of comandos) {
66
+ L.push(`| \`/${c.name}\` | \`${resumir(c.argumentHint, 120)}\` | ${resumir(c.description, 260)} |`);
67
+ }
68
+ return L.join('\n');
69
+ }
70
+
71
+ /** Tabla compacta para AGENTS.md: comando + propósito. */
72
+ function bloqueAgentsMd(comandos) {
73
+ const L = [];
74
+ L.push(`Los ${comandos.length} comandos del sistema, generados desde el frontmatter`);
75
+ L.push('de `comandos/swl/*.md`. Detalle de flags: `COMANDOS.md`; operación: `MANUAL_USO.md`.');
76
+ L.push('');
77
+ L.push('| Comando | Propósito |');
78
+ L.push('|---------|-----------|');
79
+ for (const c of comandos) {
80
+ L.push(`| \`/${c.name}\` | ${resumir(c.description, 200)} |`);
81
+ }
82
+ return L.join('\n');
83
+ }
84
+
85
+ /** Reemplaza el contenido entre marcadores. Devuelve null si faltan marcadores. */
86
+ function inyectar(contenido, bloque) {
87
+ const ini = contenido.search(MARCA_INICIO);
88
+ const fin = contenido.indexOf(MARCA_FIN);
89
+ if (ini === -1 || fin === -1 || fin < ini) return null;
90
+ const finMarcaIni = contenido.indexOf('-->', ini) + 3;
91
+ return contenido.slice(0, finMarcaIni) + '\n\n' + bloque + '\n\n' + contenido.slice(fin);
92
+ }
93
+
94
+ /**
95
+ * Aplica el bloque al contenido crudo del archivo, tolerante a EOL.
96
+ *
97
+ * La comparación se hace SIEMPRE en LF: con `core.autocrlf=true` git puede
98
+ * reescribir el working tree con CRLF sin que el contenido cambie (checkout,
99
+ * reset, stash) — un compare byte a byte reporta DRIFT falso con catálogo
100
+ * idéntico (caso real: reventó el `prepublishOnly` del publish v2.5.0). La
101
+ * salida preserva el EOL dominante del archivo para no producir mixtos.
102
+ *
103
+ * @returns {{estado: 'sin-marcadores'|'sin-cambios'|'drift', salida?: string}}
104
+ */
105
+ function aplicarBloque(crudo, bloque) {
106
+ const eol = crudo.includes('\r\n') ? '\r\n' : '\n';
107
+ const actual = crudo.replace(/\r\n/g, '\n');
108
+ const nuevo = inyectar(actual, bloque);
109
+ if (nuevo === null) return { estado: 'sin-marcadores' };
110
+ if (nuevo === actual) return { estado: 'sin-cambios' };
111
+ return { estado: 'drift', salida: eol === '\r\n' ? nuevo.replace(/\n/g, '\r\n') : nuevo };
112
+ }
113
+
114
+ function procesar(archivo, bloque) {
115
+ const ruta = path.join(RAIZ, archivo);
116
+ const crudo = fs.readFileSync(ruta, 'utf8');
117
+ const r = aplicarBloque(crudo, bloque);
118
+ if (r.estado === 'sin-marcadores') {
119
+ console.error(`[generar-comandos] ${archivo}: faltan los marcadores BEGIN/END:CATALOGO-COMANDOS`);
120
+ process.exitCode = 1;
121
+ return false;
122
+ }
123
+ if (r.estado === 'sin-cambios') {
124
+ console.log(`[generar-comandos] ${archivo}: sin cambios`);
125
+ return true;
126
+ }
127
+ if (CHECK) {
128
+ console.error(`[generar-comandos] DRIFT en ${archivo}: el catálogo no coincide con el frontmatter. Regenerar con \`node scripts/generar-comandos.js\`.`);
129
+ process.exitCode = 2;
130
+ return false;
131
+ }
132
+ atomicWriteSync(ruta, r.salida, 'utf8');
133
+ console.log(`[generar-comandos] ${archivo}: catálogo regenerado (${bloque.split('\n').length} líneas)`);
134
+ return true;
135
+ }
136
+
137
+ module.exports = { inyectar, aplicarBloque };
138
+
139
+ if (require.main === module) {
140
+ const comandos = recolectarComandos();
141
+ procesar('COMANDOS.md', bloqueComandosMd(comandos));
142
+ procesar('AGENTS.md', bloqueAgentsMd(comandos));
143
+ }