@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.
@@ -0,0 +1,182 @@
1
+ # Regla: Sin duplicación de reglas globales en CLAUDE.md de proyecto
2
+
3
+ Esta regla es **OBLIGATORIA** y aplica al diseño/auditoría de cualquier
4
+ `CLAUDE.md` de proyecto donde el sistema SWL esté instalado. Extiende el
5
+ contrato canónico de `swl-claudemd` con un sub-caso específico: la
6
+ duplicación inline de reglas que ya viven en `~/.claude/rules/`.
7
+
8
+ ---
9
+
10
+ ## Principio
11
+
12
+ > Cuando una regla ya está documentada en `~/.claude/rules/` (carga global
13
+ > automática en todas las sesiones del usuario), **el `CLAUDE.md` del
14
+ > proyecto NO debe duplicarla inline**. La duplicación viola DRY a nivel
15
+ > de gobernanza, genera drift cuando la regla global se actualiza, y
16
+ > erosiona el contrato canónico de `CLAUDE.md` (≤200 LOC).
17
+
18
+ La regla global es **la fuente de verdad**. El `CLAUDE.md` del proyecto
19
+ puede referenciarla con `@~/.claude/rules/<archivo>.md` o agregar
20
+ **matices propios del proyecto**, pero NUNCA re-derivar el principio.
21
+
22
+ ---
23
+
24
+ ## Cuándo aplicar
25
+
26
+ OBLIGATORIO al:
27
+
28
+ - Crear un `CLAUDE.md` de proyecto nuevo (`/swl:claudemd init-project`,
29
+ `/swl:nuevo-proyecto`, `/swl:adoptar-proyecto`).
30
+ - Modificar `CLAUDE.md` de proyecto inline (`/swl:aprender` Paso 6 Tipo A,
31
+ edición manual del usuario, `Edit`/`Write` programático).
32
+ - Revisar `CLAUDE.md` (`/swl:claudemd audit`, `/swl:claudemd refactor`,
33
+ hook `claudemd-bloat-detector`, hook `claudemd-duplicacion-detector`).
34
+
35
+ NO aplica al:
36
+
37
+ - `~/.claude/CLAUDE.md` (user-level) — ahí SÍ pueden declararse
38
+ preferencias personales que parafrasean o complementan reglas globales.
39
+ - Documentación dentro del proyecto que no es `CLAUDE.md` (READMEs,
40
+ ADRs, docs de feature) — ahí no aplica el contrato canónico.
41
+
42
+ ---
43
+
44
+ ## Catálogo de reglas globales conocidas
45
+
46
+ Las siguientes reglas globales se cargan automáticamente cada sesión y
47
+ NO deben duplicarse en `CLAUDE.md` de proyecto:
48
+
49
+ | Regla global | Sección canónica | Referencia para citar |
50
+ |---|---|---|
51
+ | `brevedad-output.md` | Idioma obligatorio: español de México | `@~/.claude/rules/brevedad-output.md § Idioma obligatorio` |
52
+ | `brevedad-output.md` | Brevedad y eficiencia de output | `@~/.claude/rules/brevedad-output.md § Brevedad` |
53
+ | `git-coauthor.md` | Sin co-autores en commits | `@~/.claude/rules/git-coauthor.md` |
54
+ | `arreglar-al-detectar.md` | Detectar → Informar → Arreglar | `@~/.claude/rules/arreglar-al-detectar.md` |
55
+ | `debatir-antes-de-aceptar.md` | Debatir decisiones que chocan con reglas | `@~/.claude/rules/debatir-antes-de-aceptar.md` |
56
+ | `usar-context7.md` | Consultar Context7 antes de dependencias | `@~/.claude/rules/usar-context7.md` |
57
+
58
+ Catálogo declarativo completo:
59
+ `scripts/lib/reglas-globales-conocidas.json`. El catálogo es la fuente
60
+ de verdad consumida por el auditor (`scripts/auditar-claudemd.js`) y el
61
+ hook (`hooks/claudemd-duplicacion-detector.js`).
62
+
63
+ ---
64
+
65
+ ## Qué SÍ es legítimo en CLAUDE.md de proyecto
66
+
67
+ Lo siguiente NO se considera duplicación y es bienvenido:
68
+
69
+ 1. **Matiz local específico**: "Convenciones locales: identificadores
70
+ técnicos en inglés (rutas, comandos), prosa en español." — el matiz
71
+ es del proyecto, no la regla del idioma.
72
+ 2. **Override explícito documentado**: "Override de
73
+ `~/.claude/rules/brevedad-output.md` § Brevedad: este proyecto usa
74
+ docstrings extendidos por requerimiento legal." — el override es
75
+ explícito y nombra la regla global.
76
+ 3. **Excepción acotada con justificación**: "Excepción a
77
+ `~/.claude/rules/arreglar-al-detectar.md`: bugs cosméticos en el
78
+ módulo legacy `X/` se difieren a Q3 por congelación." — la excepción
79
+ nombra la regla y justifica.
80
+ 4. **Referencia `@`**: `@~/.claude/rules/<archivo>.md` — incluir la
81
+ regla por referencia, no inline.
82
+
83
+ ---
84
+
85
+ ## Cómo detectarlo
86
+
87
+ ### Manual
88
+
89
+ ```bash
90
+ node scripts/auditar-claudemd.js
91
+ # Buscar en el output: "duplicacion-reglas-globales"
92
+ ```
93
+
94
+ ### Automático
95
+
96
+ - **Auditor síncrono**: `scripts/auditar-claudemd.js` (dimensión 7,
97
+ severidad WARN, no bloquea).
98
+ - **Hook async**: `hooks/claudemd-duplicacion-detector.js` (PostToolUse,
99
+ emite nudge a `.planning/evolucion/nudges.jsonl`).
100
+ - **Comando**: `/swl:claudemd audit` reporta el conteo; `/swl:claudemd
101
+ refactor` propone el reemplazo concreto.
102
+
103
+ ---
104
+
105
+ ## Cómo remediar
106
+
107
+ 1. **Identificar el bloque** señalado por el auditor (línea aproximada).
108
+ 2. **Decidir**:
109
+ - ¿El bloque es 100% paráfrasis de la regla global? → **eliminar**.
110
+ - ¿El bloque agrega matiz local? → **reescribir como matiz corto**
111
+ (≤3 líneas) que nombra la regla global ("Convenciones locales:
112
+ <matiz>. Ver `@~/.claude/rules/<archivo>.md`.").
113
+ 3. **Verificar**: re-ejecutar `node scripts/auditar-claudemd.js` hasta
114
+ que la duplicación desaparezca.
115
+
116
+ ---
117
+
118
+ ## Anti-patrones
119
+
120
+ - **Copiar contenido de reglas globales "para que esté visible"**: la
121
+ regla global YA está visible — se carga automáticamente. Copiarla en
122
+ cada proyecto es deuda silenciosa.
123
+ - **Re-derivar el principio con palabras propias**: si dices "todo
124
+ contenido generado debe ser en español de México con acentos
125
+ correctos…" estás re-derivando `brevedad-output.md § Idioma`.
126
+ - **Bloque "Reglas de máxima prioridad" que repite reglas globales**:
127
+ las reglas globales SON prioritarias por construcción — duplicarlas
128
+ no aumenta su prioridad.
129
+ - **Idioma "Language" en inglés que dice "español"**: paradójico y
130
+ duplica la regla.
131
+ - **Override implícito sin nombrar la regla global**: "este proyecto
132
+ usa docstrings extendidos" sin decir que es override de qué.
133
+ - **Ignorar el WARN del auditor argumentando "es para claridad"**: el
134
+ auditor lleva razón. La regla global es suficiente claridad.
135
+
136
+ ---
137
+
138
+ ## Relación con otras reglas
139
+
140
+ - `~/.claude/rules/memoria-consolidada.md § Reglas de no-duplicación`:
141
+ principio general "un dato vive en exactamente un canal". Esta regla
142
+ lo aplica al canal CLAUDE.md.
143
+ - `~/.claude/rules/fragmentos-compartidos.md`: la analogía a nivel de
144
+ agentes (fragmentos `_*.md` se incrustan, no se duplican).
145
+ - `~/.claude/rules/skills-estandar.md`: la analogía a nivel de skills
146
+ (skill referencia otro skill, no duplica su contenido).
147
+ - `reglas/auditorias-documentales-estructurales.md § Para prosa
148
+ cuantificada en campos descriptivos`: hermana — verifica drift
149
+ cross-manifest. Esta regla verifica drift cross-fuente.
150
+
151
+ ---
152
+
153
+ ## Origen de esta regla
154
+
155
+ Sesión 2026-05-22, swl-ses v1.7.0. El usuario pegó 4 fragmentos de
156
+ `CLAUDE.md` de distintos proyectos que repetían la misma regla de idioma
157
+ "español de México" en formas distintas (lista con bullets, H3 inline,
158
+ lista numerada, sección "Language" en inglés). El análisis reveló:
159
+
160
+ - La regla ya vivía en `~/.claude/rules/brevedad-output.md § Idioma
161
+ obligatorio: español de México` desde sesiones previas.
162
+ - Cada proyecto la había re-derivado por iteraciones de
163
+ `/swl:aprender` Tipo A sin que ningún check detectara la
164
+ duplicación.
165
+ - El bloat acumulado en cada `CLAUDE.md` consumía 7-15 líneas que
166
+ podían eliminarse sin perder semántica.
167
+
168
+ Resolución: crear el catálogo declarativo + detector + dimensión 7 del
169
+ auditor + hook PostToolUse + esta regla. Release v1.7.0 MINOR.
170
+
171
+ ---
172
+
173
+ ## Checklist al modificar CLAUDE.md de proyecto
174
+
175
+ - [ ] ¿El bloque que voy a agregar parafrasea una regla global de
176
+ `~/.claude/rules/`?
177
+ - [ ] Si sí: ¿agrega matiz local genuino, o solo repite el principio?
178
+ - [ ] Si solo repite: NO agregar — la regla global ya aplica.
179
+ - [ ] Si agrega matiz: redactar en ≤3 líneas que **nombran la regla
180
+ global** (referencia con `@~/.claude/rules/<archivo>.md`).
181
+ - [ ] Tras escribir: ejecutar `node scripts/auditar-claudemd.js` y
182
+ confirmar 0 hallazgos `duplicacion-reglas-globales`.
@@ -0,0 +1,139 @@
1
+ # Regla: Comentarios temporales en código NO son verificación
2
+
3
+ Esta regla es OBLIGATORIA para todo trabajo de Claude en swl-ses. Extiende la
4
+ regla global `~/.claude/rules/verificar-citas-normativas.md § Familia 2` con
5
+ el sub-caso específico de **comentarios temporales como `// Alineado con
6
+ commits X+Y` o `// Refactorizado a vN.M.0`**.
7
+
8
+ ---
9
+
10
+ ## Principio
11
+
12
+ > Un comentario en código que afirma alineación con otro commit, versión o
13
+ > contrato (`// Alineado con commits X+Y`, `// Refactorizado a vN.M.0`,
14
+ > `# Sincronizado con backend`, `<!-- Coherente con schema X -->`) **NO es
15
+ > verificación** — es afirmación temporal sin prueba. Antes de actuar
16
+ > sobre el comentario como si fuera evidencia, verificar contra la fuente
17
+ > real con `grep` / `Read` / `git log`.
18
+
19
+ El comentario puede haberse desactualizado silenciosamente si el commit
20
+ referenciado se revirtió, nunca se aplicó, o si el refactor citado cambió
21
+ después de escribir el comentario.
22
+
23
+ ---
24
+
25
+ ## Cuándo aplicar
26
+
27
+ OBLIGATORIO verificar antes de aceptar como evidencia accionable:
28
+
29
+ - Comentarios `// Alineado con commits <SHA>+<SHA>` en código
30
+ - Comentarios `// Refactorizado a v<N.M.0>` o `// Migrado a <framework>`
31
+ - Comentarios `# Sincronizado con backend` o `# Espejo del schema X`
32
+ - Comentarios `<!-- Tipos coherentes con API vN -->`
33
+ - Cualquier afirmación de cross-stack alignment en comentario inline
34
+
35
+ NO aplica cuando:
36
+
37
+ - El comentario describe la INTENCIÓN del código (no afirma alineación)
38
+ - El comentario es una nota pendiente (`T‍ODO` / `FIXME`) con ticket asociado
39
+ - El comentario es una explicación del POR QUÉ (no del QUÉ histórico)
40
+
41
+ ---
42
+
43
+ ## Cómo verificar
44
+
45
+ Cuando un comentario afirma "alineado con X":
46
+
47
+ 1. Identificar **qué se afirma alineado**: archivo, commit, versión, schema.
48
+ 2. Localizar la fuente real:
49
+ ```bash
50
+ # Si afirma alineación con commit:
51
+ git show <SHA> -- <archivo-citado>
52
+
53
+ # Si afirma alineación con schema/types:
54
+ grep -rn "<campo-clave>" backend/ frontend/ schemas/
55
+ ```
56
+ 3. Comparar campo por campo. Si hay drift, NO confiar en el comentario.
57
+ 4. Actualizar el comentario o eliminarlo (un comentario stale es peor que
58
+ ningún comentario).
59
+
60
+ ---
61
+
62
+ ## Caso de origen — sesión SIGAF 2026-05-22
63
+
64
+ `frontend/.../contratos/models/contrato.models.ts` línea 2 decía:
65
+
66
+ ```typescript
67
+ // Alineados con el backend refactorizado (commits 284df74 + 5869ac9)
68
+ ```
69
+
70
+ Pero el backend nunca aplicó ese refactor. El frontend enviaba 5 campos
71
+ (`monto_total`, `fecha_inicio`, `fecha_termino`, `porcentaje_iva`,
72
+ `tipo_contrato_id`) que Pydantic con `extra=ignore` descartaba
73
+ silenciosamente. Los contratos se creaban sin monto ni vigencia durante
74
+ semanas, hasta que se detectó por triangulación cross-stack (modelo
75
+ SQLAlchemy + schema Pydantic + types TypeScript).
76
+
77
+ El comentario funcionó como **falso anclaje de confianza**: un reviewer
78
+ posterior lo leyó, asumió validez, y no verificó. La causa raíz era el
79
+ comentario stale, no el código.
80
+
81
+ ---
82
+
83
+ ## Anti-patrones específicos
84
+
85
+ - **Confiar en el comentario en code review**: el reviewer ve
86
+ `// Alineado con commits X+Y` y asume válido sin hacer `git show`.
87
+ - **Propagar el comentario en refactors**: al copiar código, el comentario
88
+ viaja con él y eventualmente se vuelve falso sin que nadie lo note.
89
+ - **No actualizar el comentario al revertir el commit citado**: el `git
90
+ revert` no borra comentarios — el comentario sobrevive afirmando
91
+ alineación con un commit que ya no existe en la línea de desarrollo.
92
+ - **Usar el comentario como "prueba" en debates técnicos**: "ya está
93
+ alineado con X" cuando el comentario es la única evidencia es razonar
94
+ desde una afirmación no verificada.
95
+
96
+ ---
97
+
98
+ ## Alternativas seguras a los comentarios temporales
99
+
100
+ Cuando se necesita marcar alineación cross-stack:
101
+
102
+ | En vez de | Usar |
103
+ |-----------|------|
104
+ | `// Alineado con commits X+Y` | Test de contrato que valide el shape real en CI |
105
+ | `// Refactorizado a v2.3.0` | Versión semántica en el archivo + lock |
106
+ | `# Sincronizado con backend` | Tipos generados (OpenAPI codegen, Prisma) |
107
+ | `<!-- Coherente con schema X -->` | Validador automatizado en pre-commit |
108
+
109
+ Si el comentario es la única opción (no hay test/codegen disponible),
110
+ incluir **fecha y SHA** explícitos para que la revisión periódica detecte
111
+ drift:
112
+
113
+ ```typescript
114
+ // Alineado con backend commit 284df74 al 2026-05-22.
115
+ // Re-verificar si el endpoint POST /contratos cambia su contrato.
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Relación con otras reglas
121
+
122
+ - `~/.claude/rules/verificar-citas-normativas.md § Familia 2` — regla
123
+ global hermana. Esta regla extiende el principio al sub-caso "comentarios
124
+ temporales en código".
125
+ - `reglas/arreglar-al-detectar.md` — un comentario stale detectado es
126
+ trabajo a resolver en mismo turno (actualizarlo o eliminarlo).
127
+ - `~/.claude/rules/usar-sistema-swl.md` — invocar `revisor-codigo-swl`
128
+ cuando se sospecha drift acumulado de comentarios temporales en un
129
+ módulo.
130
+
131
+ ---
132
+
133
+ ## Checklist al detectar un comentario temporal
134
+
135
+ - [ ] ¿El comentario afirma alineación con algo verificable (commit, schema, versión)?
136
+ - [ ] ¿Verifiqué la fuente real con `grep`/`Read`/`git show` ANTES de actuar?
137
+ - [ ] Si hay drift detectado, ¿actualicé/eliminé el comentario en el mismo commit?
138
+ - [ ] Si voy a propagar el código (copy-paste, refactor), ¿el comentario sigue siendo válido?
139
+ - [ ] Si el comentario es la única opción, ¿incluye fecha y SHA explícitos?
@@ -13,6 +13,7 @@
13
13
  * - Presencia de secciones canónicas (Stack, Comandos, Code style, Conventions, @references)
14
14
  * - Uso de @references (al menos 1 si el archivo supera 80 líneas)
15
15
  * - Placeholders sin reemplazar ([TBD], [TODO], [COMPLETAR])
16
+ * - Referencia Karpathy (CLAUDE.md project-level >50 LOC menciona los 4 principios o el skill)
16
17
  *
17
18
  * Uso:
18
19
  * node scripts/auditar-claudemd.js [ruta] # Audita CLAUDE.md (default: ./)
@@ -26,11 +27,24 @@
26
27
 
27
28
  const fs = require('fs');
28
29
  const path = require('path');
30
+ const os = require('os');
31
+
32
+ // Lazy: el detector vive en scripts/lib/ y puede no estar presente en
33
+ // destinos legacy. Si falta, la auditoría sigue con las 7 dimensiones
34
+ // originales sin la dimensión 8.
35
+ function cargarDetectorReglasDuplicadas() {
36
+ try {
37
+ return require('./lib/detector-reglas-duplicadas').detectarDuplicaciones;
38
+ } catch (_) {
39
+ return null;
40
+ }
41
+ }
29
42
 
30
43
  // ─── Config ───────────────────────────────────────────────────────────────
31
44
  const MAX_LINES = parseInt(process.env.SWL_CLAUDEMD_MAX_LINES, 10) || 200;
32
45
  const MAX_BULLET_CHARS =
33
46
  parseInt(process.env.SWL_CLAUDEMD_MAX_BULLET_CHARS, 10) || 1000;
47
+ const KARPATHY_MIN_LINES = 50;
34
48
 
35
49
  const SECCIONES_CANONICAS = [
36
50
  { nombre: 'Stack', regex: /^##\s+Stack/m },
@@ -39,6 +53,15 @@ const SECCIONES_CANONICAS = [
39
53
  { nombre: 'Conventions', regex: /^##\s+(Conventions|Convenciones)/im },
40
54
  ];
41
55
 
56
+ // Patrones que reconocen que el CLAUDE.md incorpora los 4 principios Karpathy
57
+ // o referencia explícita al skill prevencion-sobreingenieria.
58
+ const KARPATHY_PATTERNS = [
59
+ /karpathy/i,
60
+ /cuatro\s+principios/i,
61
+ /4\s+principios/i,
62
+ /prevencion-sobreingenieria/i,
63
+ ];
64
+
42
65
  const PLACEHOLDERS = /\[(TBD|TODO|COMPLETAR|PENDIENTE|XXX|FIXME)\]/g;
43
66
 
44
67
  // ─── Auditoría ────────────────────────────────────────────────────────────
@@ -129,6 +152,42 @@ function auditar(rutaClaudeMd) {
129
152
  });
130
153
  }
131
154
 
155
+ // 6. Referencia a los 4 principios Karpathy o al skill prevencion-sobreingenieria.
156
+ // Solo aplica a CLAUDE.md project-level con tamaño no trivial.
157
+ const tieneReferenciaKarpathy = detectarReferenciaKarpathy(contenido);
158
+ const esProjectLevel = !esRutaUserLevel(rutaClaudeMd);
159
+ if (!tieneReferenciaKarpathy && lineas.length > KARPATHY_MIN_LINES && esProjectLevel) {
160
+ hallazgos.push({
161
+ severidad: 'WARN',
162
+ regla: 'karpathy-reference',
163
+ mensaje: 'CLAUDE.md no menciona los cuatro principios Karpathy ni el skill prevencion-sobreingenieria',
164
+ sugerencia: 'Agregar la sub-sección compacta (ver `/swl:claudemd init-project` § Bloque obligatorio Karpathy)',
165
+ });
166
+ }
167
+
168
+ // 7. Duplicación de reglas globales (~/.claude/rules/) en CLAUDE.md de proyecto.
169
+ // Solo aplica a project-level (user-level puede declarar preferencias).
170
+ let resultadoDuplicaciones = { evaluado: false, duplicaciones: [], total_reglas_evaluadas: 0 };
171
+ const detectarDuplicaciones = cargarDetectorReglasDuplicadas();
172
+ if (detectarDuplicaciones) {
173
+ try {
174
+ resultadoDuplicaciones = detectarDuplicaciones(contenido, null, {
175
+ esUserLevel: !esProjectLevel,
176
+ });
177
+ for (const dup of resultadoDuplicaciones.duplicaciones) {
178
+ hallazgos.push({
179
+ severidad: dup.severidad || 'WARN',
180
+ regla: 'duplicacion-reglas-globales',
181
+ mensaje: `Bloque duplica regla global \`~/.claude/rules/${dup.regla_global}\` (línea ~${dup.linea_aproximada})`,
182
+ sugerencia: dup.remediacion,
183
+ referencia_canonica: dup.referencia_canonica,
184
+ });
185
+ }
186
+ } catch (e) {
187
+ // Si el catálogo está corrupto o falla, no bloqueamos la auditoría.
188
+ }
189
+ }
190
+
132
191
  // ─── Veredicto ───────────────────────────────────────────────────────────
133
192
  const tieneError = hallazgos.some(h => h.severidad === 'ERROR');
134
193
  const tieneWarn = hallazgos.some(h => h.severidad === 'WARN');
@@ -145,11 +204,39 @@ function auditar(rutaClaudeMd) {
145
204
  secciones_presentes: SECCIONES_CANONICAS.filter(s => s.regex.test(contenido)).map(s => s.nombre),
146
205
  secciones_ausentes: seccionesAusentes.map(s => s.nombre),
147
206
  tiene_at_references: /@[a-zA-Z][a-zA-Z0-9_\-./]+\.md/.test(contenido),
207
+ tiene_referencia_karpathy: tieneReferenciaKarpathy,
208
+ es_project_level: esProjectLevel,
209
+ duplicaciones_reglas_globales: {
210
+ evaluado: resultadoDuplicaciones.evaluado,
211
+ total_reglas_evaluadas: resultadoDuplicaciones.total_reglas_evaluadas,
212
+ detectadas: resultadoDuplicaciones.duplicaciones.length,
213
+ ids: resultadoDuplicaciones.duplicaciones.map(d => d.id),
214
+ },
148
215
  },
149
216
  hallazgos,
150
217
  };
151
218
  }
152
219
 
220
+ /**
221
+ * True si el contenido menciona los 4 principios Karpathy o el skill
222
+ * prevencion-sobreingenieria (cualquier coincidencia case-insensitive).
223
+ */
224
+ function detectarReferenciaKarpathy(contenido) {
225
+ return KARPATHY_PATTERNS.some(re => re.test(contenido));
226
+ }
227
+
228
+ /**
229
+ * True si la ruta apunta a un CLAUDE.md user-level (~/.claude/CLAUDE.md).
230
+ * El check Karpathy NO aplica a user-level — esos siguen otro contrato
231
+ * (preferencias personales, no implementación).
232
+ */
233
+ function esRutaUserLevel(rutaClaudeMd) {
234
+ if (!rutaClaudeMd) return false;
235
+ const homeClaudeDir = path.resolve(path.join(os.homedir(), '.claude'));
236
+ const rutaAbs = path.resolve(rutaClaudeMd);
237
+ return rutaAbs.startsWith(homeClaudeDir + path.sep) || rutaAbs === path.join(homeClaudeDir, 'CLAUDE.md');
238
+ }
239
+
153
240
  /**
154
241
  * Detecta bullets/párrafos cuyo contenido excede MAX_BULLET_CHARS.
155
242
  * Un "bullet" es una línea que empieza con `-` o `*` (ignorando indentación).
@@ -249,6 +336,13 @@ function imprimirReporte(resultado) {
249
336
  console.log(` - Secciones ausentes: ${m.secciones_ausentes.join(', ')}`);
250
337
  }
251
338
  console.log(` - @references: ${m.tiene_at_references ? 'sí' : 'no'}`);
339
+ if (m.es_project_level) {
340
+ console.log(` - Referencia Karpathy: ${m.tiene_referencia_karpathy ? 'sí' : 'no'}`);
341
+ }
342
+ if (m.duplicaciones_reglas_globales && m.duplicaciones_reglas_globales.evaluado) {
343
+ const d = m.duplicaciones_reglas_globales;
344
+ console.log(` - Duplicaciones de reglas globales: ${d.detectadas}/${d.total_reglas_evaluadas} reglas duplicadas${d.detectadas > 0 ? ' (' + d.ids.join(', ') + ')' : ''}`);
345
+ }
252
346
  console.log('');
253
347
  }
254
348
 
@@ -294,4 +388,16 @@ if (require.main === module) {
294
388
  main();
295
389
  }
296
390
 
297
- module.exports = { auditar, ubicarClaudeMd, detectarBulletsGigantes, main, MAX_LINES, MAX_BULLET_CHARS };
391
+ module.exports = {
392
+ auditar,
393
+ ubicarClaudeMd,
394
+ detectarBulletsGigantes,
395
+ detectarReferenciaKarpathy,
396
+ esRutaUserLevel,
397
+ main,
398
+ MAX_LINES,
399
+ MAX_BULLET_CHARS,
400
+ KARPATHY_MIN_LINES,
401
+ // Re-export lazy del detector (puede ser null si lib/ no está disponible):
402
+ cargarDetectorReglasDuplicadas,
403
+ };