specatlas 0.1.38 → 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 +231 -54
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -36820,6 +36820,103 @@ function msg(key, language) {
36820
36820
  return dict[key] ?? key;
36821
36821
  }
36822
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
+
36823
36920
  // src/version.ts
36824
36921
  import { readFileSync } from "fs";
36825
36922
  import { fileURLToPath } from "url";
@@ -37665,7 +37762,7 @@ async function runCi(ctx) {
37665
37762
  }
37666
37763
  const gate = await runCiGate({ root, strict, extra });
37667
37764
  const diagnostics = [...gate.diagnostics];
37668
- const lines = ["CI de SpecAtlas", ""];
37765
+ const lines = encabezado(detectarTema(), "Comprobaci\xF3n continua", strict ? "modo estricto" : void 0);
37669
37766
  let sarifWritten;
37670
37767
  if (sarifPath) {
37671
37768
  const target = path38.resolve(ctx.cwd, sarifPath);
@@ -37681,13 +37778,18 @@ async function runCi(ctx) {
37681
37778
  });
37682
37779
  }
37683
37780
  }
37781
+ const tema = detectarTema();
37782
+ const s = simbolos(tema);
37684
37783
  for (const check of gate.checks) {
37685
- const mark = check.errors > 0 ? "FALLA" : check.warnings > 0 ? "AVISO" : "OK ";
37686
- 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 }]));
37687
37787
  }
37688
37788
  lines.push("");
37689
- lines.push(gate.failed ? "Resultado: BLOQUEADO" : "Resultado: OK");
37690
- 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)"));
37691
37793
  if (sarifPath) {
37692
37794
  lines.push(sarifWritten ? `Informe SARIF: ${sarifWritten} (${gate.diagnostics.length} hallazgo(s))` : `Informe SARIF: no se pudo escribir en "${sarifPath}"`);
37693
37795
  }
@@ -38072,7 +38174,7 @@ assumptions: []
38072
38174
  }
38073
38175
 
38074
38176
  // src/commands/doctor.ts
38075
- import path44 from "path";
38177
+ import "path";
38076
38178
  async function runDoctorCommand(ctx) {
38077
38179
  const { root, config } = await requireWorkspace(ctx);
38078
38180
  const report = await runDoctor(root);
@@ -38092,17 +38194,10 @@ async function runDoctorCommand(ctx) {
38092
38194
  const advisory = await upgradeAdvisory(root);
38093
38195
  report.findings.push(...advisory.diagnostics);
38094
38196
  report.summary = countBySeverity(report.findings);
38095
- const lines = [msg("doctor.title", ctx.language), ""];
38197
+ const tema = detectarTema();
38198
+ const lines = encabezado(tema, msg("doctor.title", ctx.language));
38096
38199
  if (report.findings.length === 0) {
38097
- lines.push("Sin hallazgos. El workspace est\xE1 sano.");
38098
- } else {
38099
- for (const finding of report.findings) {
38100
- const location = finding.path ? ` ${path44.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
38101
- lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${location} \u2014 ${finding.message}`);
38102
- if (finding.suggestion) lines.push(` \u21B3 ${finding.suggestion}`);
38103
- }
38104
- lines.push("");
38105
- lines.push(`${report.summary.errors} errores, ${report.summary.warnings} avisos, ${report.summary.infos} notas`);
38200
+ lines.push(exito(tema, "El workspace est\xE1 sano."));
38106
38201
  }
38107
38202
  return {
38108
38203
  exitCode: report.summary.errors > 0 ? 1 : 0,
@@ -39267,21 +39362,28 @@ async function runNext(ctx) {
39267
39362
  text: [`\u25B6 ${action.command}`, "", ...outcome.result.text ?? []]
39268
39363
  };
39269
39364
  }
39270
- 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));
39271
39368
  const data = [];
39272
39369
  let hasErrors2 = false;
39273
39370
  for (const change of targets) {
39274
39371
  const evaluation = await evaluateChange(workspace, config, change, approvals);
39275
39372
  if (evaluation.blocking > 0) hasErrors2 = true;
39276
39373
  const { state, nextAction, blockedBy } = evaluation.state;
39277
- lines.push(` ${change.slug} \u2014 ${stateLabel(state)}`);
39278
- lines.push(` ${msg("status.next", ctx.language)} ${nextAction.command}`);
39279
- lines.push(` ${nextAction.description}${nextAction.requiresAgent ? " (requiere agente)" : ""}`);
39280
- 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)}`);
39281
39383
  lines.push("");
39282
39384
  data.push({ slug: change.slug, state, next: nextAction.command, blockedBy, requiresAgent: nextAction.requiresAgent });
39283
39385
  }
39284
- if (targets.length === 0) lines.push("Sin cambios activos.");
39386
+ if (targets.length === 0) lines.push(pinta(tema, "gris", " Sin cambios activos."));
39285
39387
  return { exitCode: hasErrors2 ? 1 : 0, diagnostics: [], data: { changes: data }, text: lines };
39286
39388
  }
39287
39389
 
@@ -39568,20 +39670,52 @@ async function avisoDeActualizacion(json2, cwd) {
39568
39670
  }
39569
39671
 
39570
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
+ }
39571
39695
  async function runStatus(ctx) {
39572
39696
  const { root, workspace, config, approvals } = await requireWorkspace(ctx);
39573
39697
  const advisory = await upgradeAdvisory(root);
39574
- 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}`);
39575
39707
  const changes = [];
39576
39708
  if (workspace.changes.length === 0) {
39577
- 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>\`.`));
39578
39710
  }
39579
39711
  for (const change of workspace.changes) {
39580
39712
  const evaluation = await evaluateChange(workspace, config, change, approvals);
39581
- 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;
39582
39716
  changes.push({
39583
39717
  slug: change.slug,
39584
- lane: change.meta?.lane ?? config.lanes.default,
39718
+ lane,
39585
39719
  domain: change.meta?.domain,
39586
39720
  state,
39587
39721
  label: stateLabel(state),
@@ -39590,27 +39724,48 @@ async function runStatus(ctx) {
39590
39724
  blocking: evaluation.blocking
39591
39725
  });
39592
39726
  lines.push(
39593
- ` ${change.slug} ${(change.meta?.domain ?? "-").padEnd(10)} ${(change.meta?.lane ?? config.lanes.default).padEnd(9)} ${stateLabel(state)}`
39594
- );
39595
- lines.push(
39596
- ` ${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
+ ])
39597
39732
  );
39598
- 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("");
39599
39741
  }
39600
- lines.push("");
39601
- lines.push(`${msg("status.specs", ctx.language)}: ${workspace.specs.length}`);
39602
- for (const spec of workspace.specs) {
39603
- 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("");
39604
39754
  }
39605
- const fixes = await loadLivingFixes(root);
39606
- lines.push("");
39607
- lines.push(`${msg("status.fixes", ctx.language)}: ${fixes.length}`);
39755
+ lines.push(seccion(tema, msg("status.fixes", ctx.language), `${fixes.length}`));
39608
39756
  if (fixes.length === 0) {
39609
- lines.push(" (se llenan al archivar un fix)");
39757
+ lines.push(pinta(tema, "gris", " (se llenan al archivar un fix)"));
39610
39758
  } else {
39611
39759
  for (const fix of fixes) {
39612
- const origin = fix.source === "archive" ? " (hist\xF3rico)" : "";
39613
- 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
+ );
39614
39769
  }
39615
39770
  }
39616
39771
  const diagnostics = [...workspace.diagnostics, ...advisory.diagnostics];
@@ -39620,7 +39775,7 @@ async function runStatus(ctx) {
39620
39775
  diagnostics,
39621
39776
  data: {
39622
39777
  changes,
39623
- 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 })),
39624
39779
  fixes: fixes.map((fix) => ({
39625
39780
  slug: fix.slug,
39626
39781
  date: fix.date,
@@ -39794,8 +39949,10 @@ async function runValidate(ctx) {
39794
39949
  const slug = flagString(ctx.flags, "change");
39795
39950
  const strict = flagBool(ctx.flags, "strict");
39796
39951
  const advisory = slug ? void 0 : await upgradeAdvisory(root);
39952
+ const tema = detectarTema();
39953
+ const s = simbolos(tema);
39797
39954
  const diagnostics = [];
39798
- const lines = [msg("validate.title", ctx.language), ""];
39955
+ const lines = encabezado(tema, msg("validate.title", ctx.language), slug ? slug : void 0);
39799
39956
  for (const spec of workspace.specs) {
39800
39957
  diagnostics.push(...lintSpec(spec.spec, { language: config.spec.language, businessOnly: config.spec.business_only }));
39801
39958
  }
@@ -39811,11 +39968,15 @@ async function runValidate(ctx) {
39811
39968
  const evaluation = await evaluateChange(workspace, config, change, approvals);
39812
39969
  diagnostics.push(...evaluation.lintFindings);
39813
39970
  changes.push({ slug: change.slug, blocking: evaluation.blocking, findings: evaluation.lintFindings.length });
39814
- lines.push(` ${change.slug}: ${evaluation.lintFindings.length} ${msg("summary.findings", ctx.language)}`);
39815
- for (const finding of evaluation.lintFindings) {
39816
- lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${finding.line ? `:${finding.line}` : ""} \u2014 ${finding.message}`);
39817
- }
39818
- 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
+ );
39819
39980
  }
39820
39981
  if (targets.length === 0) lines.push("Sin cambios activos.");
39821
39982
  if (advisory && advisory.plan.pending.length > 0) {
@@ -39823,7 +39984,10 @@ async function runValidate(ctx) {
39823
39984
  }
39824
39985
  const errors = diagnostics.filter((d) => d.severity === "error").length;
39825
39986
  const warnings = diagnostics.filter((d) => d.severity === "warning").length;
39826
- 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
+ }
39827
39991
  return {
39828
39992
  exitCode: errors > 0 || strict && warnings > 0 ? 1 : 0,
39829
39993
  diagnostics: advisory ? [...diagnostics, ...advisory.diagnostics] : diagnostics,
@@ -40192,15 +40356,28 @@ function printResult(command, result, json2, language) {
40192
40356
  `);
40193
40357
  return;
40194
40358
  }
40359
+ const tema = detectarTema();
40195
40360
  const lines = [...result.text ?? []];
40361
+ if (result.diagnostics.length > 0) lines.push("");
40196
40362
  for (const d of result.diagnostics) {
40197
- const location = d.path ? ` ${path62.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
40198
- lines.push(`${d.severity === "error" ? "ERROR" : d.severity === "warning" ? "AVISO" : "NOTA"} ${d.code}${location} \u2014 ${d.message}`);
40199
- 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
+ );
40200
40377
  }
40201
- const counts = countBySeverity(result.diagnostics);
40202
40378
  if (result.diagnostics.length > 0) {
40203
- 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)));
40204
40381
  }
40205
40382
  if (lines.length > 0) process.stdout.write(`${lines.join("\n")}
40206
40383
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specatlas",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "description": "SpecAtlas: kernel determinista de Spec-Driven Development (CLI)",
5
5
  "license": "MIT",
6
6
  "type": "module",