@saulwade/swl-ses 2.0.0 → 2.1.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.
Files changed (59) hide show
  1. package/CLAUDE.md +196 -196
  2. package/README.md +579 -579
  3. package/agentes/_propose-step.md +90 -0
  4. package/agentes/implementador-swl.md +2 -0
  5. package/agentes/orquestador-swl.md +2 -0
  6. package/agentes/perfilador-usuario-swl.md +14 -1
  7. package/bin/swl-ses.js +1 -1
  8. package/comandos/swl/aprobar-plan.md +3 -2
  9. package/comandos/swl/briefing.md +122 -0
  10. package/comandos/swl/compactar.md +29 -2
  11. package/comandos/swl/discutir-fase.md +8 -5
  12. package/comandos/swl/ejecutar-fase.md +6 -0
  13. package/comandos/swl/planear-fase.md +5 -3
  14. package/comandos/swl/release.md +46 -0
  15. package/comandos/swl/status.md +69 -0
  16. package/comandos/swl/verificar.md +3 -2
  17. package/habilidades/changelog-generator/scripts/parse-commits.js +6 -4
  18. package/habilidades/ejecutar-fase/SKILL.md +541 -518
  19. package/habilidades/planear-fase/SKILL.md +3 -2
  20. package/habilidades/tdd-workflow/SKILL.md +715 -713
  21. package/habilidades/validacion-ci-sistema/SKILL.md +17 -1
  22. package/hooks/calidad-pre-commit.js +5 -1
  23. package/hooks/check-update.js +39 -1
  24. package/hooks/lib/autonomia.js +208 -0
  25. package/hooks/lib/briefing.js +474 -0
  26. package/hooks/lib/propose-step.js +357 -0
  27. package/hooks/session-briefing.js +98 -0
  28. package/hooks/telemetria-skill-routing.js +100 -0
  29. package/instintos/autonomia.yaml +27 -0
  30. package/llms.txt +4 -4
  31. package/manifiestos/hooks-config.json +18 -0
  32. package/manifiestos/modulos.json +25 -3
  33. package/manifiestos/skills-lock.json +14 -14
  34. package/package.json +93 -93
  35. package/plugin.json +371 -371
  36. package/reglas/analizar-directorios-antes-de-escribir.md +228 -0
  37. package/reglas/consultar-vault-primero.md +195 -0
  38. package/reglas/debatir-antes-de-aceptar.md +158 -0
  39. package/reglas/git-coauthor.md +100 -0
  40. package/reglas/monitor-ci.md +309 -0
  41. package/reglas/registro-componentes-nuevos.md +38 -10
  42. package/reglas/sesiones-paralelas.md +180 -0
  43. package/reglas/usar-code-review-graph.md +155 -0
  44. package/reglas/verificar-citas-normativas.md +548 -0
  45. package/scripts/instalador.js +52 -6
  46. package/scripts/lib/ci-reader.js +193 -0
  47. package/scripts/lib/detectar-host-swl.js +175 -0
  48. package/scripts/lib/evidencia-release.js +322 -0
  49. package/scripts/lib/gate-hooks-requires.js +249 -0
  50. package/scripts/lib/gate-licencias.js +212 -0
  51. package/scripts/lib/git-metricas.js +257 -0
  52. package/scripts/lib/metricas-dora.js +204 -0
  53. package/scripts/tui/ejecutores.js +1 -1
  54. package/scripts/validar-manifest.js +92 -1
  55. package/scripts/verificar-evolucion.js +54 -4
  56. package/scripts/verificar-release.js +102 -0
  57. package/scripts/verificar-trazabilidad.js +11 -5
  58. package/reglas/arquitectura.evolved.json +0 -7
  59. package/reglas/seguridad.evolved.json +0 -7
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: validacion-ci-sistema
3
3
  description: Validación de integridad del sistema SWL inspirado en ECC CI stack. Cubre validación de frontmatter de agentes, validación de skills (SKILL.md presente y no vacío), validación de hooks (sintaxis Node.js, exit codes), validación de comandos, validación de reglas, catálogo automático con métricas y script ejecutable de validación.
4
- version: "1.0.0"
4
+ version: "1.1.0"
5
5
  herramientasPermitidas: [Read, Bash]
6
6
  exclusiones:
7
7
  - "No cargar para validar el código de aplicación del usuario (tests, linting de Python/TS, cobertura) — este skill valida la integridad del sistema SWL (frontmatter de agentes, SKILL.md presentes, exit codes de hooks); para validación de código cargar `reglas/pruebas.md` o invocar `tdd-qa-swl`."
@@ -20,6 +20,22 @@ El sistema SWL crece con el tiempo. Sin validación, acumula:
20
20
  - Hooks que fallan con códigos de salida incorrectos
21
21
  - Comandos con formato inválido
22
22
 
23
+ ## Precondición — solo aplica al repo host de SWL
24
+
25
+ Este skill valida los **componentes** del sistema SWL (`agentes/`, `habilidades/`,
26
+ `comandos/swl/`, `reglas/`, `hooks/`). Solo tiene sentido en el repo que los
27
+ **hospeda** (swl-ses o un fork). En un proyecto **consumidor** (usa las
28
+ convenciones SWL vía `~/.claude/` pero no aloja los componentes), no hay nada que
29
+ validar y un score de componentes sería engañoso. El gate determinista vive en
30
+ `/swl:status salud` (Paso 0):
31
+
32
+ ```bash
33
+ node scripts/lib/detectar-host-swl.js --json
34
+ ```
35
+
36
+ Si `esHost: false`, NO ejecutar este skill: reportar que `salud` no aplica y
37
+ redirigir a los subcomandos de `/swl:status` que sí aplican al proyecto consumidor.
38
+
23
39
  ---
24
40
 
25
41
  ## Reglas de Validación por Componente
@@ -911,7 +911,11 @@ function extraerMensajeCommit(comando) {
911
911
  * @returns {boolean}
912
912
  */
913
913
  function esPrefijoExentoDeTests(mensaje) {
914
- return /^\s*(docs|chore|style)(\([^)]*\))?!?:/i.test(mensaje);
914
+ // Tolera prefijo(s) de trazabilidad de `ejecutar-fase` antes del tipo CC:
915
+ // `[T-NN]`, `[REQ-NN]`, `[F<fase>·T-NN]` (uno o más, consecutivos). Desde la
916
+ // Fase 12 los IDs se namespacean por fase (DT-IDS-NAMESPACE). El prefijo no
917
+ // cambia la semántica: un `[F12·T-03] docs(...)` sigue siendo exento de tests.
918
+ return /^\s*(?:\[(?:F\d+·)?(?:T|REQ)-\d+(?:-\d+)?\]\s*)*(docs|chore|style)(\([^)]*\))?!?:/i.test(mensaje);
915
919
  }
916
920
 
917
921
  /**
@@ -22,7 +22,22 @@ const fs = require('fs');
22
22
  const path = require('path');
23
23
  const os = require('os');
24
24
 
25
- const { compararSemver, versionRemotaParalela } = require('../scripts/lib/npm-version');
25
+ // La lib npm-version se distribuye junto con el hook (modulos.json hooks-core).
26
+ // Require tolerante: repo madre / destino copy (../scripts/lib), destino
27
+ // aplanado (./lib), o null. Antes de v2.2 este require era directo y la lib NO
28
+ // se distribuía: en TODA instalación destino el hook moría con MODULE_NOT_FOUND
29
+ // que el wrapper de settings.json traga en silencio — el aviso de nuevas
30
+ // versiones nunca llegó a otros equipos (reportado 2026-06-12). Sin la lib, el
31
+ // hook emite un diagnóstico throttled en vez de callar (anti-fallback-silencioso).
32
+ let compararSemver = null;
33
+ let versionRemotaParalela = null;
34
+ try {
35
+ ({ compararSemver, versionRemotaParalela } = require('../scripts/lib/npm-version'));
36
+ } catch (_) {
37
+ try {
38
+ ({ compararSemver, versionRemotaParalela } = require('./lib/npm-version'));
39
+ } catch (_2) { /* null — diagnosticado en el entrypoint */ }
40
+ }
26
41
 
27
42
  // ---------------------------------------------------------------------------
28
43
  // Constantes
@@ -176,6 +191,29 @@ process.stdin.on('data', chunk => { inputRaw += chunk; });
176
191
 
177
192
  process.stdin.on('end', async () => {
178
193
  try {
194
+ // Lib no disponible (instalación incompleta o versión previa al fix de
195
+ // distribución): avisar una vez por ventana de throttle en vez de morir
196
+ // en silencio. `remota` con sentinel truthy → ventana de 24h, no de 1h.
197
+ if (!versionRemotaParalela || !compararSemver) {
198
+ if (debeVerificar()) {
199
+ try {
200
+ fs.writeFileSync(flagPath(), JSON.stringify({
201
+ timestamp: Date.now(),
202
+ local: null,
203
+ remota: 'lib-no-disponible',
204
+ hayNueva: false,
205
+ notificado: 2,
206
+ }), 'utf8');
207
+ } catch { /* silencioso */ }
208
+ process.stderr.write(
209
+ '[swl-ses/check-update] lib npm-version no disponible — el aviso de ' +
210
+ 'nuevas versiones está inactivo. Reinstala con: ' +
211
+ 'npx -y @saulwade/swl-ses@latest update\n'
212
+ );
213
+ }
214
+ return;
215
+ }
216
+
179
217
  // Throttle: solo verificar cada 24h
180
218
  if (!debeVerificar()) {
181
219
  // Si el último check detectó actualización, repetir notificación (1 vez)
@@ -0,0 +1,208 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * hooks/lib/autonomia.js — Fase 13 (ADR-0037): presupuesto de autonomía.
5
+ *
6
+ * Carga el dial de autonomía por clase de riesgo desde instintos/autonomia.yaml
7
+ * y provee el auto-checkpoint mecánico que precede a una acción autónoma de clase
8
+ * cambio_reversible (reversibilidad como precondición de autonomía, D-13-05).
9
+ *
10
+ * Defaults = los controles vigentes de reglas/seguridad-agentes.md. La lib NUNCA
11
+ * relaja: valores desconocidos o clases ausentes degradan al default (más
12
+ * restrictivo). Si el yaml no existe (proyecto destino sin el archivo), usa
13
+ * DEFAULTS hardcodeados.
14
+ *
15
+ * Enforcement v1 = GUÍA leída + auto-checkpoint mecánico. NO es un gate bloqueante
16
+ * (eso es el patrón que el ADR descartó). El test de REQ-13-07 verifica el
17
+ * mecanismo del checkpoint, no la conducta del agente.
18
+ *
19
+ * Zero-deps (Node stdlib). Parser YAML local mínimo: la estructura de
20
+ * autonomia.yaml es plana y trivial, así se evita el require cross-dir
21
+ * hooks/lib → scripts/lib que se rompe en el destino aplanado.
22
+ */
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+ const { execFileSync } = require('child_process');
27
+
28
+ // Defaults = reglas/seguridad-agentes.md (este archivo NO los relaja).
29
+ const DEFAULTS = Object.freeze({
30
+ lectura_analisis: 'total',
31
+ cambio_reversible: 'con_auto_checkpoint',
32
+ migracion_auth_push_publish: 'hitl',
33
+ });
34
+
35
+ // Nivel válido por clase. Cualquier otro valor degrada a 'hitl'.
36
+ const NIVELES_VALIDOS = new Set(['total', 'con_auto_checkpoint', 'hitl']);
37
+
38
+ const YAML_PATH = ['instintos', 'autonomia.yaml'];
39
+ const CHECKPOINTS_PATH = ['.planning', 'user-profile', 'auto-checkpoints.jsonl'];
40
+
41
+ /**
42
+ * Parser mínimo de la estructura plana de autonomia.yaml:
43
+ * key: value (escalares de primer nivel)
44
+ * clases:
45
+ * subkey: value (1 nivel de anidamiento)
46
+ * Ignora comentarios (#) y líneas en blanco. Suficiente para este archivo.
47
+ */
48
+ // Claves prohibidas: evitan prototype pollution si un yaml hostil las declara.
49
+ const CLAVES_PROHIBIDAS = new Set(['__proto__', 'constructor', 'prototype']);
50
+
51
+ function _parsearYamlPlano(texto) {
52
+ const out = {};
53
+ let seccion = null;
54
+ for (const lineaRaw of String(texto).split(/\r?\n/)) {
55
+ const linea = lineaRaw.replace(/\s+#.*$/, ''); // comentario inline
56
+ if (!linea.trim() || linea.trim().startsWith('#')) continue;
57
+ const mSeccion = linea.match(/^([A-Za-z_][\w-]*)\s*:\s*$/);
58
+ if (mSeccion) {
59
+ seccion = mSeccion[1];
60
+ if (CLAVES_PROHIBIDAS.has(seccion)) { seccion = null; continue; }
61
+ out[seccion] = {};
62
+ continue;
63
+ }
64
+ const mKV = linea.match(/^(\s*)([A-Za-z_][\w-]*)\s*:\s*(.+?)\s*$/);
65
+ if (mKV) {
66
+ const indent = mKV[1].length;
67
+ const k = mKV[2];
68
+ if (CLAVES_PROHIBIDAS.has(k)) continue;
69
+ const v = mKV[3].replace(/^["'](.*)["']$/, '$1');
70
+ if (indent > 0 && seccion) {
71
+ out[seccion][k] = v;
72
+ } else {
73
+ seccion = null;
74
+ out[k] = v;
75
+ }
76
+ }
77
+ }
78
+ return out;
79
+ }
80
+
81
+ function _normalizarNivel(valor) {
82
+ return NIVELES_VALIDOS.has(valor) ? valor : 'hitl';
83
+ }
84
+
85
+ /**
86
+ * Carga el dial de autonomía. Merge que solo COMPLETA con defaults y degrada
87
+ * valores desconocidos a 'hitl' (nunca relaja).
88
+ * @param {string} baseDir - raíz del proyecto (default: cwd).
89
+ * @returns {{version?:string, defaults?:string, clases:Object}}
90
+ */
91
+ function cargarDial(baseDir) {
92
+ const ruta = path.join(baseDir || process.cwd(), ...YAML_PATH);
93
+ let parsed = {};
94
+ try {
95
+ parsed = _parsearYamlPlano(fs.readFileSync(ruta, 'utf8'));
96
+ } catch (_) {
97
+ parsed = {}; // sin archivo → solo defaults
98
+ }
99
+ const clasesYaml = parsed.clases && typeof parsed.clases === 'object' ? parsed.clases : {};
100
+ const clases = {};
101
+ for (const clase of Object.keys(DEFAULTS)) {
102
+ clases[clase] = clase in clasesYaml ? _normalizarNivel(clasesYaml[clase]) : DEFAULTS[clase];
103
+ }
104
+ return {
105
+ version: parsed.version,
106
+ defaults: parsed.defaults || 'seguridad-agentes.md',
107
+ clases,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * ¿La clase requiere auto-checkpoint antes de actuar autónomamente?
113
+ * Solo cuando el dial declara la clase como 'con_auto_checkpoint'.
114
+ * @param {object} dial
115
+ * @param {string} clase
116
+ * @returns {boolean}
117
+ */
118
+ function requiereAutoCheckpoint(dial, clase) {
119
+ return !!(dial && dial.clases && dial.clases[clase] === 'con_auto_checkpoint');
120
+ }
121
+
122
+ function _gitHead(baseDir) {
123
+ try {
124
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
125
+ cwd: baseDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
126
+ }).trim() || null;
127
+ } catch (_) {
128
+ return null;
129
+ }
130
+ }
131
+
132
+ function _archivosModificados(baseDir) {
133
+ try {
134
+ return execFileSync('git', ['status', '--porcelain'], {
135
+ cwd: baseDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
136
+ })
137
+ .split('\n').map((l) => l.slice(3).trim()).filter(Boolean);
138
+ } catch (_) {
139
+ return [];
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Registra un auto-checkpoint antes de una acción autónoma de cambio reversible.
145
+ * No hace snapshot: los commits atómicos SON la reversibilidad (seguridad-agentes.md);
146
+ * el checkpoint registra HEAD + archivos modificados como evidencia de rollback.
147
+ * Best-effort: nunca lanza (no debe bloquear la acción que protege).
148
+ * @param {string} baseDir
149
+ * @param {string} accion - descripción corta de la acción.
150
+ * @param {string} [tsISO] - timestamp inyectable para tests.
151
+ * @returns {{ts,accion,clase,gitHead,archivosModificados}}
152
+ */
153
+ const MAX_ACCION_LEN = 512; // tope para que el JSONL no crezca sin límite
154
+
155
+ function autoCheckpoint(baseDir, accion, tsISO) {
156
+ const base = baseDir || process.cwd();
157
+ const registro = {
158
+ ts: tsISO || new Date().toISOString(),
159
+ accion: String(accion || '').slice(0, MAX_ACCION_LEN),
160
+ clase: 'cambio_reversible',
161
+ gitHead: _gitHead(base),
162
+ archivosModificados: _archivosModificados(base),
163
+ };
164
+ try {
165
+ const ruta = path.join(base, ...CHECKPOINTS_PATH);
166
+ const dir = path.dirname(ruta);
167
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
168
+ fs.appendFileSync(ruta, JSON.stringify(registro) + '\n');
169
+ } catch (_) {
170
+ // persistir es best-effort; el registro se devuelve igual.
171
+ }
172
+ return registro;
173
+ }
174
+
175
+ // ─── CLI ──────────────────────────────────────────────────────────────────────
176
+
177
+ // Soporta `--accion=texto` y `--accion texto`. Un solo mecanismo de parse.
178
+ function _parseAccion(args) {
179
+ for (let j = 0; j < args.length; j++) {
180
+ const a = args[j];
181
+ if (a.startsWith('--accion=')) return a.slice('--accion='.length);
182
+ if (a === '--accion' && args[j + 1] && !args[j + 1].startsWith('--')) return args[j + 1];
183
+ }
184
+ return '';
185
+ }
186
+
187
+ function main(argv) {
188
+ const accion = _parseAccion(argv.slice(2));
189
+ const baseDir = process.cwd();
190
+ const dial = cargarDial(baseDir);
191
+ if (requiereAutoCheckpoint(dial, 'cambio_reversible')) {
192
+ const reg = autoCheckpoint(baseDir, accion || '(sin descripción)');
193
+ process.stdout.write(JSON.stringify(reg) + '\n');
194
+ }
195
+ return 0;
196
+ }
197
+
198
+ if (require.main === module) {
199
+ process.exit(main(process.argv));
200
+ }
201
+
202
+ module.exports = {
203
+ DEFAULTS,
204
+ cargarDial,
205
+ requiereAutoCheckpoint,
206
+ autoCheckpoint,
207
+ _parsearYamlPlano,
208
+ };