specatlas 0.1.37 → 0.1.39

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 (2) hide show
  1. package/dist/bin.js +267 -60
  2. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -34465,6 +34465,11 @@ function labelsFor(language) {
34465
34465
  signatureName: "Approver name",
34466
34466
  signatureDate: "Signature date",
34467
34467
  signatureNote: "The signature is recorded with the specification hash and the date.",
34468
+ signedBy: "Approved by",
34469
+ signedAt: "Date",
34470
+ signedHash: "Signed hash",
34471
+ signedChannel: "Channel",
34472
+ signHere: "Sign here to approve this proposal.",
34468
34473
  openMockup: "Open mockup",
34469
34474
  mockupHint: "Each screen opens full size in its own tab. In a browser they are also previewed inline.",
34470
34475
  illustrates: "Illustrates"
@@ -34496,6 +34501,11 @@ function labelsFor(language) {
34496
34501
  missingMockups: "Mockups declarados que faltan",
34497
34502
  screenshots: "Capturas",
34498
34503
  print: "Imprimir / Guardar PDF",
34504
+ signedBy: "Aprobada por",
34505
+ signedAt: "Fecha",
34506
+ signedHash: "Huella firmada",
34507
+ signedChannel: "Canal",
34508
+ signHere: "Firma aqu\xED para aprobar esta propuesta.",
34499
34509
  signatureName: "Nombre de quien aprueba",
34500
34510
  signatureDate: "Fecha de la firma",
34501
34511
  signatureNote: "La firma se registra con la huella de la especificaci\xF3n y la fecha.",
@@ -34578,6 +34588,8 @@ th { background:var(--elev); font-size:11px; text-transform:uppercase; letter-sp
34578
34588
  .grid-shots { display:grid; grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); gap:12px; margin-top:12px; }
34579
34589
  .grid-shots img { width:100%; border:1px solid var(--line); border-radius:10px; }
34580
34590
  .signature { display:grid; grid-template-columns:1.2fr .8fr; gap:18px; }
34591
+ .sign-line.signed { border-style:solid; border-color:var(--ok); background:color-mix(in srgb, var(--ok) 8%, transparent); }
34592
+ .sign-line .mono { font-family:ui-monospace, Consolas, monospace; font-size:11.5px; word-break:break-all; }
34581
34593
  .sign-line { border:1px dashed var(--accent); border-radius:8px; padding:16px; background:var(--accent-soft); }
34582
34594
  .sign-line .sl-row { display:flex; justify-content:space-between; gap:12px; border-bottom:1px solid var(--line); padding:0 0 6px; margin-bottom:18px; color:var(--muted); font-size:12.5px; }
34583
34595
  .sign-line .sl-row:last-child { border-bottom:0; margin-bottom:0; padding-top:14px; }
@@ -34624,6 +34636,15 @@ function page(input) {
34624
34636
  return `<tr><td><code>${esc(s.id)}</code></td><td>${esc(s.title)}</td><td><span class="pill ${cls}">${esc(label)}</span></td></tr>`;
34625
34637
  }).join("");
34626
34638
  const signatureState = input.approvalStatus === "valid" && input.approval ? `<div class="sign-state ok"><span class="ss-label">${esc(l.approve)}</span><b>${esc(l.approveDone.replace("{by}", input.approval.by).replace("{at}", input.approval.at))}</b></div>` : input.approvalStatus === "stale" && input.approval ? `<div class="sign-state stale"><span class="ss-label">${esc(l.approve)}</span><b>${esc(l.approveStale)}</b><p>${esc(l.approveDone.replace("{by}", input.approval.by).replace("{at}", input.approval.at))}</p></div>` : `<div class="sign-state"><span class="ss-label">${esc(l.approve)}</span><b>${esc(l.approvePending)}</b><p>${esc(l.approveText)}</p></div>`;
34639
+ const signatureBlock = input.approvalStatus === "valid" && input.approval ? `<div class="sign-line signed">
34640
+ <div class="sl-row"><span>${esc(l.signedBy)}</span><b>${esc(input.approval.by)}</b></div>
34641
+ <div class="sl-row"><span>${esc(l.signedAt)}</span><b>${esc(input.approval.at)}</b></div>
34642
+ <div class="sl-row"><span>${esc(l.signedHash)}</span><span class="mono">${esc(input.hash)}</span></div>
34643
+ </div>` : `<div class="sign-line">
34644
+ <p class="sign-help" style="margin:0 0 12px">${esc(l.signHere)}</p>
34645
+ <div class="sl-row"><span>${esc(l.signatureName)}</span><span style="font-family:ui-monospace,Consolas,monospace">____________________</span></div>
34646
+ <div class="sl-row"><span>${esc(l.signatureDate)}</span><span style="font-family:ui-monospace,Consolas,monospace">____ / ____ / ________</span></div>
34647
+ </div>`;
34627
34648
  const mockupHtml = input.mockups.length === 0 ? `<div class="callout">${esc(l.noMockups)}</div>` : `<p class="sign-help" style="margin:0 0 12px">${esc(l.mockupHint)}</p><div class="mockups">${input.mockups.map(
34628
34649
  (m) => `<div class="mockup ${m.platform === "mobile" ? "phone" : ""}" data-mockup="${esc(m.file)}" data-title="${esc(m.title)}"><header><span>${esc(m.title)}</span><a class="btn" href="${esc(m.file)}" target="_blank" rel="noopener">${esc(l.openMockup)}</a></header><div class="mockup-body"><span class="mockup-file">${esc(m.file)}</span>${m.illustrates.length > 0 ? `<span class="mockup-scn">${esc(l.illustrates)}: ${m.illustrates.map((id) => esc(id)).join(", ")}</span>` : ""}</div></div>`
34629
34650
  ).join("")}</div>`;
@@ -34718,14 +34739,11 @@ function page(input) {
34718
34739
  <div class="card-head"><span class="num">05</span><h2>${esc(l.approve)}</h2><span class="sub">acto humano \xB7 queda auditado</span></div>
34719
34740
  <div class="card-body">
34720
34741
  <div class="signature">
34721
- <div class="sign-line">
34722
- <div class="sl-row"><span>${esc(l.signatureName)}</span><span style="font-family:ui-monospace,Consolas,monospace">____________________</span></div>
34723
- <div class="sl-row"><span>${esc(l.signatureDate)}</span><span style="font-family:ui-monospace,Consolas,monospace">____ / ____ / ________</span></div>
34724
- </div>
34742
+ ${signatureBlock}
34725
34743
  <div>
34726
34744
  <p class="sign-help">${esc(l.signatureNote)}</p>
34727
- <p class="sign-help" style="margin-top:8px"><code>${esc(input.approveHint)}</code></p>
34728
- <p class="sign-help" style="margin-top:10px">${esc(l.approveStale)}</p>
34745
+ ${input.approvalStatus === "valid" ? "" : `<p class="sign-help" style="margin-top:8px"><code>${esc(input.approveHint)}</code></p>`}
34746
+ ${input.approvalStatus === "stale" ? `<p class="sign-help" style="margin-top:10px">${esc(l.approveStale)}</p>` : ""}
34729
34747
  </div>
34730
34748
  </div>
34731
34749
  </div>
@@ -36802,6 +36820,103 @@ function msg(key, language) {
36802
36820
  return dict[key] ?? key;
36803
36821
  }
36804
36822
 
36823
+ // src/ui.ts
36824
+ var CODIGOS = {
36825
+ rojo: [31, 39],
36826
+ verde: [32, 39],
36827
+ amarillo: [33, 39],
36828
+ azul: [34, 39],
36829
+ morado: [35, 39],
36830
+ cian: [36, 39],
36831
+ gris: [90, 39],
36832
+ negrita: [1, 22],
36833
+ atenuado: [2, 22],
36834
+ subrayado: [4, 24],
36835
+ inverso: [7, 27]
36836
+ };
36837
+ function detectarTema(env = process.env, salida = process.stdout) {
36838
+ const forzado = env["FORCE_COLOR"];
36839
+ const color = forzado !== void 0 && forzado !== "0" ? true : env["NO_COLOR"] !== void 0 || env["TERM"] === "dumb" || salida.isTTY !== true ? false : true;
36840
+ const unicode = env["SPECATLAS_ASCII"] === "1" ? false : process.platform !== "win32" || Boolean(env["WT_SESSION"] || env["TERM_PROGRAM"] || env["ConEmuANSI"] || env["TERM"]);
36841
+ const ancho = Math.max(40, Math.min(salida.columns ?? 100, 120));
36842
+ return { color, unicode, ancho };
36843
+ }
36844
+ function pinta(tema, estilo, texto) {
36845
+ if (!tema.color || texto === "") return texto;
36846
+ const estilos = Array.isArray(estilo) ? estilo : [estilo];
36847
+ return estilos.reduce((acc, uno) => {
36848
+ const [abre, cierra] = CODIGOS[uno];
36849
+ return `\x1B[${abre}m${acc}\x1B[${cierra}m`;
36850
+ }, texto);
36851
+ }
36852
+ var UNICODE = { ok: "\u2713", error: "\u2716", aviso: "\u25B2", info: "\u2139", flecha: "\u2192", punto: "\u25CF", bloqueo: "\u2298", pausa: "\u23F8", lleno: "\u25B0", vacio: "\u25B1", separador: "\xB7" };
36853
+ var ASCII = { ok: "v", error: "x", aviso: "!", info: "i", flecha: "->", punto: "*", bloqueo: "o", pausa: "||", lleno: "#", vacio: "-", separador: "-" };
36854
+ function simbolos(tema) {
36855
+ return tema.unicode ? UNICODE : ASCII;
36856
+ }
36857
+ function anchoVisible(texto) {
36858
+ return texto.replace(/\u001b\[[0-9;]*m/g, "").length;
36859
+ }
36860
+ function rellenar(texto, ancho) {
36861
+ const falta = ancho - anchoVisible(texto);
36862
+ return falta > 0 ? texto + " ".repeat(falta) : texto;
36863
+ }
36864
+ function encabezado(tema, titulo, contexto) {
36865
+ const izquierda = pinta(tema, "negrita", titulo);
36866
+ if (!contexto) return [izquierda, ""];
36867
+ const hueco = tema.ancho - anchoVisible(izquierda) - anchoVisible(contexto);
36868
+ if (hueco < 2) return [izquierda, pinta(tema, "gris", contexto), ""];
36869
+ return [`${izquierda}${" ".repeat(hueco)}${pinta(tema, "gris", contexto)}`, ""];
36870
+ }
36871
+ function barraProgreso(tema, hecho, total, ancho = 8) {
36872
+ const s = simbolos(tema);
36873
+ if (total <= 0) return pinta(tema, "gris", `${s.vacio.repeat(ancho)} 0/0`);
36874
+ const llenos = Math.max(0, Math.min(ancho, Math.round(hecho / total * ancho)));
36875
+ const completo = hecho >= total;
36876
+ const barra = `${s.lleno.repeat(llenos)}${s.vacio.repeat(ancho - llenos)}`;
36877
+ return `${pinta(tema, completo ? "verde" : "azul", barra)} ${hecho}/${total}`;
36878
+ }
36879
+ function marcaSeveridad(tema, severidad) {
36880
+ const s = simbolos(tema);
36881
+ if (severidad === "error") return pinta(tema, "rojo", s.error);
36882
+ if (severidad === "warning") return pinta(tema, "amarillo", s.aviso);
36883
+ return pinta(tema, "azul", s.info);
36884
+ }
36885
+ function formatearDiagnostico(tema, d, rutaRelativa) {
36886
+ const s = simbolos(tema);
36887
+ const ubicacion = rutaRelativa ? pinta(tema, "gris", `${rutaRelativa}${d.line ? `:${d.line}` : ""}`) : "";
36888
+ const codigo = pinta(tema, d.severity === "error" ? ["rojo", "negrita"] : d.severity === "warning" ? ["amarillo", "negrita"] : ["azul", "negrita"], d.code);
36889
+ const cabeza = [` ${marcaSeveridad(tema, d.severity)} ${codigo}`, ubicacion].filter((parte) => parte !== "").join(" ");
36890
+ const lineas = [cabeza, ` ${d.message}`];
36891
+ if (d.suggestion) lineas.push(pinta(tema, "gris", ` ${s.flecha} ${d.suggestion}`));
36892
+ return lineas;
36893
+ }
36894
+ function resumenHallazgos(tema, counts) {
36895
+ const s = simbolos(tema);
36896
+ if (counts.errors === 0 && counts.warnings === 0 && counts.infos === 0) return "";
36897
+ const partes = [];
36898
+ if (counts.errors > 0) partes.push(pinta(tema, "rojo", `${s.error} ${counts.errors} ${counts.errors === 1 ? "error" : "errores"}`));
36899
+ if (counts.warnings > 0) partes.push(pinta(tema, "amarillo", `${s.aviso} ${counts.warnings} ${counts.warnings === 1 ? "aviso" : "avisos"}`));
36900
+ if (counts.infos > 0) partes.push(pinta(tema, "azul", `${s.info} ${counts.infos} ${counts.infos === 1 ? "nota" : "notas"}`));
36901
+ return ` ${partes.join(pinta(tema, "gris", ` ${s.separador} `))}`;
36902
+ }
36903
+ function exito(tema, texto) {
36904
+ return ` ${pinta(tema, "verde", simbolos(tema).ok)} ${texto}`;
36905
+ }
36906
+ function siguienteAccion(tema, comando, descripcion) {
36907
+ const s = simbolos(tema);
36908
+ const lineas = [` ${pinta(tema, "cian", s.flecha)} ${pinta(tema, ["cian", "negrita"], comando)}`];
36909
+ if (descripcion) lineas.push(pinta(tema, "gris", ` ${descripcion}`));
36910
+ return lineas;
36911
+ }
36912
+ function filaAlineada(columnas, separacion = 2) {
36913
+ return columnas.map((columna, indice) => indice === columnas.length - 1 || columna.ancho === void 0 ? columna.texto : rellenar(columna.texto, columna.ancho)).join(" ".repeat(separacion));
36914
+ }
36915
+ function seccion(tema, titulo, detalle) {
36916
+ const cabeza = pinta(tema, ["negrita"], titulo);
36917
+ return detalle ? `${cabeza} ${pinta(tema, "gris", detalle)}` : cabeza;
36918
+ }
36919
+
36805
36920
  // src/version.ts
36806
36921
  import { readFileSync } from "fs";
36807
36922
  import { fileURLToPath } from "url";
@@ -37553,6 +37668,18 @@ async function runApprove(ctx) {
37553
37668
  lines.push(` fecha: ${result.approval.approvedAt}`);
37554
37669
  lines.push("");
37555
37670
  lines.push("La firma desbloquea el plan. Si el artefacto cambia, la firma queda obsoleta autom\xE1ticamente.");
37671
+ const slug = change?.slug;
37672
+ if (slug && !flagBool(ctx.flags, "dry-run")) {
37673
+ const presentation = path36.join(root, ".sdd", "changes", slug, "presentation", "index.html");
37674
+ if (await exists(presentation)) {
37675
+ try {
37676
+ await generatePresentation({ root, slug });
37677
+ lines.push(`Propuesta actualizada con la firma: ${path36.relative(ctx.cwd, presentation)}`);
37678
+ } catch (error2) {
37679
+ lines.push(`La propuesta no se pudo rehacer (${error2.message}); vuelve a generarla con \`satlas present ${slug}\`.`);
37680
+ }
37681
+ }
37682
+ }
37556
37683
  }
37557
37684
  const hasErrors2 = result.diagnostics.some((d) => d.severity === "error");
37558
37685
  return {
@@ -37635,7 +37762,7 @@ async function runCi(ctx) {
37635
37762
  }
37636
37763
  const gate = await runCiGate({ root, strict, extra });
37637
37764
  const diagnostics = [...gate.diagnostics];
37638
- const lines = ["CI de SpecAtlas", ""];
37765
+ const lines = encabezado(detectarTema(), "Comprobaci\xF3n continua", strict ? "modo estricto" : void 0);
37639
37766
  let sarifWritten;
37640
37767
  if (sarifPath) {
37641
37768
  const target = path38.resolve(ctx.cwd, sarifPath);
@@ -37651,13 +37778,18 @@ async function runCi(ctx) {
37651
37778
  });
37652
37779
  }
37653
37780
  }
37781
+ const tema = detectarTema();
37782
+ const s = simbolos(tema);
37654
37783
  for (const check of gate.checks) {
37655
- const mark = check.errors > 0 ? "FALLA" : check.warnings > 0 ? "AVISO" : "OK ";
37656
- lines.push(` ${mark} ${check.name}: ${check.errors} errores, ${check.warnings} avisos`);
37784
+ const marca = check.errors > 0 ? pinta(tema, "rojo", s.error) : check.warnings > 0 ? pinta(tema, "amarillo", s.aviso) : pinta(tema, "verde", s.ok);
37785
+ const detalle = check.errors === 0 && check.warnings === 0 ? pinta(tema, "gris", "sin hallazgos") : [check.errors > 0 ? pinta(tema, "rojo", `${check.errors} ${check.errors === 1 ? "error" : "errores"}`) : "", check.warnings > 0 ? pinta(tema, "amarillo", `${check.warnings} ${check.warnings === 1 ? "aviso" : "avisos"}`) : ""].filter((parte) => parte !== "").join(pinta(tema, "gris", ` ${s.separador} `));
37786
+ lines.push(filaAlineada([{ texto: ` ${marca} ${check.name}`, ancho: 46 }, { texto: detalle }]));
37657
37787
  }
37658
37788
  lines.push("");
37659
- lines.push(gate.failed ? "Resultado: BLOQUEADO" : "Resultado: OK");
37660
- if (strict) lines.push("(modo estricto: los avisos tambi\xE9n bloquean)");
37789
+ lines.push(
37790
+ gate.failed ? ` ${pinta(tema, ["rojo", "negrita"], `${s.error} BLOQUEADO`)}${pinta(tema, "gris", " \u2014 resuelve los hallazgos antes de continuar")}` : ` ${pinta(tema, ["verde", "negrita"], `${s.ok} TODO EN ORDEN`)}`
37791
+ );
37792
+ if (strict) lines.push(pinta(tema, "gris", " (modo estricto: los avisos tambi\xE9n bloquean)"));
37661
37793
  if (sarifPath) {
37662
37794
  lines.push(sarifWritten ? `Informe SARIF: ${sarifWritten} (${gate.diagnostics.length} hallazgo(s))` : `Informe SARIF: no se pudo escribir en "${sarifPath}"`);
37663
37795
  }
@@ -38042,7 +38174,7 @@ assumptions: []
38042
38174
  }
38043
38175
 
38044
38176
  // src/commands/doctor.ts
38045
- import path44 from "path";
38177
+ import "path";
38046
38178
  async function runDoctorCommand(ctx) {
38047
38179
  const { root, config } = await requireWorkspace(ctx);
38048
38180
  const report = await runDoctor(root);
@@ -38062,17 +38194,10 @@ async function runDoctorCommand(ctx) {
38062
38194
  const advisory = await upgradeAdvisory(root);
38063
38195
  report.findings.push(...advisory.diagnostics);
38064
38196
  report.summary = countBySeverity(report.findings);
38065
- const lines = [msg("doctor.title", ctx.language), ""];
38197
+ const tema = detectarTema();
38198
+ const lines = encabezado(tema, msg("doctor.title", ctx.language));
38066
38199
  if (report.findings.length === 0) {
38067
- lines.push("Sin hallazgos. El workspace est\xE1 sano.");
38068
- } else {
38069
- for (const finding of report.findings) {
38070
- const location = finding.path ? ` ${path44.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
38071
- lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${location} \u2014 ${finding.message}`);
38072
- if (finding.suggestion) lines.push(` \u21B3 ${finding.suggestion}`);
38073
- }
38074
- lines.push("");
38075
- lines.push(`${report.summary.errors} errores, ${report.summary.warnings} avisos, ${report.summary.infos} notas`);
38200
+ lines.push(exito(tema, "El workspace est\xE1 sano."));
38076
38201
  }
38077
38202
  return {
38078
38203
  exitCode: report.summary.errors > 0 ? 1 : 0,
@@ -39237,21 +39362,28 @@ async function runNext(ctx) {
39237
39362
  text: [`\u25B6 ${action.command}`, "", ...outcome.result.text ?? []]
39238
39363
  };
39239
39364
  }
39240
- const lines = [msg("next.title", ctx.language), ""];
39365
+ const tema = detectarTema();
39366
+ const s = simbolos(tema);
39367
+ const lines = encabezado(tema, msg("next.title", ctx.language));
39241
39368
  const data = [];
39242
39369
  let hasErrors2 = false;
39243
39370
  for (const change of targets) {
39244
39371
  const evaluation = await evaluateChange(workspace, config, change, approvals);
39245
39372
  if (evaluation.blocking > 0) hasErrors2 = true;
39246
39373
  const { state, nextAction, blockedBy } = evaluation.state;
39247
- lines.push(` ${change.slug} \u2014 ${stateLabel(state)}`);
39248
- lines.push(` ${msg("status.next", ctx.language)} ${nextAction.command}`);
39249
- lines.push(` ${nextAction.description}${nextAction.requiresAgent ? " (requiere agente)" : ""}`);
39250
- for (const block of blockedBy) lines.push(` bloqueado por: ${block}`);
39374
+ const actor = nextAction.requiresAgent ? pinta(tema, "azul", "con agente") : /^satlas (approve|archive|amend|pause|resume)/.test(nextAction.command) ? pinta(tema, "morado", "humana") : pinta(tema, "verde", "local");
39375
+ lines.push(
39376
+ filaAlineada([
39377
+ { texto: ` ${pinta(tema, "negrita", change.slug)}`, ancho: 30 },
39378
+ { texto: pinta(tema, "gris", stateLabel(state)) }
39379
+ ])
39380
+ );
39381
+ lines.push(...siguienteAccion(tema, nextAction.command, `${nextAction.description} ${s.separador} ${actor}`));
39382
+ for (const block of blockedBy) lines.push(` ${pinta(tema, "amarillo", s.bloqueo)} ${pinta(tema, "gris", block)}`);
39251
39383
  lines.push("");
39252
39384
  data.push({ slug: change.slug, state, next: nextAction.command, blockedBy, requiresAgent: nextAction.requiresAgent });
39253
39385
  }
39254
- if (targets.length === 0) lines.push("Sin cambios activos.");
39386
+ if (targets.length === 0) lines.push(pinta(tema, "gris", " Sin cambios activos."));
39255
39387
  return { exitCode: hasErrors2 ? 1 : 0, diagnostics: [], data: { changes: data }, text: lines };
39256
39388
  }
39257
39389
 
@@ -39538,20 +39670,52 @@ async function avisoDeActualizacion(json2, cwd) {
39538
39670
  }
39539
39671
 
39540
39672
  // src/commands/status.ts
39673
+ var TONO_ESTADO = {
39674
+ draft: "gris",
39675
+ spec_draft: "amarillo",
39676
+ paused: "amarillo",
39677
+ awaiting_mockups: "morado",
39678
+ awaiting_approval: "morado",
39679
+ approved: "azul",
39680
+ planned: "azul",
39681
+ building: "azul",
39682
+ built: "amarillo",
39683
+ verified: "verde",
39684
+ reviewed: "verde",
39685
+ ready: "verde",
39686
+ archived: "gris"
39687
+ };
39688
+ function marcaDeEstado(tema, estado, bloqueado) {
39689
+ const s = simbolos(tema);
39690
+ if (bloqueado) return pinta(tema, "rojo", s.bloqueo);
39691
+ if (estado === "paused") return pinta(tema, "amarillo", s.pausa);
39692
+ if (estado === "ready") return pinta(tema, "verde", s.ok);
39693
+ return pinta(tema, TONO_ESTADO[estado] ?? "azul", s.punto);
39694
+ }
39541
39695
  async function runStatus(ctx) {
39542
39696
  const { root, workspace, config, approvals } = await requireWorkspace(ctx);
39543
39697
  const advisory = await upgradeAdvisory(root);
39544
- const lines = [msg("status.title", ctx.language), ""];
39698
+ const tema = detectarTema();
39699
+ const s = simbolos(tema);
39700
+ const fixes = await loadLivingFixes(root);
39701
+ const contexto = [
39702
+ `${workspace.specs.length} ${workspace.specs.length === 1 ? "spec viva" : "specs vivas"}`,
39703
+ `${workspace.changes.length} ${workspace.changes.length === 1 ? "cambio" : "cambios"}`,
39704
+ fixes.length > 0 ? `${fixes.length} ${fixes.length === 1 ? "fix" : "fixes"}` : ""
39705
+ ].filter((parte) => parte !== "").join(` ${s.separador} `);
39706
+ const lines = encabezado(tema, msg("status.title", ctx.language), `${config.project.name} ${s.separador} ${contexto}`);
39545
39707
  const changes = [];
39546
39708
  if (workspace.changes.length === 0) {
39547
- lines.push("Sin cambios activos. Crea uno con `satlas new <slug>`.");
39709
+ lines.push(pinta(tema, "gris", ` Sin cambios activos. Crea uno con \`satlas new <slug>\`.`));
39548
39710
  }
39549
39711
  for (const change of workspace.changes) {
39550
39712
  const evaluation = await evaluateChange(workspace, config, change, approvals);
39551
- const { state, progress, nextAction } = evaluation.state;
39713
+ const { state, progress, nextAction, blockedBy } = evaluation.state;
39714
+ const lane = change.meta?.lane ?? config.lanes.default;
39715
+ const bloqueado = blockedBy.length > 0 || evaluation.blocking > 0;
39552
39716
  changes.push({
39553
39717
  slug: change.slug,
39554
- lane: change.meta?.lane ?? config.lanes.default,
39718
+ lane,
39555
39719
  domain: change.meta?.domain,
39556
39720
  state,
39557
39721
  label: stateLabel(state),
@@ -39560,27 +39724,48 @@ async function runStatus(ctx) {
39560
39724
  blocking: evaluation.blocking
39561
39725
  });
39562
39726
  lines.push(
39563
- ` ${change.slug} ${(change.meta?.domain ?? "-").padEnd(10)} ${(change.meta?.lane ?? config.lanes.default).padEnd(9)} ${stateLabel(state)}`
39564
- );
39565
- lines.push(
39566
- ` ${msg("status.tasks", ctx.language)} ${progress.tasksDone}/${progress.tasksTotal} \xB7 ${msg("status.evidence", ctx.language)} ${progress.scenariosEvidenced}/${progress.scenariosTotal}`
39727
+ filaAlineada([
39728
+ { texto: ` ${marcaDeEstado(tema, state, bloqueado)} ${pinta(tema, "negrita", change.slug)}`, ancho: 30 },
39729
+ { texto: pinta(tema, TONO_ESTADO[state] ?? "azul", stateLabel(state)), ancho: 22 },
39730
+ { texto: pinta(tema, "gris", `${change.meta?.domain ?? "\u2014"} ${s.separador} ${lane}`) }
39731
+ ])
39567
39732
  );
39568
- lines.push(` ${msg("status.next", ctx.language)} ${nextAction.command} \u2014 ${nextAction.description}`);
39733
+ const medidas = [
39734
+ `${pinta(tema, "gris", "tareas")} ${barraProgreso(tema, progress.tasksDone, progress.tasksTotal)}`,
39735
+ `${pinta(tema, "gris", "evidencia")} ${barraProgreso(tema, progress.scenariosEvidenced, progress.scenariosTotal)}`
39736
+ ];
39737
+ lines.push(` ${medidas.join(pinta(tema, "gris", ` ${s.separador} `))}`);
39738
+ for (const bloqueo of blockedBy) lines.push(` ${pinta(tema, "amarillo", s.bloqueo)} ${pinta(tema, "gris", bloqueo)}`);
39739
+ lines.push(...siguienteAccion(tema, nextAction.command, nextAction.description));
39740
+ lines.push("");
39569
39741
  }
39570
- lines.push("");
39571
- lines.push(`${msg("status.specs", ctx.language)}: ${workspace.specs.length}`);
39572
- for (const spec of workspace.specs) {
39573
- lines.push(` ${spec.domain} \u2014 ${spec.spec.requirements.length} requisitos`);
39742
+ if (workspace.specs.length > 0) {
39743
+ lines.push(seccion(tema, msg("status.specs", ctx.language), `${workspace.specs.length}`));
39744
+ const ancho = Math.max(...workspace.specs.map((spec) => spec.domain.length), 8);
39745
+ for (const spec of workspace.specs) {
39746
+ lines.push(
39747
+ filaAlineada([
39748
+ { texto: ` ${pinta(tema, "morado", s.punto)} ${spec.domain}`, ancho: ancho + 6 },
39749
+ { texto: pinta(tema, "gris", `${spec.spec.requirements.length} ${spec.spec.requirements.length === 1 ? "requisito" : "requisitos"}`) }
39750
+ ])
39751
+ );
39752
+ }
39753
+ lines.push("");
39574
39754
  }
39575
- const fixes = await loadLivingFixes(root);
39576
- lines.push("");
39577
- lines.push(`${msg("status.fixes", ctx.language)}: ${fixes.length}`);
39755
+ lines.push(seccion(tema, msg("status.fixes", ctx.language), `${fixes.length}`));
39578
39756
  if (fixes.length === 0) {
39579
- lines.push(" (se llenan al archivar un fix)");
39757
+ lines.push(pinta(tema, "gris", " (se llenan al archivar un fix)"));
39580
39758
  } else {
39581
39759
  for (const fix of fixes) {
39582
- const origin = fix.source === "archive" ? " (hist\xF3rico)" : "";
39583
- lines.push(` ${fix.date || "\u2014"} ${(fix.domain ?? "\u2014").padEnd(10)} ${fix.slug} \u2014 ${fix.result}${origin}`);
39760
+ const resultado = fix.result === "pass" ? pinta(tema, "verde", fix.result) : pinta(tema, "rojo", fix.result);
39761
+ lines.push(
39762
+ filaAlineada([
39763
+ { texto: ` ${pinta(tema, "gris", fix.date || "\u2014")}`, ancho: 14 },
39764
+ { texto: pinta(tema, "gris", fix.domain ?? "\u2014"), ancho: 14 },
39765
+ { texto: fix.slug, ancho: 28 },
39766
+ { texto: `${resultado}${fix.source === "archive" ? pinta(tema, "gris", " (hist\xF3rico)") : ""}` }
39767
+ ])
39768
+ );
39584
39769
  }
39585
39770
  }
39586
39771
  const diagnostics = [...workspace.diagnostics, ...advisory.diagnostics];
@@ -39590,7 +39775,7 @@ async function runStatus(ctx) {
39590
39775
  diagnostics,
39591
39776
  data: {
39592
39777
  changes,
39593
- specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length })),
39778
+ specs: workspace.specs.map((spec) => ({ domain: spec.domain, requirements: spec.spec.requirements.length })),
39594
39779
  fixes: fixes.map((fix) => ({
39595
39780
  slug: fix.slug,
39596
39781
  date: fix.date,
@@ -39764,8 +39949,10 @@ async function runValidate(ctx) {
39764
39949
  const slug = flagString(ctx.flags, "change");
39765
39950
  const strict = flagBool(ctx.flags, "strict");
39766
39951
  const advisory = slug ? void 0 : await upgradeAdvisory(root);
39952
+ const tema = detectarTema();
39953
+ const s = simbolos(tema);
39767
39954
  const diagnostics = [];
39768
- const lines = [msg("validate.title", ctx.language), ""];
39955
+ const lines = encabezado(tema, msg("validate.title", ctx.language), slug ? slug : void 0);
39769
39956
  for (const spec of workspace.specs) {
39770
39957
  diagnostics.push(...lintSpec(spec.spec, { language: config.spec.language, businessOnly: config.spec.business_only }));
39771
39958
  }
@@ -39781,11 +39968,15 @@ async function runValidate(ctx) {
39781
39968
  const evaluation = await evaluateChange(workspace, config, change, approvals);
39782
39969
  diagnostics.push(...evaluation.lintFindings);
39783
39970
  changes.push({ slug: change.slug, blocking: evaluation.blocking, findings: evaluation.lintFindings.length });
39784
- lines.push(` ${change.slug}: ${evaluation.lintFindings.length} ${msg("summary.findings", ctx.language)}`);
39785
- for (const finding of evaluation.lintFindings) {
39786
- lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${finding.line ? `:${finding.line}` : ""} \u2014 ${finding.message}`);
39787
- }
39788
- lines.push(` archivo: ${path59.relative(ctx.cwd, path59.join(change.dir, "spec.md"))}`);
39971
+ const errores = evaluation.lintFindings.filter((finding) => finding.severity === "error").length;
39972
+ const marca = errores > 0 ? pinta(tema, "rojo", s.error) : evaluation.lintFindings.length > 0 ? pinta(tema, "amarillo", s.aviso) : pinta(tema, "verde", s.ok);
39973
+ lines.push(
39974
+ filaAlineada([
39975
+ { texto: ` ${marca} ${pinta(tema, "negrita", change.slug)}`, ancho: 30 },
39976
+ { texto: evaluation.lintFindings.length === 0 ? pinta(tema, "gris", "sin hallazgos") : `${evaluation.lintFindings.length} ${msg("summary.findings", ctx.language)}`, ancho: 20 },
39977
+ { texto: pinta(tema, "gris", path59.relative(ctx.cwd, path59.join(change.dir, "spec.md"))) }
39978
+ ])
39979
+ );
39789
39980
  }
39790
39981
  if (targets.length === 0) lines.push("Sin cambios activos.");
39791
39982
  if (advisory && advisory.plan.pending.length > 0) {
@@ -39793,7 +39984,10 @@ async function runValidate(ctx) {
39793
39984
  }
39794
39985
  const errors = diagnostics.filter((d) => d.severity === "error").length;
39795
39986
  const warnings = diagnostics.filter((d) => d.severity === "warning").length;
39796
- if (errors === 0 && warnings === 0) lines.push(msg("validate.ok", ctx.language));
39987
+ if (errors === 0 && warnings === 0) {
39988
+ lines.push("");
39989
+ lines.push(exito(tema, msg("validate.ok", ctx.language)));
39990
+ }
39797
39991
  return {
39798
39992
  exitCode: errors > 0 || strict && warnings > 0 ? 1 : 0,
39799
39993
  diagnostics: advisory ? [...diagnostics, ...advisory.diagnostics] : diagnostics,
@@ -40162,15 +40356,28 @@ function printResult(command, result, json2, language) {
40162
40356
  `);
40163
40357
  return;
40164
40358
  }
40359
+ const tema = detectarTema();
40165
40360
  const lines = [...result.text ?? []];
40361
+ if (result.diagnostics.length > 0) lines.push("");
40166
40362
  for (const d of result.diagnostics) {
40167
- const location = d.path ? ` ${path62.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
40168
- lines.push(`${d.severity === "error" ? "ERROR" : d.severity === "warning" ? "AVISO" : "NOTA"} ${d.code}${location} \u2014 ${d.message}`);
40169
- if (d.suggestion) lines.push(` \u21B3 ${d.suggestion}`);
40363
+ const ruta = d.path ? path62.relative(process.cwd(), d.path) : void 0;
40364
+ lines.push(
40365
+ ...formatearDiagnostico(
40366
+ tema,
40367
+ {
40368
+ code: d.code,
40369
+ severity: d.severity,
40370
+ message: d.message,
40371
+ ...d.line !== void 0 ? { line: d.line } : {},
40372
+ ...d.suggestion !== void 0 ? { suggestion: d.suggestion } : {}
40373
+ },
40374
+ ruta
40375
+ )
40376
+ );
40170
40377
  }
40171
- const counts = countBySeverity(result.diagnostics);
40172
40378
  if (result.diagnostics.length > 0) {
40173
- lines.push(`${counts.errors} ${msg("label.errors", language)}, ${counts.warnings} ${msg("label.warnings", language)}, ${counts.infos} ${msg("label.infos", language)}`);
40379
+ lines.push("");
40380
+ lines.push(resumenHallazgos(tema, countBySeverity(result.diagnostics)));
40174
40381
  }
40175
40382
  if (lines.length > 0) process.stdout.write(`${lines.join("\n")}
40176
40383
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specatlas",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "description": "SpecAtlas: kernel determinista de Spec-Driven Development (CLI)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@specatlas/adapters": "0.1.2",
43
- "@specatlas/core": "0.1.31"
43
+ "@specatlas/core": "0.1.32"
44
44
  },
45
45
  "scripts": {
46
46
  "typecheck": "tsc --noEmit",