@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
@@ -1,71 +1,71 @@
1
- 'use strict';
2
-
3
- /**
4
- * Expansión de --target (CSV) y --all-runtimes para multi-target install.
5
- *
6
- * ADR-0019 Sub-fase 2.5.
7
- *
8
- * Extraído de bin/swl-ses.js para permitir tests unitarios. NUNCA debe tener
9
- * side effects fuera de logging — solo manipula la lista de strings.
10
- *
11
- * @module scripts/lib/expandir-targets
12
- */
13
-
14
- const { listarRuntimesInstalables, RUNTIMES } = require('./detectar-runtime');
15
-
16
- /**
17
- * Expande las opciones del CLI a un array de target IDs.
18
- *
19
- * Reglas:
20
- * - `all_runtimes` tiene prioridad sobre `target`. Si ambos vienen, se loggea
21
- * aviso (vía `logger.warn`) y se usa `all_runtimes`.
22
- * - `target='a,b,c'` → ['a','b','c'].
23
- * - `target='claude'` → ['claude'].
24
- * - Sin nada → ['claude'] (default histórico, backward-compat).
25
- * - Duplicados se deduplican preservando el primer orden.
26
- * - Targets desconocidos se omiten con aviso (logger.warn).
27
- *
28
- * @param {object} opciones - Objeto con `target` (string|undefined) y `all_runtimes` (bool).
29
- * @param {object} [logger] - Sustituible para tests. Default console.
30
- * @returns {{ targets: string[], omitidos: string[], errores: string[] }}
31
- */
32
- function expandirTargets(opciones, logger) {
33
- const log = logger || console;
34
- const omitidos = [];
35
- const errores = [];
36
-
37
- let candidatos;
38
- if (opciones.all_runtimes) {
39
- if (opciones.target && typeof opciones.target === 'string') {
40
- log.warn && log.warn('[expandir-targets] --all-runtimes tiene prioridad sobre --target; se ignora --target.');
41
- }
42
- candidatos = listarRuntimesInstalables();
43
- } else if (typeof opciones.target === 'string' && opciones.target.includes(',')) {
44
- candidatos = opciones.target.split(',').map(s => s.trim()).filter(Boolean);
45
- } else if (typeof opciones.target === 'string' && opciones.target.length > 0) {
46
- candidatos = [opciones.target.trim()];
47
- } else {
48
- candidatos = ['claude']; // backward-compat
49
- }
50
-
51
- const visto = new Set();
52
- const targets = [];
53
- for (const t of candidatos) {
54
- if (visto.has(t)) continue;
55
- visto.add(t);
56
- if (!RUNTIMES[t]) {
57
- log.warn && log.warn(`[expandir-targets] Target desconocido omitido: "${t}". Disponibles: ${Object.keys(RUNTIMES).join(', ')}`);
58
- omitidos.push(t);
59
- continue;
60
- }
61
- targets.push(t);
62
- }
63
-
64
- if (targets.length === 0) {
65
- errores.push('Ningún target válido tras expansión.');
66
- }
67
-
68
- return { targets, omitidos, errores };
69
- }
70
-
71
- module.exports = { expandirTargets };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Expansión de --target (CSV) y --all-runtimes para multi-target install.
5
+ *
6
+ * ADR-0019 Sub-fase 2.5.
7
+ *
8
+ * Extraído de bin/swl-ses.js para permitir tests unitarios. NUNCA debe tener
9
+ * side effects fuera de logging — solo manipula la lista de strings.
10
+ *
11
+ * @module scripts/lib/expandir-targets
12
+ */
13
+
14
+ const { listarRuntimesInstalables, RUNTIMES } = require('./detectar-runtime');
15
+
16
+ /**
17
+ * Expande las opciones del CLI a un array de target IDs.
18
+ *
19
+ * Reglas:
20
+ * - `all_runtimes` tiene prioridad sobre `target`. Si ambos vienen, se loggea
21
+ * aviso (vía `logger.warn`) y se usa `all_runtimes`.
22
+ * - `target='a,b,c'` → ['a','b','c'].
23
+ * - `target='claude'` → ['claude'].
24
+ * - Sin nada → ['claude'] (default histórico, backward-compat).
25
+ * - Duplicados se deduplican preservando el primer orden.
26
+ * - Targets desconocidos se omiten con aviso (logger.warn).
27
+ *
28
+ * @param {object} opciones - Objeto con `target` (string|undefined) y `all_runtimes` (bool).
29
+ * @param {object} [logger] - Sustituible para tests. Default console.
30
+ * @returns {{ targets: string[], omitidos: string[], errores: string[] }}
31
+ */
32
+ function expandirTargets(opciones, logger) {
33
+ const log = logger || console;
34
+ const omitidos = [];
35
+ const errores = [];
36
+
37
+ let candidatos;
38
+ if (opciones.all_runtimes) {
39
+ if (opciones.target && typeof opciones.target === 'string') {
40
+ log.warn && log.warn('[expandir-targets] --all-runtimes tiene prioridad sobre --target; se ignora --target.');
41
+ }
42
+ candidatos = listarRuntimesInstalables();
43
+ } else if (typeof opciones.target === 'string' && opciones.target.includes(',')) {
44
+ candidatos = opciones.target.split(',').map(s => s.trim()).filter(Boolean);
45
+ } else if (typeof opciones.target === 'string' && opciones.target.length > 0) {
46
+ candidatos = [opciones.target.trim()];
47
+ } else {
48
+ candidatos = ['claude']; // backward-compat
49
+ }
50
+
51
+ const visto = new Set();
52
+ const targets = [];
53
+ for (const t of candidatos) {
54
+ if (visto.has(t)) continue;
55
+ visto.add(t);
56
+ if (!RUNTIMES[t]) {
57
+ log.warn && log.warn(`[expandir-targets] Target desconocido omitido: "${t}". Disponibles: ${Object.keys(RUNTIMES).join(', ')}`);
58
+ omitidos.push(t);
59
+ continue;
60
+ }
61
+ targets.push(t);
62
+ }
63
+
64
+ if (targets.length === 0) {
65
+ errores.push('Ningún target válido tras expansión.');
66
+ }
67
+
68
+ return { targets, omitidos, errores };
69
+ }
70
+
71
+ module.exports = { expandirTargets };
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * frontmatter-md.js — Parser mínimo zero-deps de frontmatter YAML en archivos .md.
5
+ *
6
+ * Soporta el subconjunto que usan los componentes SWL:
7
+ * - pares `clave: valor` de una línea (comillas opcionales)
8
+ * - escalares plegados (`clave: >`, `|`, `>-`, `|-`) juntando las líneas indentadas
9
+ * - listas inline (`tools: [Read, Write]`) — se devuelven como string; usar
10
+ * `parsearListaInline()` para convertirlas a array
11
+ *
12
+ * NO es un parser YAML completo (sin listas multilínea ni objetos anidados) —
13
+ * suficiente y determinista para frontmatter de agentes/skills/comandos.
14
+ * Consumidores: scripts/generar-inventario.js, scripts/generar-comandos.js.
15
+ */
16
+
17
+ const fs = require('fs');
18
+
19
+ function leerFrontmatter(ruta) {
20
+ try {
21
+ const contenido = fs.readFileSync(ruta, 'utf8');
22
+ const match = contenido.match(/^---\r?\n([\s\S]*?)\r?\n---/);
23
+ if (!match) return {};
24
+ const fm = {};
25
+ const lineas = match[1].split(/\r?\n/);
26
+ for (let i = 0; i < lineas.length; i++) {
27
+ const linea = lineas[i];
28
+ if (/^\s/.test(linea)) continue; // continuación de multilínea
29
+ const idx = linea.indexOf(':');
30
+ if (idx <= 0) continue;
31
+ const key = linea.slice(0, idx).trim();
32
+ let val = linea.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
33
+ if (val === '>' || val === '|' || val === '>-' || val === '|-' || val === '') {
34
+ const partes = [];
35
+ for (let j = i + 1; j < lineas.length && /^\s+\S/.test(lineas[j]); j++) {
36
+ partes.push(lineas[j].trim());
37
+ }
38
+ if (partes.length > 0) val = partes.join(' ');
39
+ }
40
+ fm[key] = val;
41
+ }
42
+ return fm;
43
+ } catch { return {}; }
44
+ }
45
+
46
+ /** `[a, b, c]` → ['a','b','c']; string vacío → []. */
47
+ function parsearListaInline(valor) {
48
+ return String(valor || '')
49
+ .replace(/^\[|\]$/g, '')
50
+ .split(',')
51
+ .map(s => s.trim())
52
+ .filter(Boolean);
53
+ }
54
+
55
+ /** Recorta a n caracteres en límite de palabra, colapsando whitespace y pipes. */
56
+ function resumir(texto, n = 110) {
57
+ const limpio = String(texto || '').replace(/\s+/g, ' ').replace(/\|/g, '/').trim();
58
+ if (limpio.length <= n) return limpio;
59
+ const corte = limpio.lastIndexOf(' ', n);
60
+ return limpio.slice(0, corte > 40 ? corte : n) + '…';
61
+ }
62
+
63
+ module.exports = { leerFrontmatter, parsearListaInline, resumir };
@@ -48,6 +48,8 @@ const BOOLEANAS = [
48
48
  // TUI opt-out (Fase 4 instalador visual). Default: si stdin es TTY y no
49
49
  // hay flags, lanza el TUI. Con --no-tui cae al install-asistido clásico.
50
50
  'no-tui', 'tui',
51
+ // prune (DT-INSTALADOR-PRUNE): --confirmar ejecuta el plan; --json salida machine-readable
52
+ 'confirmar', 'json',
51
53
  ];
52
54
 
53
55
  /**
@@ -0,0 +1,180 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * prune-componentes — Purga de componentes retirados en instalaciones SWL.
5
+ *
6
+ * Cierra DT-INSTALADOR-PRUNE: cuando el paquete retira un componente
7
+ * (skill/agente/comando/regla/hook), las instalaciones existentes lo
8
+ * conservaban para siempre. Este módulo compara el `.swl-install-state.json`
9
+ * del destino contra el paquete actual y elimina SOLO lo que cumple las dos
10
+ * condiciones de seguridad:
11
+ *
12
+ * 1. El `origen` registrado ya NO existe en el paquete (componente retirado).
13
+ * 2. El contenido instalado coincide con un hash canónico conocido del
14
+ * componente (manifiestos/canonical-hashes.json, cualquier versión) —
15
+ * es contenido de fábrica sin tocar.
16
+ *
17
+ * Si el contenido difiere de todo hash canónico conocido (evolución del
18
+ * usuario) o el componente no tiene hash registrado (p. ej. recursos/ de un
19
+ * skill), NUNCA se elimina: se reporta como `preservados` para revisión
20
+ * manual. Invariante merge-no-overwrite (ADR-0040) extendida al borrado.
21
+ *
22
+ * API pura respecto de rutas: recibe `rutaBase` (instalación destino) y
23
+ * `raizPaquete` (paquete fuente actual). El caller (scripts/prune.js) resuelve
24
+ * ambos; los tests inyectan sandboxes.
25
+ *
26
+ * @module scripts/lib/prune-componentes
27
+ */
28
+
29
+ const fs = require('fs');
30
+ const path = require('path');
31
+
32
+ const { cargarEstado, guardarEstado } = require('./estado.js');
33
+ const { canonicalHash } = require('./canonical-hash.js');
34
+
35
+ /**
36
+ * Construye el índice rutaRelativa → Set(hashes canónicos de todas las
37
+ * versiones) desde manifiestos/canonical-hashes.json del paquete.
38
+ *
39
+ * @param {string} raizPaquete
40
+ * @returns {Map<string, Set<string>>}
41
+ */
42
+ function cargarHashesConocidos(raizPaquete) {
43
+ const indice = new Map();
44
+ try {
45
+ const ruta = path.join(raizPaquete, 'manifiestos', 'canonical-hashes.json');
46
+ const porVersion = JSON.parse(fs.readFileSync(ruta, 'utf-8'));
47
+ for (const version of Object.keys(porVersion)) {
48
+ const componentes = porVersion[version];
49
+ if (!componentes || typeof componentes !== 'object') continue;
50
+ for (const [rutaRel, hash] of Object.entries(componentes)) {
51
+ const clave = rutaRel.replace(/\\/g, '/');
52
+ if (!indice.has(clave)) indice.set(clave, new Set());
53
+ indice.get(clave).add(String(hash));
54
+ }
55
+ }
56
+ } catch { /* sin manifiesto de hashes → índice vacío → todo se preserva */ }
57
+ return indice;
58
+ }
59
+
60
+ /**
61
+ * Clasifica los archivos instalados contra el paquete actual.
62
+ *
63
+ * @param {object} opciones
64
+ * @param {string} opciones.rutaBase Directorio de la instalación (contiene .swl-install-state.json)
65
+ * @param {string} opciones.raizPaquete Raíz del paquete swl-ses actual
66
+ * @returns {{
67
+ * error?: string,
68
+ * eliminables: Array<{origen: string, destino: string, tipo: string}>,
69
+ * preservados: Array<{origen: string, destino: string, tipo: string, razon: string}>,
70
+ * yaAusentes: Array<{origen: string, destino: string}>,
71
+ * vigentes: number,
72
+ * }}
73
+ */
74
+ function planPrune({ rutaBase, raizPaquete }) {
75
+ const estado = cargarEstado(rutaBase);
76
+ if (!estado) {
77
+ return { error: `No hay .swl-install-state.json en ${rutaBase} — nada que podar.`, eliminables: [], preservados: [], yaAusentes: [], vigentes: 0 };
78
+ }
79
+
80
+ const hashesConocidos = cargarHashesConocidos(raizPaquete);
81
+ const eliminables = [];
82
+ const preservados = [];
83
+ const yaAusentes = [];
84
+ let vigentes = 0;
85
+
86
+ for (const archivo of estado.archivosInstalados || []) {
87
+ const origenRel = String(archivo.origen || '').replace(/\\/g, '/');
88
+ if (!origenRel) continue;
89
+
90
+ if (fs.existsSync(path.join(raizPaquete, origenRel))) {
91
+ vigentes++;
92
+ continue; // el paquete todavía distribuye este componente
93
+ }
94
+
95
+ // Componente retirado del paquete.
96
+ if (!fs.existsSync(archivo.destino)) {
97
+ yaAusentes.push({ origen: origenRel, destino: archivo.destino });
98
+ continue;
99
+ }
100
+
101
+ const hashes = hashesConocidos.get(origenRel);
102
+ if (!hashes || hashes.size === 0) {
103
+ preservados.push({
104
+ origen: origenRel,
105
+ destino: archivo.destino,
106
+ tipo: archivo.tipo || '?',
107
+ razon: 'sin hash canónico registrado — revisar y borrar manualmente si aplica',
108
+ });
109
+ continue;
110
+ }
111
+
112
+ let hashLocal;
113
+ try {
114
+ hashLocal = canonicalHash(fs.readFileSync(archivo.destino, 'utf-8'));
115
+ } catch {
116
+ preservados.push({ origen: origenRel, destino: archivo.destino, tipo: archivo.tipo || '?', razon: 'destino ilegible' });
117
+ continue;
118
+ }
119
+
120
+ if (hashes.has(hashLocal)) {
121
+ eliminables.push({ origen: origenRel, destino: archivo.destino, tipo: archivo.tipo || '?' });
122
+ } else {
123
+ preservados.push({
124
+ origen: origenRel,
125
+ destino: archivo.destino,
126
+ tipo: archivo.tipo || '?',
127
+ razon: 'contenido evolucionado por el usuario (hash ≠ canónico) — merge-no-overwrite',
128
+ });
129
+ }
130
+ }
131
+
132
+ return { eliminables, preservados, yaAusentes, vigentes };
133
+ }
134
+
135
+ /**
136
+ * Ejecuta el plan: borra los eliminables, limpia del estado las entradas
137
+ * eliminadas y las ya ausentes, y persiste el estado actualizado.
138
+ *
139
+ * @param {object} plan Resultado de planPrune
140
+ * @param {object} opciones
141
+ * @param {string} opciones.rutaBase
142
+ * @returns {{eliminados: string[], erroresBorrado: Array<{destino: string, error: string}>, entradasLimpiadas: number}}
143
+ */
144
+ function ejecutarPrune(plan, { rutaBase }) {
145
+ const eliminados = [];
146
+ const erroresBorrado = [];
147
+
148
+ for (const item of plan.eliminables) {
149
+ try {
150
+ fs.unlinkSync(item.destino);
151
+ eliminados.push(item.destino);
152
+ // Si el directorio del skill quedó vacío (p. ej. habilidades/x/), retirarlo.
153
+ const dir = path.dirname(item.destino);
154
+ try {
155
+ if (fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);
156
+ } catch { /* dir compartido o ya ausente — irrelevante */ }
157
+ } catch (err) {
158
+ erroresBorrado.push({ destino: item.destino, error: err.message });
159
+ }
160
+ }
161
+
162
+ // Limpiar el estado: fuera las entradas eliminadas y las ya ausentes.
163
+ const estado = cargarEstado(rutaBase);
164
+ let entradasLimpiadas = 0;
165
+ if (estado) {
166
+ const quitar = new Set([
167
+ ...eliminados,
168
+ ...plan.yaAusentes.map(a => a.destino),
169
+ ]);
170
+ const antes = estado.archivosInstalados.length;
171
+ estado.archivosInstalados = estado.archivosInstalados.filter(a => !quitar.has(a.destino));
172
+ entradasLimpiadas = antes - estado.archivosInstalados.length;
173
+ estado.actualizadoEn = new Date().toISOString();
174
+ guardarEstado(estado, rutaBase);
175
+ }
176
+
177
+ return { eliminados, erroresBorrado, entradasLimpiadas };
178
+ }
179
+
180
+ module.exports = { planPrune, ejecutarPrune, cargarHashesConocidos };
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema-version": "1.0.0",
3
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.1.0",
5
- "actualizado": "2026-05-22",
4
+ "version": "1.2.0",
5
+ "actualizado": "2026-07-08",
6
6
  "reglas": [
7
7
  {
8
8
  "id": "idioma-espanol-mexico",
@@ -168,6 +168,20 @@
168
168
  "min_matches": 1,
169
169
  "scope": "project-level",
170
170
  "remediacion_sugerida": "Eliminar el bloque local. La regla global aplica a cualquier proyecto con vault Obsidian disponible."
171
+ },
172
+ {
173
+ "id": "usar-code-review-graph",
174
+ "regla_global": "usar-code-review-graph.md",
175
+ "seccion_canonica": "Usar code-review-graph antes de Read/Grep/Glob para explorar",
176
+ "referencia_canonica": "@~/.claude/rules/usar-code-review-graph.md",
177
+ "descripcion": "Comportamiento completo del MCP code-review-graph (tabla de herramientas, workflow, excepciones). El CLAUDE.md del proyecto solo declara la disponibilidad del grafo — re-derivar el comportamiento inline es duplicación. Caso real: CLAUDE.md de swl-ses acumuló 37 líneas re-derivándolo en inglés (removido 2026-07-08).",
178
+ "patrones": [
179
+ "ALWAYS use thes+code-review-graph",
180
+ "code-review-graph MCP tools BEFORE",
181
+ "graph tools FIRST",
182
+ "detect_changes.*get_review_context|get_impact_radius.*blast radius"
183
+ ],
184
+ "remediacion_sugerida": "Una línea en Stack: 'MCP del proyecto: code-review-graph disponible vía .mcp.json. Comportamiento: @~/.claude/rules/usar-code-review-graph.md'"
171
185
  }
172
186
  ],
173
187
  "configuracion": {
@@ -203,8 +203,8 @@ function applyFeedback(instinto, type, currentDate) {
203
203
  throw new Error(`Tipo de feedback inválido: ${type}`);
204
204
  }
205
205
  const now = (currentDate instanceof Date)
206
- ? currentDate.toISOString().slice(0, 10)
207
- : (currentDate || new Date().toISOString().slice(0, 10));
206
+ ? currentDate.toLocaleDateString('sv')
207
+ : (currentDate || new Date().toLocaleDateString('sv'));
208
208
 
209
209
  const next = { ...instinto };
210
210
  if (type === 'helpful') next.helpful_count = (next.helpful_count || 0) + 1;