@saulwade/swl-ses 2.4.3 → 2.5.2
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 +194 -241
- package/README.md +600 -597
- package/agentes/_intent-spec.md +73 -73
- package/agentes/_propose-step.md +90 -90
- package/agentes/abogado-diablo-swl.md +145 -0
- package/agentes/accesibilidad-wcag-swl.md +690 -690
- package/agentes/arquitecto-swl.md +267 -267
- package/agentes/auto-evolucion-swl.md +908 -908
- package/agentes/backend-api-swl.md +1 -1
- package/agentes/backend-csharp-swl.md +420 -420
- package/agentes/backend-go-swl.md +390 -390
- package/agentes/backend-java-swl.md +281 -281
- package/agentes/backend-node-swl.md +1 -1
- package/agentes/backend-python-swl.md +1 -1
- package/agentes/backend-rust-swl.md +364 -364
- package/agentes/backend-workers-swl.md +482 -482
- package/agentes/cloud-infra-swl.md +509 -509
- package/agentes/consolidador-swl.md +541 -541
- package/agentes/datos-swl.md +1 -1
- package/agentes/depurador-swl.md +352 -352
- package/agentes/devops-ci-swl.md +400 -400
- package/agentes/disenador-ui-swl.md +569 -569
- package/agentes/documentador-swl.md +345 -345
- package/agentes/frontend-angular-swl.md +621 -621
- package/agentes/frontend-css-swl.md +716 -716
- package/agentes/frontend-react-swl.md +692 -692
- package/agentes/frontend-swl.md +496 -496
- package/agentes/frontend-tailwind-swl.md +826 -826
- package/agentes/gh-fix-ci-swl.md +6 -1
- package/agentes/implementador-swl.md +1 -1
- package/agentes/investigador-swl.md +432 -432
- package/agentes/investigador-ux-swl.md +505 -505
- package/agentes/llm-apps-swl.md +1 -1
- package/agentes/migrador-swl.md +442 -442
- package/agentes/mobile-android-swl.md +511 -511
- package/agentes/mobile-cross-swl.md +541 -541
- package/agentes/mobile-ios-swl.md +502 -502
- package/agentes/mobile-testing-swl.md +302 -302
- package/agentes/nemesis-auditor-swl.md +285 -285
- package/agentes/notificador-swl.md +1 -1
- package/agentes/observabilidad-swl.md +438 -438
- package/agentes/pagos-swl.md +310 -310
- package/agentes/perfilador-usuario-swl.md +321 -321
- package/agentes/planificador-swl.md +399 -399
- package/agentes/producto-prd-swl.md +589 -589
- package/agentes/red-team-swl.md +218 -218
- package/agentes/release-manager-swl.md +590 -590
- package/agentes/rendimiento-swl.md +713 -713
- package/agentes/resolutor-build-swl.md +10 -1
- package/agentes/revisor-angular-swl.md +278 -278
- package/agentes/revisor-codigo-swl.md +1 -1
- package/agentes/revisor-csharp-swl.md +264 -264
- package/agentes/revisor-go-swl.md +259 -259
- package/agentes/revisor-java-swl.md +257 -257
- package/agentes/revisor-kotlin-swl.md +273 -273
- package/agentes/revisor-nextjs-swl.md +281 -281
- package/agentes/revisor-php-swl.md +271 -271
- package/agentes/revisor-react-swl.md +278 -278
- package/agentes/revisor-rust-swl.md +346 -346
- package/agentes/revisor-seguridad-swl.md +399 -399
- package/agentes/revisor-swift-swl.md +268 -268
- package/agentes/revisor-typescript-swl.md +346 -346
- package/agentes/sre-swl.md +1 -1
- package/agentes/tdd-qa-swl.md +393 -393
- package/bin/lib/bot-comandos.js +1 -1
- package/bin/swl-ses.js +6 -0
- package/comandos/swl/adoptar-proyecto.md +14 -2
- package/comandos/swl/configurar-ci.md +8 -1
- package/comandos/swl/deuda-codigo.md +97 -97
- package/comandos/swl/discutir-fase.md +22 -118
- package/comandos/swl/fix.md +118 -0
- package/comandos/swl/nuevo-proyecto.md +54 -3
- package/comandos/swl/predecir.md +32 -2
- package/comandos/swl/seguridad.md +189 -0
- package/comandos/swl/status.md +5 -3
- package/habilidades/aprendizaje-continuo/SKILL.md +3 -1
- package/habilidades/discutir-fase/SKILL.md +84 -81
- package/habilidades/discutir-fase/recursos/plantilla-contexto.md +136 -0
- package/habilidades/doc-sync/SKILL.md +3 -1
- package/habilidades/doubt-driven-review/SKILL.md +15 -1
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/estructura-proyecto-claude/SKILL.md +11 -2
- package/habilidades/harness-claude-code/SKILL.md +3 -1
- package/habilidades/instalar-sistema/SKILL.md +3 -1
- package/habilidades/meta-reglas-extendido/SKILL.md +92 -0
- package/habilidades/meta-reglas-extendido/recursos/analisis-previo-tareas-grandes.md +186 -0
- package/habilidades/meta-reglas-extendido/recursos/analizar-directorios-antes-de-escribir.md +235 -0
- package/habilidades/meta-reglas-extendido/recursos/api-diseno.md +413 -0
- package/habilidades/meta-reglas-extendido/recursos/arquitectura.md +491 -0
- package/habilidades/meta-reglas-extendido/recursos/arreglar-al-detectar.md +264 -0
- package/habilidades/meta-reglas-extendido/recursos/debatir-antes-de-aceptar.md +152 -0
- package/habilidades/meta-reglas-extendido/recursos/git-workflow.md +259 -0
- package/habilidades/meta-reglas-extendido/recursos/gobernanza.md +291 -0
- package/habilidades/meta-reglas-extendido/recursos/memoria-consolidada.md +263 -0
- package/habilidades/meta-reglas-extendido/recursos/seguridad-agentes.md +443 -0
- package/habilidades/meta-reglas-extendido/recursos/sesiones-paralelas.md +190 -0
- package/habilidades/meta-reglas-extendido/recursos/sin-duplicacion-reglas-globales.md +179 -0
- package/habilidades/meta-reglas-extendido/recursos/skills-estandar.md +394 -0
- package/habilidades/meta-reglas-extendido/recursos/usar-code-review-graph.md +156 -0
- package/habilidades/meta-reglas-extendido/recursos/usar-context7.md +236 -0
- package/habilidades/meta-reglas-extendido/recursos/usar-sistema-swl.md +253 -0
- package/habilidades/meta-reglas-extendido/recursos/verificar-citas-normativas.md +527 -0
- package/habilidades/meta-skills-estandar/SKILL.md +3 -1
- package/habilidades/nuevo-proyecto/SKILL.md +20 -3
- package/habilidades/php-experto/SKILL.md +10 -3
- package/habilidades/{filament-admin/SKILL.md → php-experto/recursos/filament-admin.md} +23 -39
- package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
- package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
- package/habilidades/proceso-debate-adversarial/recursos/personas.md +5 -4
- package/habilidades/proceso-ingenieria-requerimientos/SKILL.md +147 -0
- package/hooks/check-update.js +19 -10
- package/hooks/contexto-subagente.js +68 -68
- package/hooks/degradacion-instintos.js +1 -1
- package/hooks/extraccion-aprendizajes.js +2 -2
- package/hooks/lib/briefing.js +3 -3
- package/hooks/lib/nudge-tracker.js +1 -1
- package/hooks/lib/otlp-exporter.js +1 -1
- package/hooks/lib/webhook-dedup.js +1 -1
- package/hooks/session-briefing.js +1 -1
- package/llms.txt +6 -6
- package/manifiestos/canonical-hashes.json +1043 -52
- package/manifiestos/hooks-config.json +469 -469
- package/manifiestos/invariantes-criticos.json +30 -30
- package/manifiestos/modulos.json +168 -135
- package/manifiestos/perfiles.json +0 -2
- package/manifiestos/skills-lock.json +49 -56
- package/package.json +7 -5
- package/plantillas/github-workflows/README.md +15 -1
- package/plantillas/github-workflows/swl-devsecops.yml +70 -0
- package/plugin.json +5 -5
- package/reglas/analisis-previo-tareas-grandes.md +30 -156
- package/reglas/analizar-directorios-antes-de-escribir.md +30 -211
- package/reglas/api-diseno.md +28 -398
- package/reglas/arquitectura.md +35 -456
- package/reglas/arreglar-al-detectar.md +30 -230
- package/reglas/debatir-antes-de-aceptar.md +30 -143
- package/reglas/docs.md +7 -0
- package/reglas/estilo-codigo.md +9 -0
- package/reglas/fragmentos-compartidos.md +6 -0
- package/reglas/git-workflow.md +44 -240
- package/reglas/gobernanza.md +23 -262
- package/reglas/memoria-consolidada.md +34 -228
- package/reglas/performance.md +8 -0
- package/reglas/pruebas.md +12 -0
- package/reglas/seguridad-agentes.md +37 -418
- package/reglas/seguridad.md +12 -0
- package/reglas/sesiones-paralelas.md +29 -162
- package/reglas/sin-duplicacion-reglas-globales.md +25 -166
- package/reglas/skills-estandar.md +23 -373
- package/reglas/usar-code-review-graph.md +31 -140
- package/reglas/usar-context7.md +30 -208
- package/reglas/usar-sistema-swl.md +47 -242
- package/reglas/verificar-citas-normativas.md +47 -537
- package/scripts/actualizar.js +253 -253
- package/scripts/audit-tools/auditar-relleno-inventario.js +145 -0
- package/scripts/auditar-clases-conocidas.js +106 -0
- package/scripts/bootstrap-instintos.js +2 -2
- package/scripts/canario-hooks.js +166 -0
- package/scripts/cli/configurar-ci.js +2 -1
- package/scripts/evidencia-valor.js +101 -0
- package/scripts/field-report.js +18 -2
- package/scripts/generar-comandos.js +143 -0
- package/scripts/generar-inventario.js +236 -23
- package/scripts/generar-matriz-lenguajes.js +1 -1
- package/scripts/instalador.js +15 -1
- package/scripts/instalar-git-hook.js +8 -1
- package/scripts/lib/configurar-ci.js +10 -3
- package/scripts/lib/diary-entry.js +3 -1
- package/scripts/lib/drift-detector.js +1 -1
- package/scripts/lib/evidencia-valor.js +228 -0
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/frontmatter-md.js +63 -0
- package/scripts/lib/parsear-opciones.js +2 -0
- package/scripts/lib/prune-componentes.js +180 -0
- package/scripts/lib/reglas-globales-conocidas.json +16 -2
- package/scripts/lib/scoring-instintos.js +2 -2
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/lib/transformadores/claude.js +1 -1
- package/scripts/lib/transformadores/codex.js +1 -1
- package/scripts/lib/transformadores/copilot.js +1 -1
- package/scripts/lib/transformadores/cursor.js +1 -1
- package/scripts/lib/transformadores/gemini.js +22 -2
- package/scripts/lib/transformadores/opencode.js +1 -1
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/prune.js +102 -0
- package/scripts/publicar.js +18 -2
- package/scripts/tui/index.js +10 -1
- package/scripts/tui/pantallas/inspect.js +175 -175
- package/scripts/tui/pantallas/install-wizard.js +21 -8
- package/scripts/tui/pantallas/uninstall-wizard.js +210 -210
- package/scripts/tui/pantallas/update-wizard.js +234 -234
- package/scripts/tui/pantallas/welcome.js +188 -189
- package/habilidades/paid-media-tracking/SKILL.md +0 -269
- package/habilidades/paid-media-tracking/recursos/auditoria-tracking.md +0 -220
- package/habilidades/paid-media-tracking/recursos/google-ads-api.md +0 -215
- package/habilidades/tracking-measurement/SKILL.md +0 -239
- package/habilidades/tracking-measurement/recursos/consent-mode.md +0 -231
- package/habilidades/tracking-measurement/recursos/gtm-datalayer.md +0 -216
- package/habilidades/tracking-measurement/recursos/meta-capi.md +0 -262
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* evidencia-valor.js — Evidencia de valor del harness EN CAMPO (downstream).
|
|
5
|
+
*
|
|
6
|
+
* En un proyecto real no hay A/B (nadie desarrolla la misma feature dos
|
|
7
|
+
* veces); la evidencia creíble son HECHOS producidos por actores
|
|
8
|
+
* independientes del sistema, nunca scores autodeclarados (regla
|
|
9
|
+
* anti-gaming de seguridad-agentes). Cuatro fuentes, cada una con
|
|
10
|
+
* procedencia explícita:
|
|
11
|
+
*
|
|
12
|
+
* 1. Intercepciones de gates — eventos durables de `.planning/evolution/
|
|
13
|
+
* nudges.jsonl` (hook disparó una intervención). Evidencia directa:
|
|
14
|
+
* cada bloqueo/alerta con causa real es un defecto prevenido contable.
|
|
15
|
+
* 2. Presión de defectos — git: ratio commits fix/feat en la ventana
|
|
16
|
+
* + fixes POSTERIORES a la última verificación de fase (escape rate
|
|
17
|
+
* aproximado — la métrica que CASTIGA al harness). Proxy honesto.
|
|
18
|
+
* 3. DORA-lite + CI — reusa scripts/lib/metricas-dora.js
|
|
19
|
+
* (deployment frequency, lead time, change failure rate, MTTR vía gh).
|
|
20
|
+
* Producido por git/GitHub, no por el sistema.
|
|
21
|
+
* 4. Uso del sistema — .planning/skill-metrics.json. Solo
|
|
22
|
+
* contexto (correlación, jamás causalidad).
|
|
23
|
+
*
|
|
24
|
+
* FRONTERA DE DATOS (documentada en docs/evidencia-valor.md): TODO se lee
|
|
25
|
+
* LOCAL, del `.planning/` y el git del propio proyecto downstream donde
|
|
26
|
+
* corre el comando. El repo madre NUNCA lee estos datos; el único canal
|
|
27
|
+
* hacia el autor es el field-report opt-in con agregados anónimos.
|
|
28
|
+
*
|
|
29
|
+
* Zero-deps. Funciones puras respecto a raíz/inyecciones para testeo.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const { execFileSync } = require('child_process');
|
|
35
|
+
|
|
36
|
+
const DIA_MS = 24 * 60 * 60 * 1000;
|
|
37
|
+
|
|
38
|
+
/** Tipos de nudge que constituyen una intervención del sistema (evidencia). */
|
|
39
|
+
const TIPOS_INTERVENCION = new Set([
|
|
40
|
+
'bloqueo', 'bloqueo-hook', 'secreto-detectado', 'calidad-pre-commit',
|
|
41
|
+
'drift-detectado', 'recovery-activado', 'riesgo-alto', 'proteccion-rutas',
|
|
42
|
+
'prompt-injection', 'veto', 'gate-fallido',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
// ─── 1. Intercepciones (nudges.jsonl) ────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Diagnóstico de POR QUÉ no hay telemetría: los hooks solo se registran a
|
|
49
|
+
* nivel PROYECTO (ADR-0009 — un install --global copia los archivos pero NO
|
|
50
|
+
* los registra en ~/.claude/settings.json, para no correr 49 hooks en cada
|
|
51
|
+
* sesión de cualquier proyecto). Sin registro local, nudges.jsonl y
|
|
52
|
+
* skill-metrics.json jamás aparecerán — el panel debe decir CÓMO activarlo,
|
|
53
|
+
* no solo "no disponible" (reporte de campo SIGM, 2026-07-09).
|
|
54
|
+
*/
|
|
55
|
+
function hooksRegistradosEnProyecto(raiz) {
|
|
56
|
+
try {
|
|
57
|
+
const s = JSON.parse(fs.readFileSync(path.join(raiz, '.claude', 'settings.json'), 'utf8'));
|
|
58
|
+
return Object.values(s.hooks || {}).some(m => Array.isArray(m) && m.length > 0);
|
|
59
|
+
} catch { return false; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const HINT_ACTIVACION = 'hooks sin registrar a nivel proyecto (el install --global no los registra por diseño, ADR-0009) — activar con: npx -y @saulwade/swl-ses@latest install --target claude --perfil core (SIN --global, en la raíz del proyecto)';
|
|
63
|
+
|
|
64
|
+
function leerNudges(raiz, { ventanaDias = 30, hoy = new Date() } = {}) {
|
|
65
|
+
const ruta = path.join(raiz, '.planning', 'evolution', 'nudges.jsonl');
|
|
66
|
+
if (!fs.existsSync(ruta)) {
|
|
67
|
+
const razon = hooksRegistradosEnProyecto(raiz)
|
|
68
|
+
? 'sin nudges.jsonl aún (hooks registrados; se acumula con el uso de sesiones)'
|
|
69
|
+
: HINT_ACTIVACION;
|
|
70
|
+
return { disponible: false, razon, eventos: [] };
|
|
71
|
+
}
|
|
72
|
+
const corte = hoy.getTime() - ventanaDias * DIA_MS;
|
|
73
|
+
const eventos = [];
|
|
74
|
+
for (const linea of fs.readFileSync(ruta, 'utf8').split(/\r?\n/)) {
|
|
75
|
+
if (!linea.trim()) continue;
|
|
76
|
+
try {
|
|
77
|
+
const e = JSON.parse(linea);
|
|
78
|
+
const t = Date.parse(e.timestamp || e.ts || '');
|
|
79
|
+
if (!Number.isNaN(t) && t >= corte) eventos.push(e);
|
|
80
|
+
} catch { /* línea corrupta: se omite, no revienta el panel */ }
|
|
81
|
+
}
|
|
82
|
+
return { disponible: true, eventos };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function intercepciones(raiz, opts = {}) {
|
|
86
|
+
const { disponible, razon, eventos } = leerNudges(raiz, opts);
|
|
87
|
+
if (!disponible) return { disponible, razon, procedencia: 'hook-event (nudges.jsonl)', total: 0, porTipo: {} };
|
|
88
|
+
const porTipo = {};
|
|
89
|
+
let intervenciones = 0;
|
|
90
|
+
for (const e of eventos) {
|
|
91
|
+
const tipo = String(e.tipo || 'desconocido');
|
|
92
|
+
porTipo[tipo] = (porTipo[tipo] || 0) + 1;
|
|
93
|
+
if (TIPOS_INTERVENCION.has(tipo) || e.blocking === true || e.estado === 'critico') intervenciones++;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
disponible: true,
|
|
97
|
+
procedencia: 'hook-event (nudges.jsonl)',
|
|
98
|
+
caracter: 'evidencia',
|
|
99
|
+
totalEventos: eventos.length,
|
|
100
|
+
intervenciones,
|
|
101
|
+
porTipo,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── 2. Presión de defectos (git) ────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
function ejecutarGit(raiz, args) {
|
|
108
|
+
return execFileSync('git', args, { cwd: raiz, encoding: 'utf8' });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function presionDefectos(raiz, { ventanaDias = 30, hoy = new Date(), git = ejecutarGit } = {}) {
|
|
112
|
+
let sujetos;
|
|
113
|
+
try {
|
|
114
|
+
const desde = new Date(hoy.getTime() - ventanaDias * DIA_MS).toISOString();
|
|
115
|
+
sujetos = git(raiz, ['log', `--since=${desde}`, '--pretty=format:%ad|%s', '--date=iso-strict'])
|
|
116
|
+
.split(/\r?\n/).filter(Boolean);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return { disponible: false, razon: `git no disponible: ${err.message.split(/\r?\n/)[0]}` };
|
|
119
|
+
}
|
|
120
|
+
const esFix = s => /^(fix|hotfix)(\(|!|:)/.test(s);
|
|
121
|
+
const esFeat = s => /^feat(\(|!|:)/.test(s);
|
|
122
|
+
const fixes = sujetos.filter(l => esFix(l.split('|').slice(1).join('|')));
|
|
123
|
+
const feats = sujetos.filter(l => esFeat(l.split('|').slice(1).join('|')));
|
|
124
|
+
|
|
125
|
+
// Escape aproximado: fixes con fecha POSTERIOR a la última verificación de
|
|
126
|
+
// fase (VERIFICACION.md más reciente). Sin fases verificadas → no aplica.
|
|
127
|
+
// La fecha se toma de GIT (autoría del último commit que tocó el archivo),
|
|
128
|
+
// no del mtime del filesystem: tras un clone/checkout todos los archivos
|
|
129
|
+
// comparten el mtime de la operación y el proxy daba falso negativo
|
|
130
|
+
// (reporte de campo SIGM, 2026-07-09). Fallback a mtime fuera de git.
|
|
131
|
+
let escapes = { disponible: false, razon: 'sin VERIFICACION.md en .planning/fases/ (proyecto sin ciclo GSD verificado)' };
|
|
132
|
+
try {
|
|
133
|
+
const dirFases = path.join(raiz, '.planning', 'fases');
|
|
134
|
+
let ultimaVerificacion = 0;
|
|
135
|
+
if (fs.existsSync(dirFases)) {
|
|
136
|
+
for (const fase of fs.readdirSync(dirFases)) {
|
|
137
|
+
const v = path.join(dirFases, fase, 'VERIFICACION.md');
|
|
138
|
+
if (!fs.existsSync(v)) continue;
|
|
139
|
+
let fecha = 0;
|
|
140
|
+
try {
|
|
141
|
+
const rel = path.relative(raiz, v);
|
|
142
|
+
const ct = parseInt(git(raiz, ['log', '-1', '--format=%ct', '--', rel]).trim(), 10);
|
|
143
|
+
if (Number.isFinite(ct) && ct > 0) fecha = ct * 1000;
|
|
144
|
+
} catch { /* fuera de git o archivo sin commits → fallback mtime */ }
|
|
145
|
+
if (fecha === 0) fecha = fs.statSync(v).mtimeMs;
|
|
146
|
+
ultimaVerificacion = Math.max(ultimaVerificacion, fecha);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (ultimaVerificacion > 0) {
|
|
150
|
+
const post = fixes.filter(l => {
|
|
151
|
+
const t = Date.parse(l.split('|')[0]);
|
|
152
|
+
return !Number.isNaN(t) && t > ultimaVerificacion;
|
|
153
|
+
});
|
|
154
|
+
escapes = {
|
|
155
|
+
disponible: true,
|
|
156
|
+
fixesTrasUltimaVerificacion: post.length,
|
|
157
|
+
ultimaVerificacion: new Date(ultimaVerificacion).toLocaleDateString('sv'),
|
|
158
|
+
nota: 'lo que el harness NO atrapó — la métrica que lo castiga',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
} catch { /* best-effort */ }
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
disponible: true,
|
|
165
|
+
procedencia: 'git log (independiente del sistema)',
|
|
166
|
+
caracter: 'proxy',
|
|
167
|
+
commits: sujetos.length,
|
|
168
|
+
fixes: fixes.length,
|
|
169
|
+
feats: feats.length,
|
|
170
|
+
ratioFixFeat: feats.length > 0 ? +(fixes.length / feats.length).toFixed(2) : null,
|
|
171
|
+
escapes,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── 3 y 4. DORA/CI y uso (reuso de fuentes existentes) ─────────────────────
|
|
176
|
+
|
|
177
|
+
function doraCi(raiz, { ventanaDias = 30, hoy = new Date() } = {}) {
|
|
178
|
+
try {
|
|
179
|
+
const { calcularDora } = require('./metricas-dora.js');
|
|
180
|
+
const d = calcularDora(raiz, { ventanaDias, hoy });
|
|
181
|
+
return { disponible: true, procedencia: 'git + GitHub Actions (gh)', caracter: 'evidencia (tendencia; correlación, no causalidad)', dora: d };
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return { disponible: false, razon: `metricas-dora no disponible: ${err.message.split(/\r?\n/)[0]}` };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function usoSistema(raiz) {
|
|
188
|
+
const ruta = path.join(raiz, '.planning', 'skill-metrics.json');
|
|
189
|
+
if (!fs.existsSync(ruta)) {
|
|
190
|
+
const razon = hooksRegistradosEnProyecto(raiz)
|
|
191
|
+
? 'sin skill-metrics.json aún (hooks registrados; se acumula con el uso de skills)'
|
|
192
|
+
: HINT_ACTIVACION;
|
|
193
|
+
return { disponible: false, razon };
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
// Forma real: { version, entries: [{skill, estado, timestamp}, ...] }
|
|
197
|
+
// (eventos individuales del hook telemetria-skill-routing) — agregamos aquí.
|
|
198
|
+
const m = JSON.parse(fs.readFileSync(ruta, 'utf8'));
|
|
199
|
+
const eventos = Object.values(m.entries || {});
|
|
200
|
+
const porSkill = {};
|
|
201
|
+
for (const e of eventos) {
|
|
202
|
+
if (!e || !e.skill) continue;
|
|
203
|
+
porSkill[e.skill] = (porSkill[e.skill] || 0) + 1;
|
|
204
|
+
}
|
|
205
|
+
const top = Object.entries(porSkill).sort((a, b) => b[1] - a[1]).slice(0, 5)
|
|
206
|
+
.map(([k, n]) => `${k}:${n}`);
|
|
207
|
+
return { disponible: true, procedencia: 'telemetria-skill-routing', caracter: 'contexto (solo correlación)', invocaciones: eventos.length, top5: top };
|
|
208
|
+
} catch (err) {
|
|
209
|
+
return { disponible: false, razon: `skill-metrics ilegible: ${err.message}` };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Panel ────────────────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
function panelEvidencia(raiz = process.cwd(), opts = {}) {
|
|
216
|
+
const ventanaDias = opts.ventanaDias || 30;
|
|
217
|
+
return {
|
|
218
|
+
generado: new Date().toLocaleDateString('sv'),
|
|
219
|
+
ventanaDias,
|
|
220
|
+
frontera: 'Datos 100% locales del proyecto (.planning/ + git). El repo madre no lee nada de aquí (docs/evidencia-valor.md).',
|
|
221
|
+
intercepciones: intercepciones(raiz, { ventanaDias, hoy: opts.hoy }),
|
|
222
|
+
defectos: presionDefectos(raiz, { ventanaDias, hoy: opts.hoy, git: opts.git }),
|
|
223
|
+
doraCi: doraCi(raiz, { ventanaDias, hoy: opts.hoy }),
|
|
224
|
+
uso: usoSistema(raiz),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = { panelEvidencia, intercepciones, presionDefectos, leerNudges, usoSistema, TIPOS_INTERVENCION };
|
|
@@ -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.
|
|
5
|
-
"actualizado": "2026-
|
|
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.
|
|
207
|
-
: (currentDate || new Date().
|
|
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;
|