@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.
- package/CLAUDE.md +196 -196
- package/README.md +579 -579
- package/agentes/_propose-step.md +90 -0
- package/agentes/implementador-swl.md +2 -0
- package/agentes/orquestador-swl.md +2 -0
- package/agentes/perfilador-usuario-swl.md +14 -1
- package/bin/swl-ses.js +1 -1
- package/comandos/swl/aprobar-plan.md +3 -2
- package/comandos/swl/briefing.md +122 -0
- package/comandos/swl/compactar.md +29 -2
- package/comandos/swl/discutir-fase.md +8 -5
- package/comandos/swl/ejecutar-fase.md +6 -0
- package/comandos/swl/planear-fase.md +5 -3
- package/comandos/swl/release.md +46 -0
- package/comandos/swl/status.md +69 -0
- package/comandos/swl/verificar.md +3 -2
- package/habilidades/changelog-generator/scripts/parse-commits.js +6 -4
- package/habilidades/ejecutar-fase/SKILL.md +541 -518
- package/habilidades/planear-fase/SKILL.md +3 -2
- package/habilidades/tdd-workflow/SKILL.md +715 -713
- package/habilidades/validacion-ci-sistema/SKILL.md +17 -1
- package/hooks/calidad-pre-commit.js +5 -1
- package/hooks/check-update.js +39 -1
- package/hooks/lib/autonomia.js +208 -0
- package/hooks/lib/briefing.js +474 -0
- package/hooks/lib/propose-step.js +357 -0
- package/hooks/session-briefing.js +98 -0
- package/hooks/telemetria-skill-routing.js +100 -0
- package/instintos/autonomia.yaml +27 -0
- package/llms.txt +4 -4
- package/manifiestos/hooks-config.json +18 -0
- package/manifiestos/modulos.json +25 -3
- package/manifiestos/skills-lock.json +14 -14
- package/package.json +93 -93
- package/plugin.json +371 -371
- package/reglas/analizar-directorios-antes-de-escribir.md +228 -0
- package/reglas/consultar-vault-primero.md +195 -0
- package/reglas/debatir-antes-de-aceptar.md +158 -0
- package/reglas/git-coauthor.md +100 -0
- package/reglas/monitor-ci.md +309 -0
- package/reglas/registro-componentes-nuevos.md +38 -10
- package/reglas/sesiones-paralelas.md +180 -0
- package/reglas/usar-code-review-graph.md +155 -0
- package/reglas/verificar-citas-normativas.md +548 -0
- package/scripts/instalador.js +52 -6
- package/scripts/lib/ci-reader.js +193 -0
- package/scripts/lib/detectar-host-swl.js +175 -0
- package/scripts/lib/evidencia-release.js +322 -0
- package/scripts/lib/gate-hooks-requires.js +249 -0
- package/scripts/lib/gate-licencias.js +212 -0
- package/scripts/lib/git-metricas.js +257 -0
- package/scripts/lib/metricas-dora.js +204 -0
- package/scripts/tui/ejecutores.js +1 -1
- package/scripts/validar-manifest.js +92 -1
- package/scripts/verificar-evolucion.js +54 -4
- package/scripts/verificar-release.js +102 -0
- package/scripts/verificar-trazabilidad.js +11 -5
- package/reglas/arquitectura.evolved.json +0 -7
- 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.
|
|
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
|
-
|
|
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
|
/**
|
package/hooks/check-update.js
CHANGED
|
@@ -22,7 +22,22 @@ const fs = require('fs');
|
|
|
22
22
|
const path = require('path');
|
|
23
23
|
const os = require('os');
|
|
24
24
|
|
|
25
|
-
|
|
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
|
+
};
|