@saulwade/swl-ses 2.5.3 → 2.6.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.
- package/CLAUDE.md +9 -9
- package/README.md +37 -37
- package/agentes/_intent-spec.md +73 -73
- package/agentes/_propose-step.md +90 -90
- package/agentes/accesibilidad-wcag-swl.md +690 -690
- package/agentes/arquitecto-swl.md +267 -267
- package/agentes/auto-evolucion-swl.md +932 -908
- 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-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/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/investigador-swl.md +432 -432
- package/agentes/investigador-ux-swl.md +505 -505
- 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/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/revisor-angular-swl.md +278 -278
- 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/tdd-qa-swl.md +393 -393
- package/bin/swl-ses.js +32 -7
- package/comandos/swl/actualizar.md +3 -3
- package/comandos/swl/aprender.md +13 -0
- package/comandos/swl/deuda-codigo.md +97 -97
- package/comandos/swl/evaluar-skill.md +18 -3
- package/comandos/swl/evolucion-continua.md +73 -0
- package/comandos/swl/evolucionar.md +13 -0
- package/comandos/swl/instalar.md +4 -4
- package/comandos/swl/notificaciones.md +1 -1
- package/comandos/swl/status.md +2 -2
- package/gateway/cron/jobs.example.json +12 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +19 -1
- package/habilidades/autoresearch/SKILL.md +3 -2
- package/habilidades/backend-async-postgres-testing/SKILL.md +2 -1
- package/habilidades/benchmark-memoria/SKILL.md +7 -7
- package/habilidades/changelog-generator/SKILL.md +1 -1
- package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
- package/habilidades/checkpoints-verificacion/SKILL.md +6 -0
- package/habilidades/compactacion-contexto/SKILL.md +2 -1
- package/habilidades/contenedores-docker/SKILL.md +4 -2
- package/habilidades/context-builder/SKILL.md +4 -0
- package/habilidades/doubt-driven-review/SKILL.md +17 -1
- package/habilidades/drift-detection/SKILL.md +6 -1
- package/habilidades/ejecutar-fase/SKILL.md +6 -6
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/eval-framework/SKILL.md +8 -3
- package/habilidades/extractor-de-aprendizajes/SKILL.md +8 -2
- package/habilidades/git-worktrees-paralelo/SKILL.md +19 -1
- package/habilidades/harness-claude-code/SKILL.md +7 -3
- package/habilidades/infra-github-actions/SKILL.md +4 -3
- package/habilidades/instalar-sistema/SKILL.md +5 -1
- package/habilidades/memoria-busqueda/SKILL.md +31 -39
- package/habilidades/planear-fase/SKILL.md +9 -1
- 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-ddia-fundamentos/SKILL.md +3 -2
- package/habilidades/proceso-ingenieria-requerimientos/SKILL.md +147 -147
- package/habilidades/release-semver/SKILL.md +2 -2
- package/habilidades/swl-claudemd/SKILL.md +6 -7
- package/habilidades/swl-dashboard/SKILL.md +11 -43
- package/habilidades/tdd-workflow/SKILL.md +12 -7
- package/habilidades/validacion-ci-sistema/SKILL.md +1 -1
- package/hooks/agente-lifecycle.js +2 -1
- package/hooks/aiisms-detector.js +13 -4
- package/hooks/audit-trail.js +2 -1
- package/hooks/auto-consolidacion.js +2 -1
- package/hooks/captura-acciones-post.js +2 -1
- package/hooks/captura-acciones-session.js +2 -1
- package/hooks/captura-feedback-usuario.js +3 -2
- package/hooks/claudemd-bloat-detector.js +12 -3
- package/hooks/claudemd-duplicacion-detector.js +13 -3
- package/hooks/contexto-iteracion.js +2 -1
- package/hooks/contexto-subagente.js +68 -68
- package/hooks/degradacion-instintos.js +2 -1
- package/hooks/extraccion-aprendizajes.js +109 -15
- package/hooks/grafo-contexto.js +2 -1
- package/hooks/guardrail-modelo.js +2 -1
- package/hooks/inbox-aviso.js +2 -1
- package/hooks/inyeccion-contexto.js +2 -1
- package/hooks/lib/agent-matcher.js +2 -1
- package/hooks/lib/agent-routing.js +2 -1
- package/hooks/lib/autonomia.js +5 -3
- package/hooks/lib/captura-acciones.js +2 -1
- package/hooks/lib/consolidation-lock.js +21 -10
- package/hooks/lib/etapa-auto-evolucion.js +10 -4
- package/hooks/lib/etapa-metricas.js +2 -1
- package/hooks/lib/etapa-perfil-usuario.js +20 -4
- package/hooks/lib/evolution-tracker.js +2 -1
- package/hooks/lib/gateway-notify.js +17 -3
- package/hooks/lib/loop-telemetry.js +5 -4
- package/hooks/lib/mcp-health.js +2 -1
- package/hooks/lib/memory-search.js +4 -0
- package/hooks/lib/merkle-audit.js +58 -6
- package/hooks/lib/notificacion-formato.js +58 -0
- package/hooks/lib/nudge-tracker.js +2 -1
- package/hooks/lib/otlp-exporter.js +2 -1
- package/hooks/lib/propose-step.js +3 -2
- package/hooks/lib/raiz-proyecto.js +127 -0
- package/hooks/lib/run-log.js +2 -1
- package/hooks/lib/singleton-guard.js +225 -27
- package/hooks/lib/telegram-cliente.js +28 -11
- package/hooks/notificacion-telegram.js +13 -3
- package/hooks/preservar-estado-pre-compact.js +2 -1
- package/hooks/proteccion-rutas.js +59 -3
- package/hooks/registro-turnos.js +2 -1
- package/hooks/resumen-sesion.js +2 -1
- package/hooks/risk-scoring.js +2 -1
- package/hooks/rotar-audit-auto.js +46 -20
- package/hooks/session-briefing.js +127 -1
- package/hooks/spec-gate.js +2 -1
- package/hooks/sugerir-contribuir.js +6 -3
- package/hooks/sugerir-regenerar-inventario.js +3 -2
- package/hooks/tdd-gate.js +2 -1
- package/hooks/telemetria-agentes.js +2 -1
- package/hooks/telemetria-skill-routing.js +2 -1
- package/hooks/tracking-costos.js +4 -3
- package/hooks/validar-formato-post-subagente.js +2 -1
- package/hooks/validar-intent-spec.js +2 -1
- package/hooks/validar-memoria-hook.js +13 -3
- package/hooks/validar-planning-paths.js +2 -1
- package/instintos/perfil-usuario.yaml +506 -3
- package/instintos/proyecto.yaml +78 -0
- package/llms.txt +29 -29
- package/manifiestos/canonical-hashes.json +5588 -4925
- package/manifiestos/hooks-config.json +469 -469
- package/manifiestos/invariantes-criticos.json +30 -30
- package/manifiestos/modulos.json +1429 -1423
- package/manifiestos/planning-paths.json +1 -0
- package/manifiestos/skills-lock.json +1275 -1275
- package/package.json +94 -95
- package/plugin.json +369 -369
- package/scripts/actualizar.js +3 -0
- package/scripts/auditar-clases-conocidas.js +134 -106
- package/scripts/benchmark-memoria.js +1 -0
- package/scripts/bootstrap-instintos.js +85 -14
- package/scripts/canario-hooks.js +166 -166
- package/scripts/cli/autonomia.js +23 -0
- package/scripts/cli/benchmark-memoria.js +37 -0
- package/scripts/cli/ciclo-autonomo.js +73 -0
- package/scripts/cli/ciclo-fase-b.js +102 -0
- package/scripts/cli/guardrail-metrics.js +39 -0
- package/scripts/cli/loop-telemetry.js +4 -2
- package/scripts/cli/memoria-search.js +69 -0
- package/scripts/cli/nudge-accionar.js +39 -0
- package/scripts/cli/run-eval.js +38 -0
- package/scripts/cli/run-skill-evals.js +13 -2
- package/scripts/derivar-feature-list.js +15 -14
- package/scripts/desinstalar.js +11 -0
- package/scripts/doctor.js +50 -13
- package/scripts/evidencia-valor.js +101 -101
- package/scripts/field-report.js +16 -16
- package/scripts/instalador.js +98 -7
- package/scripts/lib/activar-hooks-proyecto.js +116 -104
- package/scripts/lib/auditar-invocaciones-comandos.js +96 -6
- package/scripts/lib/ciclo-autonomo/candidatos.js +174 -0
- package/scripts/lib/ciclo-autonomo/config.js +165 -0
- package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -0
- package/scripts/lib/ciclo-autonomo/fallback.js +77 -0
- package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -0
- package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -0
- package/scripts/lib/ciclo-autonomo/index.js +301 -0
- package/scripts/lib/ciclo-autonomo/lock.js +124 -0
- package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -0
- package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -0
- package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -0
- package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -0
- package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -0
- package/scripts/lib/estado.js +9 -0
- package/scripts/lib/evidencia-valor.js +228 -228
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/gitignore-manifest.js +8 -1
- package/scripts/lib/hooks-settings.js +45 -0
- package/scripts/lib/limpiar-basura-global.js +161 -0
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/rotar-audit-logs.js +48 -2
- package/scripts/run-eval.js +1 -0
- package/scripts/run-skill-evals.js +287 -8
- package/scripts/smoke-test.js +16 -8
- package/scripts/tui/pantallas/install-wizard.js +69 -13
- package/scripts/validar.js +40 -1
|
@@ -1,228 +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 SIN duplicar componentes con: npx -y @saulwade/swl-ses@latest install --target claude --solo-hooks (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
|
+
'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 SIN duplicar componentes con: npx -y @saulwade/swl-ses@latest install --target claude --solo-hooks (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 };
|
|
@@ -76,6 +76,11 @@ const ENTRADAS_BASE = [
|
|
|
76
76
|
// singleton-guard, actualizar-perfil-usuario). Output de runtime, no
|
|
77
77
|
// artefactos del proyecto usuario.
|
|
78
78
|
".planning/evolution/",
|
|
79
|
+
// Shim de migración: instalaciones pre-ADR-0031 (dirs runtime en español)
|
|
80
|
+
// aún escriben aquí hasta que el usuario corra `update` (caso real
|
|
81
|
+
// sistema-verificacion-oic 2026-07-10 con nudges/agent-commits en
|
|
82
|
+
// .planning/evolucion/).
|
|
83
|
+
".planning/evolucion/",
|
|
79
84
|
".planning/auto-evolution/",
|
|
80
85
|
// locks/: contenido runtime ignorado (singleton-guard {pid,ts},
|
|
81
86
|
// fase-activa.json) EXCEPTO los plan-locks de G1 (*PLAN.md.lock), que son
|
|
@@ -102,7 +107,9 @@ const ENTRADAS_BASE = [
|
|
|
102
107
|
// destino vs lo que ENTRADAS_BASE propagaba. Todo esto es output de runtime,
|
|
103
108
|
// regenerable, no editado por el equipo → no se commitea.
|
|
104
109
|
".planning/feature-list.json", // derivar-feature-list.js (derivado de HOJA-RUTA.md)
|
|
105
|
-
|
|
110
|
+
// .planning/loops/ NO se ignora (decisión del usuario 2026-07-10): el
|
|
111
|
+
// historial de loops (iteraciones.tsv de loop-telemetry) es evidencia
|
|
112
|
+
// portable del trabajo — el equipo decide commitearlo como memoria.
|
|
106
113
|
".planning/archive/", // rotar-audit-auto.js (audit/logs rotados y comprimidos)
|
|
107
114
|
".planning/analysis/", // outputs de análisis
|
|
108
115
|
".planning/graph.json", // code-review-graph (cache del grafo)
|