@saulwade/swl-ses 1.6.7 → 1.7.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 +578 -578
- package/agentes/orquestador-swl.md +37 -1
- package/comandos/swl/adoptar-proyecto.md +28 -0
- package/comandos/swl/aprender.md +142 -3
- package/comandos/swl/claudemd.md +81 -1
- package/comandos/swl/ejecutar-fase.md +14 -0
- package/comandos/swl/nuevo-proyecto.md +29 -0
- package/habilidades/prevencion-sobreingenieria/SKILL.md +9 -5
- package/habilidades/prevencion-sobreingenieria/recursos/EXAMPLES.md +580 -0
- package/habilidades/swl-claudemd/SKILL.md +224 -2
- package/hooks/claudemd-duplicacion-detector.js +170 -0
- package/manifiestos/hooks-config.json +9 -0
- package/manifiestos/modulos.json +6 -1
- package/manifiestos/skills-lock.json +8 -8
- package/package.json +92 -92
- package/plugin.json +371 -371
- package/reglas/sin-duplicacion-reglas-globales.md +182 -0
- package/reglas/verificar-citas-temporales.md +139 -0
- package/scripts/auditar-claudemd.js +107 -1
- package/scripts/lib/detector-reglas-duplicadas.js +220 -0
- package/scripts/lib/reglas-globales-conocidas.json +112 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scripts/lib/detector-reglas-duplicadas.js
|
|
5
|
+
*
|
|
6
|
+
* Detecta inline en CLAUDE.md de proyecto contenido que duplica reglas
|
|
7
|
+
* globales de ~/.claude/rules/. Consume el catálogo declarativo en
|
|
8
|
+
* scripts/lib/reglas-globales-conocidas.json.
|
|
9
|
+
*
|
|
10
|
+
* Lógica:
|
|
11
|
+
* - Cada entrada del catálogo lista patrones regex. Si ≥ min_matches
|
|
12
|
+
* patrones aciertan en el contenido del CLAUDE.md, se marca como
|
|
13
|
+
* duplicación.
|
|
14
|
+
* - Reporta línea aproximada del primer match para que el refactor
|
|
15
|
+
* pueda señalarla.
|
|
16
|
+
* - Severidad fija WARN (no bloquea). La idea es nudge, no bloqueo.
|
|
17
|
+
*
|
|
18
|
+
* NO emite efectos secundarios — función pura input → output.
|
|
19
|
+
*
|
|
20
|
+
* Public API:
|
|
21
|
+
* cargarCatalogo(rutaJson?) → catálogo parseado
|
|
22
|
+
* detectarDuplicaciones(contenido, catalogo?, opciones?) → resultado
|
|
23
|
+
*
|
|
24
|
+
* El módulo es zero-dependency (solo fs/path standard).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const path = require('path');
|
|
29
|
+
|
|
30
|
+
const RUTA_CATALOGO_DEFAULT = path.join(__dirname, 'reglas-globales-conocidas.json');
|
|
31
|
+
|
|
32
|
+
function cargarCatalogo(rutaJson = RUTA_CATALOGO_DEFAULT) {
|
|
33
|
+
if (!fs.existsSync(rutaJson)) {
|
|
34
|
+
throw new Error(`Catálogo no encontrado: ${rutaJson}`);
|
|
35
|
+
}
|
|
36
|
+
const raw = fs.readFileSync(rutaJson, 'utf8');
|
|
37
|
+
return JSON.parse(raw);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Detecta duplicaciones de reglas globales en un CLAUDE.md.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} contenido - Contenido completo del CLAUDE.md
|
|
44
|
+
* @param {object} [catalogo] - Catálogo (default: cargar desde JSON)
|
|
45
|
+
* @param {object} [opciones]
|
|
46
|
+
* @param {boolean} [opciones.skipUserLevel=true] - No evaluar user-level
|
|
47
|
+
* @param {boolean} [opciones.esUserLevel=false] - True si rutaClaudeMd está bajo ~/.claude/
|
|
48
|
+
* @returns {object} { duplicaciones: [...], total_reglas_evaluadas: N, evaluado: bool }
|
|
49
|
+
*/
|
|
50
|
+
function detectarDuplicaciones(contenido, catalogo = null, opciones = {}) {
|
|
51
|
+
const skipUserLevel = opciones.skipUserLevel !== false;
|
|
52
|
+
const esUserLevel = opciones.esUserLevel === true;
|
|
53
|
+
|
|
54
|
+
if (skipUserLevel && esUserLevel) {
|
|
55
|
+
return {
|
|
56
|
+
evaluado: false,
|
|
57
|
+
razon: 'user-level — no evaluado (ver configuracion.ignore_si_user_level)',
|
|
58
|
+
duplicaciones: [],
|
|
59
|
+
total_reglas_evaluadas: 0,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!catalogo) catalogo = cargarCatalogo();
|
|
64
|
+
|
|
65
|
+
const minLineas = catalogo.configuracion?.min_lineas_archivo ?? 20;
|
|
66
|
+
const lineasContenido = contenido.split('\n');
|
|
67
|
+
|
|
68
|
+
if (lineasContenido.length < minLineas) {
|
|
69
|
+
return {
|
|
70
|
+
evaluado: false,
|
|
71
|
+
razon: `archivo < ${minLineas} LOC — no acumula duplicaciones`,
|
|
72
|
+
duplicaciones: [],
|
|
73
|
+
total_reglas_evaluadas: 0,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const duplicaciones = [];
|
|
78
|
+
|
|
79
|
+
for (const regla of catalogo.reglas) {
|
|
80
|
+
const matches = encontrarMatches(contenido, regla.patrones);
|
|
81
|
+
const minMatches = regla.min_matches || 1;
|
|
82
|
+
|
|
83
|
+
if (matches.length >= minMatches) {
|
|
84
|
+
// Encontrar línea aproximada del primer match
|
|
85
|
+
const lineaAprox = encontrarLineaDePrimerMatch(lineasContenido, regla.patrones);
|
|
86
|
+
|
|
87
|
+
// Si el match está cerca (±3 líneas) de una referencia consciente
|
|
88
|
+
// a la regla global (`<nombre-de-regla>.md` o `@reglas/.../<regla>.md`
|
|
89
|
+
// o `@~/.claude/rules/<regla>`), NO es duplicación — es referencia legítima.
|
|
90
|
+
if (esReferenciaConsciente(lineasContenido, lineaAprox, regla.regla_global)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
duplicaciones.push({
|
|
95
|
+
id: regla.id,
|
|
96
|
+
regla_global: regla.regla_global,
|
|
97
|
+
seccion_canonica: regla.seccion_canonica,
|
|
98
|
+
referencia_canonica: regla.referencia_canonica,
|
|
99
|
+
descripcion: regla.descripcion,
|
|
100
|
+
patrones_matcheados: matches.length,
|
|
101
|
+
min_matches_requeridos: minMatches,
|
|
102
|
+
linea_aproximada: lineaAprox,
|
|
103
|
+
remediacion: regla.remediacion_sugerida,
|
|
104
|
+
severidad: catalogo.configuracion?.severidad_default || 'WARN',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
evaluado: true,
|
|
111
|
+
duplicaciones,
|
|
112
|
+
total_reglas_evaluadas: catalogo.reglas.length,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Cuenta cuántos patrones (de una lista) tienen al menos 1 match en el contenido.
|
|
118
|
+
* No cuenta matches múltiples del mismo patrón — solo presencia/ausencia.
|
|
119
|
+
*/
|
|
120
|
+
function encontrarMatches(contenido, patronesStr) {
|
|
121
|
+
let count = 0;
|
|
122
|
+
for (const patronStr of patronesStr) {
|
|
123
|
+
try {
|
|
124
|
+
const re = new RegExp(patronStr, 'im');
|
|
125
|
+
if (re.test(contenido)) count++;
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// Patrón regex inválido — saltar silenciosamente. El catálogo es de confianza
|
|
128
|
+
// pero queremos resiliencia ante typos.
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return new Array(count); // length usable por caller
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* True si la regla global está referenciada conscientemente cerca del match,
|
|
137
|
+
* en cuyo caso NO es duplicación sino mención legítima.
|
|
138
|
+
*
|
|
139
|
+
* "Referencia consciente" = ventana ±3 líneas alrededor del match contiene
|
|
140
|
+
* el nombre del archivo de la regla global (ej. "git-coauthor.md",
|
|
141
|
+
* "@~/.claude/rules/brevedad-output.md", "@reglas/...").
|
|
142
|
+
*
|
|
143
|
+
* Esto evita marcar como duplicación bloques que:
|
|
144
|
+
* - Citan la regla global explícitamente como referencia
|
|
145
|
+
* - Documentan override con justificación
|
|
146
|
+
* - Listan las reglas obligatorias en una tabla con descripción corta
|
|
147
|
+
*/
|
|
148
|
+
function esReferenciaConsciente(lineas, lineaAprox, reglaGlobalArchivo) {
|
|
149
|
+
if (!lineaAprox || !reglaGlobalArchivo) return false;
|
|
150
|
+
// Ventana estrecha: línea exacta + adyacentes inmediatas (±1).
|
|
151
|
+
// Si el nombre del archivo de regla aparece en esa ventana, el bloque
|
|
152
|
+
// ya está nombrando explícitamente la regla → referencia consciente,
|
|
153
|
+
// no duplicación. Si está más lejos, probablemente es bloque inline
|
|
154
|
+
// que re-deriva la regla aunque la haya citado al principio.
|
|
155
|
+
const start = Math.max(0, lineaAprox - 2);
|
|
156
|
+
const end = Math.min(lineas.length, lineaAprox + 1);
|
|
157
|
+
const ventana = lineas.slice(start, end).join('\n');
|
|
158
|
+
// Buscar `<archivo>.md` literal en cualquier formato (`@~/.claude/rules/X.md`,
|
|
159
|
+
// `@reglas/X.md`, `<código>X.md</código>`, link markdown)
|
|
160
|
+
const re = new RegExp(escapeRegex(reglaGlobalArchivo), 'i');
|
|
161
|
+
return re.test(ventana);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function escapeRegex(s) {
|
|
165
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Devuelve el número de línea (1-based) del primer match encontrado entre
|
|
170
|
+
* todos los patrones. Si no hay match (no debería pasar si llegamos aquí),
|
|
171
|
+
* devuelve null.
|
|
172
|
+
*/
|
|
173
|
+
function encontrarLineaDePrimerMatch(lineas, patronesStr) {
|
|
174
|
+
for (let i = 0; i < lineas.length; i++) {
|
|
175
|
+
for (const patronStr of patronesStr) {
|
|
176
|
+
try {
|
|
177
|
+
const re = new RegExp(patronStr, 'i');
|
|
178
|
+
if (re.test(lineas[i])) {
|
|
179
|
+
return i + 1;
|
|
180
|
+
}
|
|
181
|
+
} catch (e) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Construye sugerencia de refactor para una duplicación.
|
|
191
|
+
* Output: bloque de texto listo para impresión por /swl:claudemd refactor.
|
|
192
|
+
*/
|
|
193
|
+
function construirSugerenciaRefactor(duplicacion) {
|
|
194
|
+
const partes = [];
|
|
195
|
+
partes.push(`### Duplicación detectada: ${duplicacion.id}`);
|
|
196
|
+
partes.push('');
|
|
197
|
+
partes.push(`**Regla global existente**: \`~/.claude/rules/${duplicacion.regla_global}\``);
|
|
198
|
+
partes.push(`**Sección canónica**: ${duplicacion.seccion_canonica}`);
|
|
199
|
+
if (duplicacion.linea_aproximada) {
|
|
200
|
+
partes.push(`**Línea aproximada en CLAUDE.md**: ${duplicacion.linea_aproximada}`);
|
|
201
|
+
}
|
|
202
|
+
partes.push('');
|
|
203
|
+
partes.push('**Remediación sugerida**:');
|
|
204
|
+
partes.push(duplicacion.remediacion);
|
|
205
|
+
partes.push('');
|
|
206
|
+
partes.push('**Reemplazo canónico** (si se necesita mencionarla):');
|
|
207
|
+
partes.push('```markdown');
|
|
208
|
+
partes.push(`Ver ${duplicacion.referencia_canonica}.`);
|
|
209
|
+
partes.push('```');
|
|
210
|
+
return partes.join('\n');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
cargarCatalogo,
|
|
215
|
+
detectarDuplicaciones,
|
|
216
|
+
construirSugerenciaRefactor,
|
|
217
|
+
// exports privados para tests:
|
|
218
|
+
_encontrarLineaDePrimerMatch: encontrarLineaDePrimerMatch,
|
|
219
|
+
RUTA_CATALOGO_DEFAULT,
|
|
220
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema-version": "1.0.0",
|
|
3
|
+
"description": "Catálogo de reglas globales ~/.claude/rules/ que NO deben duplicarse inline en CLAUDE.md de proyectos. Consumido por scripts/lib/detector-reglas-duplicadas.js y por scripts/auditar-claudemd.js dimensión 'duplicacion-reglas-globales'. Cada entrada lista: nombre canónico, archivo origen, sección canónica para referenciar, patrones de detección (regex), y reemplazo sugerido.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"actualizado": "2026-05-22",
|
|
6
|
+
"reglas": [
|
|
7
|
+
{
|
|
8
|
+
"id": "idioma-espanol-mexico",
|
|
9
|
+
"regla_global": "brevedad-output.md",
|
|
10
|
+
"seccion_canonica": "Idioma obligatorio: español de México",
|
|
11
|
+
"referencia_canonica": "@~/.claude/rules/brevedad-output.md § Idioma obligatorio",
|
|
12
|
+
"descripcion": "Idioma obligatorio español de México en todo contenido generado. Ya cubierto globalmente.",
|
|
13
|
+
"patrones": [
|
|
14
|
+
"\\bespañol\\s+de\\s+M[ée]xico\\b",
|
|
15
|
+
"\\bidioma\\s+obligatorio\\b",
|
|
16
|
+
"\\bacentuaci[oó]n\\s+correcta\\b",
|
|
17
|
+
"\\bgram[aá]tica\\s+normativa\\b",
|
|
18
|
+
"\\bevitar\\s+anglicismos\\b",
|
|
19
|
+
"^\\s*#+\\s*Language\\s*$",
|
|
20
|
+
"\\bdomain\\s+terms\\b.*\\bin\\s+\\*?\\*?Spanish\\*?\\*?",
|
|
21
|
+
"\\bmensajes?\\s+de\\s+commit.*siempre\\s+en\\s+español\\b"
|
|
22
|
+
],
|
|
23
|
+
"min_matches": 2,
|
|
24
|
+
"scope": "project-level",
|
|
25
|
+
"remediacion_sugerida": "Eliminar el bloque local de idioma. La regla global `~/.claude/rules/brevedad-output.md` (cargada en cada sesión SWL) ya lo cubre. Si el proyecto tiene matices propios (ej: identificadores técnicos en inglés), conservar SOLO esos matices con frase corta: \"Convenciones locales: identificadores técnicos en inglés (rutas, comandos)\"."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "brevedad-sin-aiisms",
|
|
29
|
+
"regla_global": "brevedad-output.md",
|
|
30
|
+
"seccion_canonica": "Brevedad y eficiencia de output",
|
|
31
|
+
"referencia_canonica": "@~/.claude/rules/brevedad-output.md § Brevedad y eficiencia de output",
|
|
32
|
+
"descripcion": "Brevedad, sin preámbulos de cortesía ni cierres. Ya cubierto globalmente.",
|
|
33
|
+
"patrones": [
|
|
34
|
+
"\\bsin\\s+pre[aá]mbulos\\s+de\\s+cortes[ií]a\\b",
|
|
35
|
+
"\\bnunca\\s+iniciar\\s+con\\s+[\"']?claro",
|
|
36
|
+
"\\bnunca\\s+terminar\\s+con\\s+[\"']?espero",
|
|
37
|
+
"\\bno\\s+iniciar\\s+respuestas?\\s+con\\b.*\\bcortes[ií]a"
|
|
38
|
+
],
|
|
39
|
+
"min_matches": 1,
|
|
40
|
+
"scope": "project-level",
|
|
41
|
+
"remediacion_sugerida": "Eliminar el bloque local de brevedad. La regla global ya cubre el contrato. Si el proyecto tiene matices (formato específico de revisiones, etc.) conservar solo esos."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "git-sin-coautores",
|
|
45
|
+
"regla_global": "git-coauthor.md",
|
|
46
|
+
"seccion_canonica": "Sin co-autores en commits",
|
|
47
|
+
"referencia_canonica": "@~/.claude/rules/git-coauthor.md",
|
|
48
|
+
"descripcion": "Los commits NO deben incluir Co-Authored-By de Claude/IA. Ya cubierto globalmente.",
|
|
49
|
+
"patrones": [
|
|
50
|
+
"\\bSin\\s+co-?autores\\s+en\\s+commits\\b",
|
|
51
|
+
"\\bNO\\s+incluir\\s+Co-Authored-By\\b",
|
|
52
|
+
"\\bnunca\\s+incluir\\s+[\"']?Co-Authored-By:\\s+Claude",
|
|
53
|
+
"\\bsin\\s+atribuci[oó]n\\s+a\\s+Claude\\b"
|
|
54
|
+
],
|
|
55
|
+
"min_matches": 1,
|
|
56
|
+
"scope": "project-level",
|
|
57
|
+
"remediacion_sugerida": "Eliminar el bloque local. La regla global `~/.claude/rules/git-coauthor.md` aplica a todos los proyectos del usuario."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "arreglar-al-detectar",
|
|
61
|
+
"regla_global": "arreglar-al-detectar.md",
|
|
62
|
+
"seccion_canonica": "Detectar → Informar → Arreglar en mismo turno",
|
|
63
|
+
"referencia_canonica": "@~/.claude/rules/arreglar-al-detectar.md",
|
|
64
|
+
"descripcion": "Patrón detectar-informar-arreglar en mismo turno. Ya cubierto globalmente.",
|
|
65
|
+
"patrones": [
|
|
66
|
+
"\\bdetectar\\s*[→\\-]\\s*informar\\s*[→\\-]\\s*arreglar\\b",
|
|
67
|
+
"\\bno\\s+dejar\\s+pendientes\\b.*\\barreglar\\s+en\\s+(el\\s+)?mismo\\s+turno",
|
|
68
|
+
"\\barreglar\\s+en\\s+mismo\\s+turno\\b"
|
|
69
|
+
],
|
|
70
|
+
"min_matches": 1,
|
|
71
|
+
"scope": "project-level",
|
|
72
|
+
"remediacion_sugerida": "Eliminar el bloque local. La regla global cubre el principio. Si el proyecto tiene excepciones específicas (fix urgente con incidente activo), documentarlas como excepción local sin re-derivar el principio."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "debatir-antes-de-aceptar",
|
|
76
|
+
"regla_global": "debatir-antes-de-aceptar.md",
|
|
77
|
+
"seccion_canonica": "Debatir antes de aceptar decisiones que chocan con reglas",
|
|
78
|
+
"referencia_canonica": "@~/.claude/rules/debatir-antes-de-aceptar.md",
|
|
79
|
+
"descripcion": "Debatir decisiones del usuario que chocan con reglas documentadas. Ya cubierto globalmente.",
|
|
80
|
+
"patrones": [
|
|
81
|
+
"\\bdebatir\\s+antes\\s+de\\s+aceptar\\b",
|
|
82
|
+
"\\bnunca\\s+la\\s+aceptes\\s+por\\s+reflejo\\b",
|
|
83
|
+
"\\bcita\\s+concreta\\s*\\+\\s*riesgo\\s+observable\\s*\\+\\s*alternativa\\b"
|
|
84
|
+
],
|
|
85
|
+
"min_matches": 1,
|
|
86
|
+
"scope": "project-level",
|
|
87
|
+
"remediacion_sugerida": "Eliminar el bloque local. La regla global aplica."
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "usar-context7",
|
|
91
|
+
"regla_global": "usar-context7.md",
|
|
92
|
+
"seccion_canonica": "Consultar Context7 antes de generar código con dependencias externas",
|
|
93
|
+
"referencia_canonica": "@~/.claude/rules/usar-context7.md",
|
|
94
|
+
"descripcion": "Consultar Context7 antes de agregar/usar librerías externas. Ya cubierto globalmente.",
|
|
95
|
+
"patrones": [
|
|
96
|
+
"\\bconsultar\\s+Context7\\s+antes\\b",
|
|
97
|
+
"\\bantes\\s+de\\s+agregar\\s+dependencia\\b.*\\bverificar\\b",
|
|
98
|
+
"\\bContext7\\s+obligatorio\\b"
|
|
99
|
+
],
|
|
100
|
+
"min_matches": 1,
|
|
101
|
+
"scope": "project-level",
|
|
102
|
+
"remediacion_sugerida": "Eliminar el bloque local. La regla global aplica."
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"configuracion": {
|
|
106
|
+
"severidad_default": "WARN",
|
|
107
|
+
"scope_default": "project-level",
|
|
108
|
+
"ignore_si_user_level": true,
|
|
109
|
+
"min_lineas_archivo": 20,
|
|
110
|
+
"comentario": "Solo evaluar archivos >=20 LOC (CLAUDE.md mínimos no acumulan duplicaciones). User-level (~/.claude/CLAUDE.md) NO se evalúa contra este detector porque ahí sí puede declarar preferencias personales."
|
|
111
|
+
}
|
|
112
|
+
}
|