@saulwade/swl-ses 2.2.1 → 2.2.4

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 (52) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +1 -1
  3. package/agentes/arquitecto-swl.md +1 -1
  4. package/agentes/auto-evolucion-swl.md +908 -908
  5. package/agentes/backend-csharp-swl.md +1 -1
  6. package/agentes/backend-go-swl.md +1 -1
  7. package/agentes/backend-java-swl.md +1 -1
  8. package/agentes/backend-rust-swl.md +1 -1
  9. package/agentes/evals/orquestador-swl.evals.json +1 -1
  10. package/agentes/implementador-swl.md +2 -2
  11. package/agentes/llm-apps-swl.md +1 -1
  12. package/agentes/orquestador-swl.md +7 -7
  13. package/agentes/pagos-swl.md +1 -1
  14. package/agentes/perfilador-usuario-swl.md +321 -321
  15. package/agentes/planificador-swl.md +1 -1
  16. package/agentes/producto-prd-swl.md +1 -1
  17. package/agentes/revisor-seguridad-swl.md +1 -1
  18. package/agentes/sre-swl.md +1 -1
  19. package/bin/swl-mcp-server.js +1 -1
  20. package/comandos/swl/release.md +15 -0
  21. package/comandos/swl/verificar.md +817 -817
  22. package/habilidades/compactacion-contexto/SKILL.md +3 -3
  23. package/habilidades/context-builder/SKILL.md +4 -4
  24. package/habilidades/control-profundidad/SKILL.md +5 -5
  25. package/habilidades/guardrail-semantico/SKILL.md +1 -1
  26. package/habilidades/harness-claude-code/SKILL.md +305 -305
  27. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +1 -1
  28. package/habilidades/prompt-engineering/SKILL.md +5 -5
  29. package/habilidades/release-semver/SKILL.md +4 -1
  30. package/habilidades/workflow-claude-code/SKILL.md +6 -6
  31. package/hooks/lib/context-builder.js +2 -2
  32. package/hooks/lib/etapa-perfil-usuario.js +1 -1
  33. package/hooks/lib/model-router.js +1 -1
  34. package/hooks/lib/token-estimator.js +2 -0
  35. package/hooks/linea-estado.js +7 -5
  36. package/hooks/resumen-sesion.js +4 -4
  37. package/llms.txt +1 -1
  38. package/manifiestos/canonical-hashes.json +1637 -656
  39. package/manifiestos/skills-lock.json +1282 -1282
  40. package/package.json +93 -93
  41. package/plugin.json +371 -371
  42. package/reglas/git-coauthor.md +100 -100
  43. package/reglas/pruebas.md +15 -0
  44. package/schemas/agent-frontmatter.schema.json +294 -294
  45. package/scripts/doctor.js +13 -0
  46. package/scripts/instalador.js +22 -9
  47. package/scripts/lib/audit-evolved.js +7 -2
  48. package/scripts/lib/claude-sessions.js +1 -0
  49. package/scripts/lib/manifiestos.js +1 -1
  50. package/scripts/lib/tool-cost-analyzer.js +1 -0
  51. package/scripts/publicar.js +76 -90
  52. package/scripts/remediar-evolved-instaladas.js +6 -3
@@ -606,6 +606,7 @@ async function install(opciones) {
606
606
  try {
607
607
  const resultado = instalarArchivo(archivo, rutas, runtime, {
608
608
  force,
609
+ esGlobal,
609
610
  versionAnterior,
610
611
  versionActual: VERSION,
611
612
  estado,
@@ -683,7 +684,7 @@ async function install(opciones) {
683
684
  // Instalar userland (prioridad sobre core)
684
685
  for (const archivo of archivosUserland) {
685
686
  try {
686
- const resultado = instalarArchivo(archivo, rutas, runtime, { force: true });
687
+ const resultado = instalarArchivo(archivo, rutas, runtime, { force: true, esGlobal });
687
688
  if (resultado.instalado) {
688
689
  registrarArchivo(estado, {
689
690
  origen: archivo.rutaRelativa,
@@ -1173,6 +1174,18 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1173
1174
  const cmpOrigen = esDirSkill ? path.join(origenAbsoluto, 'SKILL.md') : origenAbsoluto;
1174
1175
  const versionBackup = opciones.versionAnterior || opciones.versionActual || 'previa';
1175
1176
 
1177
+ // C-3: los artefactos de gobernanza evolved (AUDITORIA.md + diffs de
1178
+ // reconciliación) se enraízan en la base del install. En scope GLOBAL
1179
+ // (~/.claude, ~/.codex) usan `.swl-evolved-backups/` para NO crear un dir
1180
+ // `.planning/` de proyecto dentro del dir de config global; en proyecto
1181
+ // usan `.planning/` (gobernanza.md).
1182
+ const govBase = rutas.base || process.cwd();
1183
+ const govSubdir = opciones.esGlobal ? '.swl-evolved-backups' : '.planning';
1184
+ const reconcileDir = govSubdir === '.planning'
1185
+ ? path.join(govBase, '.planning', 'evolution', 'reconcile')
1186
+ : path.join(govBase, '.swl-evolved-backups', 'reconcile');
1187
+ const auditEvolved = (e) => auditarEscritura({ ...e, cwd: govBase, subdir: govSubdir });
1188
+
1176
1189
  // MEDIO (nemesis O6): dir de skill existe pero sin SKILL.md = instalación
1177
1190
  // corrupta. Diagnóstico explícito (cae al flujo normal, no rompe invariante).
1178
1191
  if (esDirSkill && !fs.existsSync(cmpDestino)) {
@@ -1193,7 +1206,7 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1193
1206
  const backup = crearBackup(process.cwd(), cmpDestino, versionBackup);
1194
1207
  if (!backup.respaldado) {
1195
1208
  console.log(` ⚠ Backup falló (${backup.error || 'desconocido'}) para ${nombreArchivo} — merge abortado, original preservado`);
1196
- auditarEscritura({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'A', accion: 'preserve', hashAntes, evidencia: `backup falló: ${backup.error || 'desconocido'}` });
1209
+ auditEvolved({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'A', accion: 'preserve', hashAntes, evidencia: `backup falló: ${backup.error || 'desconocido'}` });
1197
1210
  return { instalado: false, evolucionado: true, destino, error: 'backup-failed' };
1198
1211
  }
1199
1212
  const backupPath = backup.rutaBackup;
@@ -1202,8 +1215,8 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1202
1215
  if (opciones.estado) {
1203
1216
  registrarBackup(opciones.estado, { rutaOriginal: cmpDestino, rutaBackup: backupPath, version: versionBackup, evolucionado: true });
1204
1217
  }
1205
- // Diff centralizado bajo el root del install (no adyacente al comando/skill).
1206
- const reconcileDir = path.join(rutas.base || process.cwd(), '.planning', 'evolution', 'reconcile');
1218
+ // Diff centralizado bajo el root del install (reconcileDir calculado arriba,
1219
+ // scope-aware: .planning en proyecto, .swl-evolved-backups en global).
1207
1220
  const merge = mergeEvolved(cmpDestino, cmpOrigen, opciones.versionActual || '', { diffDir: reconcileDir });
1208
1221
  if (merge.merged && merge.diffPath) {
1209
1222
  console.log(` ⊕ Diff generado: ${path.relative(process.cwd(), merge.diffPath)} (${merge.diffsCount} mutaciones)`);
@@ -1218,7 +1231,7 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1218
1231
  recursosActualizados = true;
1219
1232
  } catch (e) { console.log(` ⚠ No se pudieron actualizar recursos del skill: ${e.message}`); }
1220
1233
  }
1221
- const aud = auditarEscritura({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'A', accion: 'merge', hashAntes, backupPath, evidencia: strategy.reason });
1234
+ const aud = auditEvolved({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'A', accion: 'merge', hashAntes, backupPath, evidencia: strategy.reason });
1222
1235
  if (!aud.registrado) console.log(` ⚠ Auditoría no registrada: ${aud.error}`);
1223
1236
  return { instalado: recursosActualizados, evolucionado: true, destino };
1224
1237
  }
@@ -1236,12 +1249,12 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1236
1249
  let backupOk = false;
1237
1250
  try { copiarDirectorio(destino, backupDir); backupOk = true; } catch (e) { console.log(` ⚠ Backup de directorio falló (${e.message})`); }
1238
1251
  if (!backupOk) {
1239
- auditarEscritura({ archivo: path.relative(process.cwd(), destino), clasificacion: 'B', accion: 'preserve', hashAntes, evidencia: 'backup de directorio falló' });
1252
+ auditEvolved({ archivo: path.relative(process.cwd(), destino), clasificacion: 'B', accion: 'preserve', hashAntes, evidencia: 'backup de directorio falló' });
1240
1253
  return { instalado: false, evolucionado: true, destino, error: 'backup-failed' };
1241
1254
  }
1242
1255
  console.log(` ⇪ Actualizado (shipped-evolved): ${nombreArchivo} — ${strategy.reason}`);
1243
1256
  if (opciones.estado) registrarBackup(opciones.estado, { rutaOriginal: destino, rutaBackup: backupDir, version: versionBackup, evolucionado: true });
1244
- const aud = auditarEscritura({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'B', accion: 'overwrite', hashAntes, hashDespues, backupPath: backupDir, evidencia: strategy.reason });
1257
+ const aud = auditEvolved({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'B', accion: 'overwrite', hashAntes, hashDespues, backupPath: backupDir, evidencia: strategy.reason });
1245
1258
  if (!aud.registrado) console.log(` ⚠ Auditoría no registrada: ${aud.error}`);
1246
1259
  copiarDirectorio(origenAbsoluto, destino); // overwrite dir completo (recursos + SKILL.md)
1247
1260
  return { instalado: true, evolucionado: true, destino };
@@ -1252,7 +1265,7 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1252
1265
  // H1 (nemesis O5): si el backup falla, NO sobrescribir — preservar original.
1253
1266
  if (!backup.respaldado) {
1254
1267
  console.log(` ⚠ Backup falló (${backup.error || 'desconocido'}) para ${nombreArchivo} — overwrite abortado, original preservado`);
1255
- auditarEscritura({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'B', accion: 'preserve', hashAntes, evidencia: `backup falló: ${backup.error || 'desconocido'}` });
1268
+ auditEvolved({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'B', accion: 'preserve', hashAntes, evidencia: `backup falló: ${backup.error || 'desconocido'}` });
1256
1269
  return { instalado: false, evolucionado: true, destino, error: 'backup-failed' };
1257
1270
  }
1258
1271
  const backupPath = backup.rutaBackup;
@@ -1260,7 +1273,7 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1260
1273
  if (opciones.estado) {
1261
1274
  registrarBackup(opciones.estado, { rutaOriginal: cmpDestino, rutaBackup: backupPath, version: versionBackup, evolucionado: true });
1262
1275
  }
1263
- const aud = auditarEscritura({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'B', accion: 'overwrite', hashAntes, hashDespues, backupPath, evidencia: strategy.reason });
1276
+ const aud = auditEvolved({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'B', accion: 'overwrite', hashAntes, hashDespues, backupPath, evidencia: strategy.reason });
1264
1277
  if (!aud.registrado) console.log(` ⚠ Auditoría no registrada: ${aud.error}`);
1265
1278
  forzarEscrituraEvolved = true; // bypassa la verificación de colisión sin --force
1266
1279
  }
@@ -34,12 +34,17 @@ const HEADER = '# AUDITORIA\n\n' +
34
34
  * @param {string} [e.backupPath] - Ruta del backup creado antes de escribir.
35
35
  * @param {string} [e.evidencia] - Razón/criterio que clasificó A vs B.
36
36
  * @param {string} [e.timestamp] - ISO; default `new Date().toISOString()`.
37
- * @param {string} [e.cwd] - Raíz del proyecto; default process.cwd().
37
+ * @param {string} [e.cwd] - Raíz de la operación; default process.cwd().
38
+ * @param {string} [e.subdir] - Subdir bajo `cwd` donde vive AUDITORIA.md.
39
+ * Default `.planning` (scope proyecto, gobernanza.md). En scope global/runtime
40
+ * (~/.claude, ~/.codex) usar `.swl-evolved-backups` para NO crear un dir
41
+ * `.planning/` de proyecto dentro del dir de config global (C-3 Fase 16.1).
38
42
  * @returns {{ registrado: boolean, ruta: string }}
39
43
  */
40
44
  function auditarEscritura(e) {
41
45
  const cwd = e.cwd || process.cwd();
42
- const ruta = path.join(cwd, '.planning', 'AUDITORIA.md');
46
+ const subdir = e.subdir || '.planning';
47
+ const ruta = path.join(cwd, subdir, 'AUDITORIA.md');
43
48
  try {
44
49
  fs.mkdirSync(path.dirname(ruta), { recursive: true });
45
50
  if (!fs.existsSync(ruta)) fs.writeFileSync(ruta, HEADER, 'utf8');
@@ -27,6 +27,7 @@ const TOLERANCIA_FUTURO_MS = 60 * 1000;
27
27
 
28
28
  // Tabla de precios por modelo (USD por token)
29
29
  const PRECIOS_MODELO = {
30
+ 'claude-opus-4-8': { entrada: 15 / 1_000_000, salida: 75 / 1_000_000 },
30
31
  'claude-opus-4-7': { entrada: 15 / 1_000_000, salida: 75 / 1_000_000 },
31
32
  'claude-opus-4-6': { entrada: 15 / 1_000_000, salida: 75 / 1_000_000 },
32
33
  'claude-sonnet-4-6': { entrada: 3 / 1_000_000, salida: 15 / 1_000_000 },
@@ -93,7 +93,7 @@ function resolverPerfil(nombrePerfil, opciones = {}) {
93
93
 
94
94
  // Verificar soporte de target
95
95
  if (opciones.target && modulo.targets && !modulo.targets.includes(opciones.target)) {
96
- warnings.push(`Módulo "${nombreModulo}" no soporta target "${opciones.target}", omitido`);
96
+ warnings.push(`Módulo "${nombreModulo}" no declarado para target "${opciones.target}" (omitido; declarado para: ${modulo.targets.join(', ')})`);
97
97
  continue;
98
98
  }
99
99
 
@@ -23,6 +23,7 @@ const DB_PATH = path.join(os.homedir(), '.claude', 'usage.db');
23
23
 
24
24
  // Precios estimados por 1M tokens (USD) — actualizados a 2026-04-17
25
25
  const PRECIOS = {
26
+ 'claude-opus-4-8': { input: 15.00, output: 75.00, cache_read: 1.50, cache_creation: 18.75 },
26
27
  'claude-opus-4-7': { input: 15.00, output: 75.00, cache_read: 1.50, cache_creation: 18.75 },
27
28
  'claude-opus-4-6': { input: 15.00, output: 75.00, cache_read: 1.50, cache_creation: 18.75 },
28
29
  'claude-sonnet-4-6': { input: 3.00, output: 15.00, cache_read: 0.30, cache_creation: 3.75 },
@@ -16,7 +16,6 @@ const { execFileSync, spawnSync } = require('child_process');
16
16
  const fs = require('fs');
17
17
  const path = require('path');
18
18
  const os = require('os');
19
- const readline = require('readline');
20
19
 
21
20
  const {
22
21
  NOMBRE_GITHUB_MIRROR: GITHUB_NAME,
@@ -32,6 +31,32 @@ const {
32
31
  const ROOT = path.resolve(__dirname, '..');
33
32
  const PKG_PATH = path.join(ROOT, 'package.json');
34
33
 
34
+ // --- Log de la corrida (.planning/logs/publish-<version>-<ts>.log) -----------
35
+ // Captura stdout+stderr del publish para diagnóstico: npm v10 NO escribe el
36
+ // stdout de un script fallido (prepublishOnly/test:release) en su debug log,
37
+ // así que sin esto el test que rompe el publish queda invisible. Tee: eco a
38
+ // consola + archivo persistente.
39
+ let LOG_PATH = null;
40
+ function iniciarLog(version) {
41
+ try {
42
+ const dir = path.join(ROOT, '.planning', 'logs');
43
+ fs.mkdirSync(dir, { recursive: true });
44
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
45
+ LOG_PATH = path.join(dir, `publish-${version}-${ts}.log`);
46
+ // Escritura SÍNCRONA (writeFileSync/appendFileSync): un createWriteStream
47
+ // async + process.exit() inmediato perdía el buffer sin volcarlo a disco
48
+ // (dejaba el log vacío). appendFileSync vacía en cada llamada.
49
+ fs.writeFileSync(LOG_PATH, `# publish ${version} — ${new Date().toISOString()}\n# argv: ${process.argv.slice(2).join(' ')}\n\n`);
50
+ } catch (err) {
51
+ process.stderr.write(`[publicar] no se pudo abrir log: ${String(err.message).slice(0, 120)}\n`);
52
+ LOG_PATH = null;
53
+ }
54
+ }
55
+ function logWrite(text) {
56
+ if (LOG_PATH && text) { try { fs.appendFileSync(LOG_PATH, text); } catch { /* best-effort */ } }
57
+ }
58
+ function cerrarLog() { /* no-op: appendFileSync ya volcó todo a disco */ }
59
+
35
60
  /**
36
61
  * Resuelve el binario de npm a invocar. Usa node + npm-cli.js cuando es
37
62
  * posible para evitar shell:true en Windows (mismo enfoque que la lib
@@ -60,15 +85,19 @@ function npmExec(args, opts = {}) {
60
85
  }
61
86
 
62
87
  /**
63
- * Variante de npmExec que captura stderr para detección posterior de errores
64
- * estructurados (ej: EOTP). stdout sigue heredado (live-streaming visible al
65
- * usuario) y stderr se pipea a un buffer + se ecoa a process.stderr al final.
88
+ * Variante de npmExec que captura stdout+stderr para (a) detección de errores
89
+ * estructurados (ej: EOTP) y (b) persistencia en el log de la corrida
90
+ * (`iniciarLog`). Ambos streams se pipean, se ecoan a consola al terminar y se
91
+ * vuelcan al log — así el stdout del script que rompe el publish (test:release)
92
+ * queda registrado. Trade-off: la salida del prepublish aparece al finalizar el
93
+ * spawn (no en vivo), por eso se imprime un aviso antes.
66
94
  *
67
95
  * Retorna { status, stderr } sin lanzar — el caller inspecciona el status.
68
96
  */
69
97
  function npmSpawnCaptureStderr(args, opts = {}) {
70
98
  const defaults = { cwd: ROOT, timeout: 300_000, env: process.env };
71
- const merged = { ...defaults, ...opts, stdio: ['inherit', 'inherit', 'pipe'] };
99
+ const merged = { ...defaults, ...opts, stdio: ['inherit', 'pipe', 'pipe'] };
100
+ process.stderr.write('[publicar] ejecutando npm (incluye prepublish/test:release; salida completa al finalizar y en el log)...\n');
72
101
  let res;
73
102
  if (NPM_CLI_JS) {
74
103
  res = spawnSync(process.execPath, [NPM_CLI_JS, ...args], merged);
@@ -76,9 +105,13 @@ function npmSpawnCaptureStderr(args, opts = {}) {
76
105
  const bin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
77
106
  res = spawnSync(bin, args, merged);
78
107
  }
108
+ const stdout = res.stdout ? String(res.stdout) : '';
79
109
  const stderr = res.stderr ? String(res.stderr) : '';
80
- // Ecoar stderr al usuario para que vea el diagnóstico de npm aunque se capturó.
110
+ // Eco a consola + volcado al log (stdout primero, luego stderr).
111
+ if (stdout) process.stdout.write(stdout);
81
112
  if (stderr) process.stderr.write(stderr);
113
+ logWrite(stdout);
114
+ logWrite(stderr);
82
115
  return { status: res.status, signal: res.signal, stderr, error: res.error };
83
116
  }
84
117
 
@@ -97,84 +130,35 @@ function esErrorOTP(stderr) {
97
130
  }
98
131
 
99
132
  /**
100
- * Solicita una OTP al usuario via stdin (readline síncrono).
101
- * Retorna la OTP normalizada (string de 6 dígitos) o null si:
102
- * - stdin no es TTY (CI, pipe) no se puede pedir interactivamente
103
- * - el usuario cancela con Ctrl+C / línea vacía
104
- * - el formato no es válido (no 6 dígitos)
105
- *
106
- * El caller debe manejar el caso null como "fallback a guía manual".
107
- */
108
- function solicitarOTPInteractiva() {
109
- if (!process.stdin.isTTY) {
110
- process.stderr.write('[publicar] stdin no es TTY: no se puede pedir OTP interactivamente.\n');
111
- process.stderr.write('[publicar] Configurar NPM_CONFIG_OTP=<otp> antes de invocar este script.\n');
112
- return null;
113
- }
114
-
115
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
116
- // questionSync vía promesa NO funciona aquí — el script es síncrono.
117
- // Usamos un truco: readline es async-only, así que dejamos al usuario
118
- // el manejo bloqueante leyendo línea por línea con readSync vía read-int.
119
- // Pero readline no expone modo síncrono. Alternativa: leer de stdin con
120
- // fd 0 + readSync. En Windows con TTY funciona; en Linux idem.
121
- try {
122
- process.stderr.write('\nNPM requiere OTP (2FA). Ingresa el código de 6 dígitos de tu autenticador: ');
123
- const otp = leerLineaStdinSync().trim();
124
- rl.close();
125
- if (!/^\d{6}$/.test(otp)) {
126
- process.stderr.write(`\n[publicar] OTP inválida: se esperan 6 dígitos, se recibió: "${otp.slice(0, 20)}"\n`);
127
- return null;
128
- }
129
- return otp;
130
- } catch (err) {
131
- rl.close();
132
- process.stderr.write(`\n[publicar] error leyendo OTP: ${String(err.message).slice(0, 120)}\n`);
133
- return null;
134
- }
135
- }
136
-
137
- /**
138
- * Lectura síncrona de una línea de stdin. Necesario porque readline solo
139
- * expone API asíncrona, pero todo el flujo de publicar.js es síncrono.
140
- * Lee byte por byte hasta encontrar newline o EOF.
133
+ * Ejecuta npm con stdio HEREDADO (interactivo): npm escribe directo a la
134
+ * terminal y lee de stdin, de modo que su flujo de 2FA NATIVO funciona —
135
+ * prompt de OTP TOTP O auth web/passkey ("abrir URL y seleccionar la llave"),
136
+ * según el método 2FA de la cuenta. Retorna el exit status sin lanzar.
137
+ * (A1: soporte passkey/WebAuthn, no solo TOTP.)
141
138
  */
142
- function leerLineaStdinSync() {
143
- const BUFFER_SIZE = 256;
144
- const buf = Buffer.alloc(BUFFER_SIZE);
145
- let line = '';
146
- while (true) {
147
- let bytesRead;
148
- try {
149
- bytesRead = fs.readSync(0, buf, 0, BUFFER_SIZE, null);
150
- } catch (err) {
151
- // EAGAIN en stdin no-bloqueante: poco común en TTY, pero defensivo.
152
- if (err.code === 'EAGAIN') continue;
153
- throw err;
154
- }
155
- if (bytesRead === 0) break; // EOF
156
- const chunk = buf.slice(0, bytesRead).toString('utf-8');
157
- const nlIdx = chunk.indexOf('\n');
158
- if (nlIdx >= 0) {
159
- line += chunk.slice(0, nlIdx);
160
- break;
161
- }
162
- line += chunk;
139
+ function npmExecInherit(args, opts = {}) {
140
+ const merged = { cwd: ROOT, timeout: 600_000, env: process.env, ...opts, stdio: 'inherit' };
141
+ let res;
142
+ if (NPM_CLI_JS) {
143
+ res = spawnSync(process.execPath, [NPM_CLI_JS, ...args], merged);
144
+ } else {
145
+ const bin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
146
+ res = spawnSync(bin, args, merged);
163
147
  }
164
- return line.replace(/\r$/, ''); // Windows envía \r\n
148
+ return res.status;
165
149
  }
166
150
 
167
151
  /**
168
152
  * Ejecuta un `npm publish` con soporte automático de OTP (2FA).
169
153
  *
170
154
  * Flujo:
171
- * 1. Si NPM_CONFIG_OTP está definida en el entorno, se usa directamente
172
- * (npm la lee automáticamente no hay que pasarla como flag).
173
- * 2. Si el publish falla con EOTP/one-time password, se intenta solicitar
174
- * la OTP interactivamente y reintentar con --ignore-scripts (los
175
- * scripts pre-publish ya pasaron en el primer intento).
176
- * 3. Si no se puede obtener OTP (no TTY o usuario cancela), reporta el
177
- * error y retorna false.
155
+ * 1. Si NPM_CONFIG_OTP está definida en el entorno (o hay token Automation),
156
+ * se usa directamente y el intento 1 publica sin interacción.
157
+ * 2. Si el publish falla con EOTP/one-time password y stdin es TTY, se
158
+ * reintenta con stdio HEREDADO para que npm conduzca su 2FA NATIVO —
159
+ * OTP TOTP O passkey/WebAuthn (navegador), según la cuenta — con
160
+ * --ignore-scripts (los pre-publish ya pasaron en el intento 1).
161
+ * 3. Si stdin no es TTY (CI sin OTP), reporta el error y retorna false.
178
162
  *
179
163
  * Retorna: { ok: boolean, stderr: string }
180
164
  *
@@ -192,24 +176,23 @@ function ejecutarPublishConOTP(args, opts = {}) {
192
176
  return { ok: false, stderr: intento1.stderr };
193
177
  }
194
178
 
195
- // Es EOTP. Intentar solicitar OTP interactivamente.
179
+ // Es EOTP — 2FA requerida. npm soporta varios métodos: OTP TOTP (6 dígitos)
180
+ // O passkey/WebAuthn vía navegador ("abrir URL y seleccionar la llave").
181
+ // Forzar un prompt de TOTP rompe a los usuarios con passkey (A1). En su lugar
182
+ // se reintenta con stdio HEREDADO para que npm conduzca su 2FA NATIVO —
183
+ // funcione cual sea el método configurado en la cuenta.
196
184
  process.stderr.write('\n[publicar] npm rechazó el publish con EOTP (2FA requerida).\n');
197
- const otp = solicitarOTPInteractiva();
198
- if (!otp) {
199
- process.stderr.write('[publicar] No se obtuvo OTP. Aborta. Reintentar con:\n');
200
- process.stderr.write(` NPM_CONFIG_OTP=<otp> node scripts/publicar.js ${process.argv.slice(2).join(' ')}\n`);
185
+ if (!process.stdin.isTTY) {
186
+ process.stderr.write('[publicar] stdin no es TTY: no se puede completar 2FA interactivo.\n');
187
+ process.stderr.write('[publicar] Opciones: exportar NPM_CONFIG_OTP=<otp>, o usar un token Automation (omite 2FA).\n');
201
188
  return { ok: false, stderr: intento1.stderr };
202
189
  }
203
-
204
- // Reintento con OTP via env + --ignore-scripts (los pre-publish ya pasaron).
205
- process.stderr.write('\n[publicar] Reintentando publish con OTP recibida...\n');
206
- const envConOTP = { ...(opts.env || process.env), NPM_CONFIG_OTP: otp };
190
+ // Reintento interactivo: --ignore-scripts porque los pre-publish ya pasaron en
191
+ // el intento 1 (el prepack limpió el árbol; no se re-ejecuta test:release).
192
+ process.stderr.write('\n[publicar] Reintentando con el 2FA interactivo nativo de npm (OTP o passkey)...\n');
207
193
  const argsConIgnore = args.includes('--ignore-scripts') ? args : [...args, '--ignore-scripts'];
208
- const intento2 = npmSpawnCaptureStderr(argsConIgnore, { ...opts, env: envConOTP });
209
- if (intento2.status === 0) {
210
- return { ok: true, stderr: intento2.stderr };
211
- }
212
- return { ok: false, stderr: intento2.stderr };
194
+ const status = npmExecInherit(argsConIgnore, opts);
195
+ return { ok: status === 0, stderr: status === 0 ? '' : 'fallo en reintento interactivo de 2FA' };
213
196
  }
214
197
 
215
198
  function leerPkg() {
@@ -482,6 +465,7 @@ function reportarResultado(pkg, opts, npmjsOk, githubOk) {
482
465
  function main() {
483
466
  const opts = parsearArgs(process.argv.slice(2));
484
467
  const pkg = leerPkg();
468
+ iniciarLog(pkg.version);
485
469
  imprimirEncabezado(pkg.version, opts.dryRun);
486
470
  avisarSiHayCambiosSinCommit(opts.dryRun);
487
471
 
@@ -489,6 +473,8 @@ function main() {
489
473
  const githubOk = opts.soloNpmjs ? true : publicarGitHub(pkg, opts.dryRun);
490
474
 
491
475
  reportarResultado(pkg, opts, npmjsOk, githubOk);
476
+ if (LOG_PATH) process.stdout.write(`\nLog de la corrida: ${path.relative(ROOT, LOG_PATH).replace(/\\/g, '/')}\n`);
477
+ cerrarLog();
492
478
  process.exit(npmjsOk && githubOk ? 0 : 1);
493
479
  }
494
480
 
@@ -136,7 +136,10 @@ function remediar({ target, apply = false } = {}) {
136
136
  target = expandirTarget(target);
137
137
  const manifiesto = cargarManifiesto();
138
138
  const ts = new Date().toISOString().replace(/[:.]/g, '-');
139
- const reconcileDir = path.join(target, '.planning', 'evolution', 'reconcile');
139
+ // El target es un dir de config runtime (~/.claude, ~/.codex), NO un proyecto:
140
+ // los artefactos de gobernanza evolved van bajo `.swl-evolved-backups/` (donde
141
+ // ya viven los backups), sin crear un `.planning/` de proyecto ahí (C-3).
142
+ const reconcileDir = path.join(target, '.swl-evolved-backups', 'reconcile');
140
143
  const resumen = { target, A: [], B: [], errores: [], reconcileDir };
141
144
 
142
145
  for (const item of escanear(target)) {
@@ -181,7 +184,7 @@ function remediar({ target, apply = false } = {}) {
181
184
  const hashFuente = canonicalHash(fs.readFileSync(fuenteAbs, 'utf8')); // antes del copy
182
185
  const backupPath = backupSimple(target, item.archivo, ts); // backup PRECEDE al overwrite
183
186
  fs.copyFileSync(fuenteAbs, item.archivo); // fuente limpio (sin evolved-*) → descongelado
184
- auditarEscritura({ archivo: item.sourceRel, clasificacion: 'B', accion: 'overwrite', hashAntes: instaladoHash, hashDespues: hashFuente, backupPath, evidencia: `remediación: ${evidencia}` });
187
+ auditarEscritura({ archivo: item.sourceRel, clasificacion: 'B', accion: 'overwrite', hashAntes: instaladoHash, hashDespues: hashFuente, backupPath, evidencia: `remediación: ${evidencia}`, cwd: target, subdir: '.swl-evolved-backups' });
185
188
  } catch (e) { resumen.errores.push({ archivo: item.sourceRel, error: e.message }); continue; }
186
189
  }
187
190
  resumen.B.push({ archivo: item.sourceRel, from: ev.from, razon: evidencia });
@@ -224,7 +227,7 @@ function imprimir(r, apply) {
224
227
  for (const e of r.errores) process.stdout.write(` ! ${e.archivo}: ${e.error}\n`);
225
228
  }
226
229
  if (apply && r.A.length) process.stdout.write(`\nDiffs de reconciliación (revisión upstream): ${path.relative(r.target, r.reconcileDir).replace(/\\/g, '/')}/INDEX.md\n`);
227
- if (!apply && r.B.length) process.stdout.write(`\n→ Para aplicar: agregar --apply (backups en <target>/.swl-evolved-backups/, diffs centralizados en .planning/evolution/reconcile/).\n`);
230
+ if (!apply && r.B.length) process.stdout.write(`\n→ Para aplicar: agregar --apply (backups, diffs y AUDITORIA centralizados en <target>/.swl-evolved-backups/).\n`);
228
231
  }
229
232
 
230
233
  if (require.main === module) {