specatlas 0.1.36 → 0.1.37

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 +416 -155
  2. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -5810,7 +5810,7 @@ var require_pako = __commonJS({
5810
5810
  });
5811
5811
 
5812
5812
  // src/cli.ts
5813
- import path61 from "path";
5813
+ import path62 from "path";
5814
5814
 
5815
5815
  // ../core/dist/index.js
5816
5816
  import path from "path";
@@ -23473,14 +23473,14 @@ var parameters = /* @__PURE__ */ new Map([
23473
23473
  ["Z", 0],
23474
23474
  ["z", 0]
23475
23475
  ]);
23476
- var parse = function(path62) {
23476
+ var parse = function(path63) {
23477
23477
  var cmd;
23478
23478
  var ret = [];
23479
23479
  var args = [];
23480
23480
  var curArg = "";
23481
23481
  var foundDecimal = false;
23482
23482
  var params = 0;
23483
- for (var _i = 0, path_1 = path62; _i < path_1.length; _i++) {
23483
+ for (var _i = 0, path_1 = path63; _i < path_1.length; _i++) {
23484
23484
  var c = path_1[_i];
23485
23485
  if (parameters.has(c)) {
23486
23486
  params = parameters.get(c);
@@ -23794,8 +23794,8 @@ var segmentToBezier = function(cx1, cy1, th0, th1, rx, ry, sinTh, cosTh) {
23794
23794
  ];
23795
23795
  return result;
23796
23796
  };
23797
- var svgPathToOperators = function(path62) {
23798
- return apply(parse(path62));
23797
+ var svgPathToOperators = function(path63) {
23798
+ return apply(parse(path63));
23799
23799
  };
23800
23800
 
23801
23801
  // ../../node_modules/.pnpm/pdf-lib@1.17.1/node_modules/pdf-lib/es/api/operations.js
@@ -23952,7 +23952,7 @@ var drawEllipse = function(options) {
23952
23952
  popGraphicsState()
23953
23953
  ]).filter(Boolean);
23954
23954
  };
23955
- var drawSvgPath = function(path62, options) {
23955
+ var drawSvgPath = function(path63, options) {
23956
23956
  var _a, _b, _c;
23957
23957
  return __spreadArrays([
23958
23958
  pushGraphicsState(),
@@ -23966,7 +23966,7 @@ var drawSvgPath = function(path62, options) {
23966
23966
  options.borderWidth && setLineWidth(options.borderWidth),
23967
23967
  options.borderLineCap && setLineCap(options.borderLineCap),
23968
23968
  setDashPattern((_b = options.borderDashArray) !== null && _b !== void 0 ? _b : [], (_c = options.borderDashPhase) !== null && _c !== void 0 ? _c : 0)
23969
- ], svgPathToOperators(path62), [
23969
+ ], svgPathToOperators(path63), [
23970
23970
  // prettier-ignore
23971
23971
  options.color && options.borderWidth ? fillAndStroke() : options.color ? fill() : options.borderColor ? stroke() : closePath(),
23972
23972
  popGraphicsState()
@@ -27948,12 +27948,12 @@ var PDFPage = (
27948
27948
  graphicsState: graphicsStateKey
27949
27949
  }));
27950
27950
  };
27951
- PDFPage2.prototype.drawSvgPath = function(path62, options) {
27951
+ PDFPage2.prototype.drawSvgPath = function(path63, options) {
27952
27952
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
27953
27953
  if (options === void 0) {
27954
27954
  options = {};
27955
27955
  }
27956
- assertIs(path62, "path", ["string"]);
27956
+ assertIs(path63, "path", ["string"]);
27957
27957
  assertOrUndefined(options.x, "options.x", ["number"]);
27958
27958
  assertOrUndefined(options.y, "options.y", ["number"]);
27959
27959
  assertOrUndefined(options.scale, "options.scale", ["number"]);
@@ -27982,7 +27982,7 @@ var PDFPage = (
27982
27982
  options.borderColor = rgb(0, 0, 0);
27983
27983
  }
27984
27984
  var contentStream = this.getContentStream();
27985
- contentStream.push.apply(contentStream, drawSvgPath(path62, {
27985
+ contentStream.push.apply(contentStream, drawSvgPath(path63, {
27986
27986
  x: (_a = options.x) !== null && _a !== void 0 ? _a : this.x,
27987
27987
  y: (_b = options.y) !== null && _b !== void 0 ? _b : this.y,
27988
27988
  scale: options.scale,
@@ -29375,10 +29375,12 @@ import path23 from "path";
29375
29375
  import { promises as fs2 } from "fs";
29376
29376
  import path24 from "path";
29377
29377
  import { parse as parseYaml11, stringify as stringifyYaml8 } from "yaml";
29378
+ import os from "os";
29378
29379
  import path25 from "path";
29379
29380
  import path26 from "path";
29380
29381
  import path27 from "path";
29381
29382
  import path28 from "path";
29383
+ import path29 from "path";
29382
29384
  var CORE_VERSION = "0.0.1";
29383
29385
  var DIAGNOSTIC_FAMILIES = [
29384
29386
  { prefix: "LINT-STR", title: "Estructura", description: "La forma del documento: ids, encabezados y bloques donde corresponde." },
@@ -30710,13 +30712,13 @@ var FIELD_LABEL = {
30710
30712
  when: "un CUANDO",
30711
30713
  then: "un ENTONCES"
30712
30714
  };
30713
- function lintPlaceholders(req, path292) {
30715
+ function lintPlaceholders(req, path302) {
30714
30716
  const hits = requirementPlaceholders(req);
30715
30717
  if (hits.length === 0) return [];
30716
30718
  if (isSkeletonRequirement(req)) {
30717
30719
  return [
30718
30720
  diag("LINT-BIZ-003", "error", `El requisito ${req.id} sigue siendo la plantilla de \`satlas new\`: no especifica nada todav\xEDa`, {
30719
- path: path292,
30721
+ path: path302,
30720
30722
  line: req.line,
30721
30723
  suggestion: "Escribe la necesidad de negocio, sus reglas y el CUANDO/ENTONCES de cada escenario"
30722
30724
  })
@@ -30725,7 +30727,7 @@ function lintPlaceholders(req, path292) {
30725
30727
  return hits.map((hit) => {
30726
30728
  const where = hit.scenario ? `${hit.scenario}` : req.id;
30727
30729
  return diag("LINT-BIZ-003", "error", `Texto de plantilla sin completar en ${where}: ${FIELD_LABEL[hit.field]} sigue siendo el hueco de \`satlas new\``, {
30728
- path: path292,
30730
+ path: path302,
30729
30731
  line: hit.line,
30730
30732
  suggestion: "Escribe el comportamiento real de negocio en lugar del texto entre par\xE9ntesis de la plantilla"
30731
30733
  });
@@ -30755,14 +30757,14 @@ function wordBoundary(text, term) {
30755
30757
  const re = new RegExp(`(?<![\\p{L}\\p{N}])${escaped}(?![\\p{L}\\p{N}])`, "iu");
30756
30758
  return re.test(text);
30757
30759
  }
30758
- function lintText(text, code, terms, label, path292, line) {
30760
+ function lintText(text, code, terms, label, path302, line) {
30759
30761
  const out = [];
30760
30762
  const lower = text.toLowerCase();
30761
30763
  for (const term of terms) {
30762
30764
  if (wordBoundary(lower, term)) {
30763
30765
  out.push(
30764
30766
  diag(code, "error", `${label}: "${term}"`, {
30765
- path: path292,
30767
+ path: path302,
30766
30768
  line,
30767
30769
  suggestion: "La especificaci\xF3n es funcional y de negocio: describe comportamiento, no tecnolog\xEDa ni adjetivos vagos"
30768
30770
  })
@@ -30771,7 +30773,7 @@ function lintText(text, code, terms, label, path292, line) {
30771
30773
  }
30772
30774
  return out;
30773
30775
  }
30774
- function lintRequirement(req, path292, opts = {}) {
30776
+ function lintRequirement(req, path302, opts = {}) {
30775
30777
  const out = [];
30776
30778
  const vague = opts.language === "en" ? VAGUE_EN : VAGUE_ES;
30777
30779
  const tech = opts.language === "en" ? TECH_EN : TECH_ES;
@@ -30782,33 +30784,33 @@ function lintRequirement(req, path292, opts = {}) {
30782
30784
  ...req.scenarios.flatMap((s) => [...s.when.map((w) => ({ text: w, line: s.line })), ...s.then.map((t) => ({ text: t, line: s.line }))])
30783
30785
  ];
30784
30786
  for (const part of parts) {
30785
- out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path292, part.line));
30787
+ out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path302, part.line));
30786
30788
  if (opts.businessOnly !== false) {
30787
- out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path292, part.line));
30789
+ out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path302, part.line));
30788
30790
  }
30789
30791
  }
30790
- out.push(...lintPlaceholders(req, path292));
30792
+ out.push(...lintPlaceholders(req, path302));
30791
30793
  if (req.scenarios.length === 0) {
30792
- out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path292, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
30794
+ out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path302, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
30793
30795
  }
30794
30796
  return out;
30795
30797
  }
30796
- function lintDelta(delta, livingRequirements, path292, opts = {}) {
30798
+ function lintDelta(delta, livingRequirements, path302, opts = {}) {
30797
30799
  const out = [...delta.diagnostics];
30798
30800
  for (const req of [...delta.added, ...delta.modified]) {
30799
- out.push(...lintRequirement(req, path292, opts));
30801
+ out.push(...lintRequirement(req, path302, opts));
30800
30802
  }
30801
30803
  for (const req of delta.modified) {
30802
30804
  const living = livingRequirements.get(req.id);
30803
30805
  if (!living) {
30804
- out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path292, line: req.line }));
30806
+ out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path302, line: req.line }));
30805
30807
  continue;
30806
30808
  }
30807
30809
  for (const existing of living.scenarios) {
30808
30810
  if (!req.scenarios.some((s) => s.id === existing.id)) {
30809
30811
  out.push(
30810
30812
  diag("TRACE-007", "error", `MODIFIED ${req.id} pierde el escenario ${existing.id}: copia el bloque completo`, {
30811
- path: path292,
30813
+ path: path302,
30812
30814
  line: req.line,
30813
30815
  suggestion: "Copia el bloque completo de la spec viva y ed\xEDtalo; para quitarlo, decl\xE1ralo en REMOVED"
30814
30816
  })
@@ -30819,15 +30821,15 @@ function lintDelta(delta, livingRequirements, path292, opts = {}) {
30819
30821
  for (const req of delta.removed) {
30820
30822
  const living = livingRequirements.get(req.id);
30821
30823
  if (!living) {
30822
- out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path292, line: req.line }));
30824
+ out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path302, line: req.line }));
30823
30825
  }
30824
30826
  }
30825
30827
  for (const rename2 of delta.renamed) {
30826
30828
  if (rename2.from.id !== rename2.to.id) {
30827
- out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path292, line: rename2.line }));
30829
+ out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path302, line: rename2.line }));
30828
30830
  }
30829
30831
  if (!livingRequirements.has(rename2.from.id)) {
30830
- out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path292, line: rename2.line }));
30832
+ out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path302, line: rename2.line }));
30831
30833
  }
30832
30834
  }
30833
30835
  return out;
@@ -30851,7 +30853,7 @@ var MERMAID_KEYWORDS = [
30851
30853
  "architecture-beta"
30852
30854
  ];
30853
30855
  var MERMAID_BLOCKS = /^\s*(alt|loop|opt|par|rect|critical|break|subgraph)\b/;
30854
- function lintPlan(planText, path292) {
30856
+ function lintPlan(planText, path302) {
30855
30857
  const out = [];
30856
30858
  const blocks = [...planText.matchAll(/```mermaid\r?\n([\s\S]*?)```/g)];
30857
30859
  for (const [index, block] of blocks.entries()) {
@@ -30861,7 +30863,7 @@ function lintPlan(planText, path292) {
30861
30863
  if (!MERMAID_KEYWORDS.some((keyword) => first.startsWith(keyword))) {
30862
30864
  out.push(
30863
30865
  diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: la primera l\xEDnea debe declarar el tipo (${MERMAID_KEYWORDS.slice(0, 5).join(", ")}\u2026) y empieza por "${first.slice(0, 30)}"`, {
30864
- path: path292,
30866
+ path: path302,
30865
30867
  suggestion: "Corrige el tipo del diagrama o elimina el bloque"
30866
30868
  })
30867
30869
  );
@@ -30875,7 +30877,7 @@ function lintPlan(planText, path292) {
30875
30877
  if (open !== 0) {
30876
30878
  out.push(
30877
30879
  diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: faltan ${Math.abs(open)} \`end\` (bloques alt/loop/subgraph sin cerrar)`, {
30878
- path: path292,
30880
+ path: path302,
30879
30881
  suggestion: "Cierra cada bloque alt/loop/opt/par/rect/subgraph con `end`"
30880
30882
  })
30881
30883
  );
@@ -30887,7 +30889,7 @@ function lintPlan(planText, path292) {
30887
30889
  if (message.includes(";")) {
30888
30890
  out.push(
30889
30891
  diag("LINT-PLN-003", "error", `Diagrama mermaid ${index + 1}: el mensaje "${message.trim().slice(0, 40)}\u2026" usa \`;\` y mermaid lo interpreta como fin de sentencia`, {
30890
- path: path292,
30892
+ path: path302,
30891
30893
  suggestion: "Sustituye `;` por `\xB7` o `,` en los mensajes de sequenceDiagram"
30892
30894
  })
30893
30895
  );
@@ -35712,6 +35714,140 @@ async function upgradeAdvisory(root) {
35712
35714
  }
35713
35715
  return { plan, diagnostics };
35714
35716
  }
35717
+ var PACKAGE_NAME = "specatlas";
35718
+ var REGISTRY = "https://registry.npmjs.org";
35719
+ var TTL_MS = 24 * 60 * 60 * 1e3;
35720
+ var TIMEOUT_MS = 2e3;
35721
+ function detectarInstalacion(rutaEjecutable2, plataforma = process.platform) {
35722
+ const ruta = toPosix(rutaEjecutable2).toLowerCase();
35723
+ if (/[/](_npx|\.npm[/]_npx)[/]/.test(ruta) || ruta.includes("/.pnpm-store/") || /[/]dlx-[^/]*[/]/.test(ruta)) {
35724
+ return {
35725
+ gestor: "npx",
35726
+ motivo: "la herramienta se est\xE1 ejecutando sin instalar: cada ejecuci\xF3n ya usa la versi\xF3n publicada"
35727
+ };
35728
+ }
35729
+ if (ruta.includes("/pnpm/global/") || ruta.includes("/.pnpm/global/") || /[/]pnpm[/][^/]*[/]global[/]/.test(ruta)) {
35730
+ return { gestor: "pnpm", comando: `pnpm add -g ${PACKAGE_NAME}@latest` };
35731
+ }
35732
+ if (ruta.includes("/.bun/")) {
35733
+ return { gestor: "bun", comando: `bun add -g ${PACKAGE_NAME}@latest` };
35734
+ }
35735
+ if (ruta.includes("/.yarn/") || ruta.includes("/yarn/global/")) {
35736
+ return { gestor: "yarn", comando: `yarn global add ${PACKAGE_NAME}@latest` };
35737
+ }
35738
+ if (ruta.includes("/npm/node_modules/") || ruta.includes("/roaming/npm/") || ruta.includes("/lib/node_modules/") || ruta.includes("/npm-global/")) {
35739
+ return { gestor: "npm", comando: `npm install -g ${PACKAGE_NAME}@latest` };
35740
+ }
35741
+ return { gestor: "desconocido", comando: plataforma === "win32" ? `npm install -g ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest` };
35742
+ }
35743
+ function compararVersiones(a, b) {
35744
+ const partes = (v) => v.trim().replace(/^v/, "").split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
35745
+ const ap = partes(a);
35746
+ const bp = partes(b);
35747
+ for (let i = 0; i < 3; i += 1) {
35748
+ const diff = (ap[i] ?? 0) - (bp[i] ?? 0);
35749
+ if (diff !== 0) return diff > 0 ? 1 : -1;
35750
+ }
35751
+ return 0;
35752
+ }
35753
+ function hayVersionNueva(instalada, publicada) {
35754
+ if (!publicada) return false;
35755
+ return compararVersiones(publicada, instalada) > 0;
35756
+ }
35757
+ var PREFERENCIAS_POR_DEFECTO = { comprobar: true, automatica: false };
35758
+ function directorioPersonal(home = os.homedir()) {
35759
+ return path25.join(home, ".specatlas");
35760
+ }
35761
+ function rutaPreferencias(home) {
35762
+ return path25.join(directorioPersonal(home), "config.json");
35763
+ }
35764
+ function rutaEstado(home) {
35765
+ return path25.join(directorioPersonal(home), "update-check.json");
35766
+ }
35767
+ async function leerPreferencias(home) {
35768
+ const raw = await readTextIfExists(rutaPreferencias(home));
35769
+ if (raw === void 0) return { ...PREFERENCIAS_POR_DEFECTO };
35770
+ try {
35771
+ const data = JSON.parse(raw);
35772
+ return {
35773
+ comprobar: data.update?.comprobar ?? PREFERENCIAS_POR_DEFECTO.comprobar,
35774
+ automatica: data.update?.automatica ?? PREFERENCIAS_POR_DEFECTO.automatica
35775
+ };
35776
+ } catch {
35777
+ return { ...PREFERENCIAS_POR_DEFECTO };
35778
+ }
35779
+ }
35780
+ async function escribirPreferencias(preferencias, home) {
35781
+ const file = rutaPreferencias(home);
35782
+ await ensureDir(path25.dirname(file));
35783
+ await writeText(file, `${JSON.stringify({ update: preferencias }, null, 2)}
35784
+ `);
35785
+ return file;
35786
+ }
35787
+ async function leerEstado(home) {
35788
+ const raw = await readTextIfExists(rutaEstado(home));
35789
+ if (raw === void 0) return {};
35790
+ try {
35791
+ return JSON.parse(raw);
35792
+ } catch {
35793
+ return {};
35794
+ }
35795
+ }
35796
+ async function escribirEstado(estado, home) {
35797
+ const file = rutaEstado(home);
35798
+ await ensureDir(path25.dirname(file));
35799
+ await writeText(file, `${JSON.stringify(estado, null, 2)}
35800
+ `);
35801
+ }
35802
+ function tocaConsultar(estado, ahora = /* @__PURE__ */ new Date(), ttlMs = TTL_MS) {
35803
+ if (!estado.ultimaConsulta) return true;
35804
+ const ultima = Date.parse(estado.ultimaConsulta);
35805
+ if (Number.isNaN(ultima)) return true;
35806
+ return ahora.getTime() - ultima >= ttlMs;
35807
+ }
35808
+ function esIntegracionContinua(env = process.env) {
35809
+ return Boolean(env["CI"] || env["CONTINUOUS_INTEGRATION"] || env["GITHUB_ACTIONS"] || env["GITLAB_CI"] || env["BUILDKITE"] || env["TEAMCITY_VERSION"]);
35810
+ }
35811
+ function comprobacionDesactivada(env = process.env) {
35812
+ const flag3 = env["SPECATLAS_NO_UPDATE_CHECK"];
35813
+ return flag3 === "1" || flag3 === "true";
35814
+ }
35815
+ async function consultarVersionPublicada(timeoutMs = TIMEOUT_MS, fetchImpl = fetch) {
35816
+ const control = new AbortController();
35817
+ const timer = setTimeout(() => control.abort(), timeoutMs);
35818
+ try {
35819
+ const respuesta = await fetchImpl(`${REGISTRY}/-/package/${PACKAGE_NAME}/dist-tags`, { signal: control.signal });
35820
+ if (!respuesta.ok) return void 0;
35821
+ const data = await respuesta.json();
35822
+ return typeof data.latest === "string" ? data.latest : void 0;
35823
+ } catch {
35824
+ return void 0;
35825
+ } finally {
35826
+ clearTimeout(timer);
35827
+ }
35828
+ }
35829
+ async function comprobarActualizacion(ctx) {
35830
+ const env = ctx.env ?? process.env;
35831
+ if (ctx.json || esIntegracionContinua(env) || comprobacionDesactivada(env)) return void 0;
35832
+ const preferencias = await leerPreferencias(ctx.home);
35833
+ if (!preferencias.comprobar) return void 0;
35834
+ const ahora = ctx.ahora ?? /* @__PURE__ */ new Date();
35835
+ const estado = await leerEstado(ctx.home);
35836
+ let publicada = estado.ultimaVersion;
35837
+ if (tocaConsultar(estado, ahora)) {
35838
+ publicada = await consultarVersionPublicada(TIMEOUT_MS, ctx.fetchImpl ?? fetch);
35839
+ if (publicada === void 0) return void 0;
35840
+ await escribirEstado({ ultimaConsulta: ahora.toISOString(), ultimaVersion: publicada }, ctx.home);
35841
+ }
35842
+ if (!hayVersionNueva(ctx.instalada, publicada)) return void 0;
35843
+ const instalacion = detectarInstalacion(ctx.rutaEjecutable);
35844
+ return {
35845
+ instalada: ctx.instalada,
35846
+ publicada,
35847
+ comando: instalacion.comando ?? `npm install -g ${PACKAGE_NAME}@latest`,
35848
+ gestor: instalacion.gestor
35849
+ };
35850
+ }
35715
35851
  var DOCS_MARKER_START = "<!-- specatlas:generado:inicio -->";
35716
35852
  var DOCS_MARKER_END = "<!-- specatlas:generado:fin -->";
35717
35853
  var ES_WORDING = {
@@ -36197,7 +36333,7 @@ ${notes}
36197
36333
  ${existing}`;
36198
36334
  }
36199
36335
  async function generateDocs(opts) {
36200
- const root = path25.resolve(opts.root);
36336
+ const root = path26.resolve(opts.root);
36201
36337
  const { config } = await loadWorkspace(root);
36202
36338
  const change = await loadChange(root, opts.slug);
36203
36339
  if (!change.meta) {
@@ -36213,10 +36349,10 @@ async function generateDocs(opts) {
36213
36349
  const tipos = tipo === "all" ? ["tecnica", "manual"] : [tipo];
36214
36350
  const templates = templatesFor(language);
36215
36351
  const [proposalRaw, planRaw, glossaryRaw, analyzeRaw, mockupManifest] = await Promise.all([
36216
- readTextIfExists(path25.join(change.dir, "proposal.md")),
36217
- readTextIfExists(path25.join(change.dir, "plan.md")),
36218
- readTextIfExists(path25.join(root, ".sdd", "glossary.md")),
36219
- readTextIfExists(path25.join(change.dir, "analyze.md")),
36352
+ readTextIfExists(path26.join(change.dir, "proposal.md")),
36353
+ readTextIfExists(path26.join(change.dir, "plan.md")),
36354
+ readTextIfExists(path26.join(root, ".sdd", "glossary.md")),
36355
+ readTextIfExists(path26.join(change.dir, "analyze.md")),
36220
36356
  readMockupManifest(root, change.slug)
36221
36357
  ]);
36222
36358
  const mockups = (mockupManifest.manifest?.screens ?? []).map((screen) => ({
@@ -36230,7 +36366,7 @@ async function generateDocs(opts) {
36230
36366
  const files = [];
36231
36367
  const diagnostics = [];
36232
36368
  for (const current of tipos) {
36233
- const file = path25.join(change.dir, "docs", `${current}.md`);
36369
+ const file = path26.join(change.dir, "docs", `${current}.md`);
36234
36370
  const block = renderTemplate(current === "tecnica" ? templates.docTecnica : templates.docManual, data);
36235
36371
  const existing = await readTextIfExists(file);
36236
36372
  const content = mergeManaged(existing, block, language);
@@ -36239,7 +36375,7 @@ async function generateDocs(opts) {
36239
36375
  const label = current === "tecnica" ? "t\xE9cnico" : "manual";
36240
36376
  const title = `${current === "tecnica" ? language === "en" ? "Technical document" : "Documento t\xE9cnico" : language === "en" ? "User manual" : "Manual de usuario"} \u2014 ${change.meta.title ?? change.slug}`;
36241
36377
  try {
36242
- const htmlPath = path25.join(change.dir, "docs", `${current}.html`);
36378
+ const htmlPath = path26.join(change.dir, "docs", `${current}.html`);
36243
36379
  await writeText(htmlPath, renderDocument(content, { theme: "auto", title }));
36244
36380
  doc.htmlPath = htmlPath;
36245
36381
  } catch (error2) {
@@ -36251,7 +36387,7 @@ async function generateDocs(opts) {
36251
36387
  );
36252
36388
  }
36253
36389
  try {
36254
- const pdfPath = path25.join(change.dir, "docs", `${current}.pdf`);
36390
+ const pdfPath = path26.join(change.dir, "docs", `${current}.pdf`);
36255
36391
  const pdf = await renderPdf(content, { title, project: config.project.name, generatedAt: localStamp(ahora) });
36256
36392
  await writeBytes(pdfPath, pdf.bytes);
36257
36393
  doc.pdfPath = pdfPath;
@@ -36304,7 +36440,7 @@ function resultOf(diagnostic, root) {
36304
36440
  message: { text: diagnostic.message }
36305
36441
  };
36306
36442
  if (diagnostic.path) {
36307
- const rel = path26.relative(root, diagnostic.path);
36443
+ const rel = path27.relative(root, diagnostic.path);
36308
36444
  if (rel !== "") {
36309
36445
  const physicalLocation = {
36310
36446
  artifactLocation: { uri: toPosix(rel) },
@@ -36359,7 +36495,7 @@ async function runDoctor(root) {
36359
36495
  const approvals = await loadApprovals(workspace.sddDir);
36360
36496
  findings.push(...approvals.diagnostics);
36361
36497
  for (const change of workspace.changes) {
36362
- const deltaPath = path27.join(change.dir, "spec.md");
36498
+ const deltaPath = path28.join(change.dir, "spec.md");
36363
36499
  const deltaContent = await readTextIfExists(deltaPath);
36364
36500
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
36365
36501
  if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
@@ -36381,7 +36517,7 @@ async function runDoctor(root) {
36381
36517
  }
36382
36518
  for (const override of change.meta?.overrides ?? []) {
36383
36519
  if (!override.reason.trim() || !override.by.trim()) {
36384
- findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path27.join(change.dir, "meta.yaml") }));
36520
+ findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path28.join(change.dir, "meta.yaml") }));
36385
36521
  }
36386
36522
  }
36387
36523
  }
@@ -36413,7 +36549,7 @@ function livingRequirementsMap(specs) {
36413
36549
  return map;
36414
36550
  }
36415
36551
  async function runCiGate(opts) {
36416
- const root = path28.resolve(opts.root);
36552
+ const root = path29.resolve(opts.root);
36417
36553
  const { workspace, config } = await loadWorkspace(root);
36418
36554
  const diagnostics = [];
36419
36555
  const checks = [];
@@ -36424,7 +36560,7 @@ async function runCiGate(opts) {
36424
36560
  let changesErrors = 0;
36425
36561
  let changesWarnings = 0;
36426
36562
  for (const change of workspace.changes) {
36427
- const lintFindings = change.delta ? lintDelta(change.delta, living, path28.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
36563
+ const lintFindings = change.delta ? lintDelta(change.delta, living, path29.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
36428
36564
  const trace = checkTrace({
36429
36565
  specs: workspace.specs,
36430
36566
  change,
@@ -36565,6 +36701,7 @@ var CATALOG = [
36565
36701
  { name: "run", description: "Persiste runs y eventos de ejecuci\xF3n (auditor\xEDa y reanudaci\xF3n)", flags: ["phase", "inputs", "data", "slug", "status", "json"], usage: "satlas run start|event|status|show|list" },
36566
36702
  { name: "hash", description: "Calcula el sha256 de un archivo o texto (para evidencia)", flags: ["text", "json"], usage: 'satlas hash <archivo> | satlas hash --text "salida"' },
36567
36703
  { name: "explain", description: "Explica un c\xF3digo de diagn\xF3stico: qu\xE9 significa, por qu\xE9 importa y c\xF3mo se cierra", flags: ["json"], usage: "satlas explain [<c\xF3digo>]" },
36704
+ { name: "self-update", description: "Actualiza la herramienta a la versi\xF3n publicada (o solo comprueba si la hay)", flags: ["check", "auto", "json"], usage: "satlas self-update [--check] [--auto on|off]" },
36568
36705
  { name: "version", description: "Muestra la versi\xF3n", flags: ["json"], usage: "satlas version" },
36569
36706
  { name: "help", description: "Muestra la ayuda", flags: ["json"], usage: "satlas help [comando]" }
36570
36707
  ];
@@ -36682,26 +36819,26 @@ function cliVersion() {
36682
36819
  }
36683
36820
 
36684
36821
  // src/commands/adapters.ts
36685
- import path31 from "path";
36822
+ import path32 from "path";
36686
36823
 
36687
36824
  // ../adapters/dist/index.js
36688
- import path29 from "path";
36825
+ import path30 from "path";
36689
36826
  import { stringify as stringifyYaml9 } from "yaml";
36690
36827
  import path210 from "path";
36691
36828
  async function loadWorkflow(workflowDir) {
36692
- const phasesDir = path29.join(workflowDir, "phases");
36693
- const snippetsDir = path29.join(workflowDir, "snippets");
36829
+ const phasesDir = path30.join(workflowDir, "phases");
36830
+ const snippetsDir = path30.join(workflowDir, "snippets");
36694
36831
  const phases = [];
36695
36832
  const snippets = /* @__PURE__ */ new Map();
36696
36833
  const hashParts = [];
36697
36834
  for (const entry of (await listDir(phasesDir)).sort()) {
36698
36835
  if (!entry.endsWith(".md")) continue;
36699
- const filePath = path29.join(phasesDir, entry);
36836
+ const filePath = path30.join(phasesDir, entry);
36700
36837
  const raw = await readText(filePath);
36701
36838
  hashParts.push(raw);
36702
36839
  const fm = parseFrontmatter(raw, filePath);
36703
36840
  const data = fm.data;
36704
- const id = typeof data["id"] === "string" ? data["id"] : path29.basename(entry, ".md");
36841
+ const id = typeof data["id"] === "string" ? data["id"] : path30.basename(entry, ".md");
36705
36842
  phases.push({
36706
36843
  id,
36707
36844
  title: typeof data["title"] === "string" ? data["title"] : id,
@@ -36715,9 +36852,9 @@ async function loadWorkflow(workflowDir) {
36715
36852
  }
36716
36853
  for (const entry of (await listDir(snippetsDir)).sort()) {
36717
36854
  if (!entry.endsWith(".md")) continue;
36718
- const raw = await readTextIfExists(path29.join(snippetsDir, entry)) ?? "";
36855
+ const raw = await readTextIfExists(path30.join(snippetsDir, entry)) ?? "";
36719
36856
  hashParts.push(raw);
36720
- snippets.set(path29.basename(entry, ".md"), raw.trim());
36857
+ snippets.set(path30.basename(entry, ".md"), raw.trim());
36721
36858
  }
36722
36859
  return { phases: phases.sort((a, b) => a.id.localeCompare(b.id)), snippets, sourceHash: sha256(hashParts.join("\n---\n")) };
36723
36860
  }
@@ -37032,11 +37169,11 @@ async function checkAdapters(opts) {
37032
37169
  }
37033
37170
 
37034
37171
  // src/paths.ts
37035
- import path30 from "path";
37172
+ import path31 from "path";
37036
37173
  import { fileURLToPath as fileURLToPath2 } from "url";
37037
37174
  async function isSourceRepoRoot(dir) {
37038
- if (!await exists(path30.join(dir, "pnpm-workspace.yaml"))) return false;
37039
- const raw = await readTextIfExists(path30.join(dir, "packages", "cli", "package.json"));
37175
+ if (!await exists(path31.join(dir, "pnpm-workspace.yaml"))) return false;
37176
+ const raw = await readTextIfExists(path31.join(dir, "packages", "cli", "package.json"));
37040
37177
  if (raw === void 0) return false;
37041
37178
  try {
37042
37179
  return JSON.parse(raw).name === "specatlas";
@@ -37048,17 +37185,17 @@ async function sourceRepoRoot(startDir) {
37048
37185
  let dir = startDir;
37049
37186
  for (let i = 0; i < 5; i += 1) {
37050
37187
  if (await isSourceRepoRoot(dir)) return dir;
37051
- const parent = path30.dirname(dir);
37188
+ const parent = path31.dirname(dir);
37052
37189
  if (parent === dir) break;
37053
37190
  dir = parent;
37054
37191
  }
37055
37192
  return void 0;
37056
37193
  }
37057
37194
  async function resolveFromSource(relative) {
37058
- const start = path30.dirname(fileURLToPath2(import.meta.url));
37195
+ const start = path31.dirname(fileURLToPath2(import.meta.url));
37059
37196
  const root = await sourceRepoRoot(start);
37060
37197
  if (!root) return void 0;
37061
- const candidate = path30.join(root, relative);
37198
+ const candidate = path31.join(root, relative);
37062
37199
  return await exists(candidate) ? candidate : void 0;
37063
37200
  }
37064
37201
  async function resolveProfilesDir() {
@@ -37069,16 +37206,16 @@ async function resolveProfilesDir() {
37069
37206
  async function resolveWorkflowDir() {
37070
37207
  const env = process.env["SPECATLAS_WORKFLOW_DIR"];
37071
37208
  if (env && await exists(env)) return env;
37072
- return await resolveFromSource("workflow") ?? walkUpFor(path30.join("workflow", "phases"));
37209
+ return await resolveFromSource("workflow") ?? walkUpFor(path31.join("workflow", "phases"));
37073
37210
  }
37074
37211
  async function walkUpFor(relative) {
37075
- let dir = path30.dirname(fileURLToPath2(import.meta.url));
37212
+ let dir = path31.dirname(fileURLToPath2(import.meta.url));
37076
37213
  for (let i = 0; i < 8; i += 1) {
37077
- const candidate = path30.join(dir, relative);
37214
+ const candidate = path31.join(dir, relative);
37078
37215
  if (await exists(candidate)) {
37079
- return relative.includes(path30.sep) ? path30.dirname(candidate) : candidate;
37216
+ return relative.includes(path31.sep) ? path31.dirname(candidate) : candidate;
37080
37217
  }
37081
- const parent = path30.dirname(dir);
37218
+ const parent = path31.dirname(dir);
37082
37219
  if (parent === dir) break;
37083
37220
  dir = parent;
37084
37221
  }
@@ -37156,7 +37293,7 @@ async function runAdapters(ctx) {
37156
37293
  for (const [target, count2] of byTarget) lines.push(` ${target}: ${count2} archivo(s)`);
37157
37294
  if (report.written.length > 0) {
37158
37295
  lines.push("");
37159
- for (const p of report.written) lines.push(` + ${path31.relative(ctx.cwd, path31.join(root, p))}`);
37296
+ for (const p of report.written) lines.push(` + ${path32.relative(ctx.cwd, path32.join(root, p))}`);
37160
37297
  } else {
37161
37298
  lines.push("");
37162
37299
  lines.push("Sin cambios: los adaptadores ya estaban al d\xEDa.");
@@ -37167,7 +37304,7 @@ async function runAdapters(ctx) {
37167
37304
  }
37168
37305
 
37169
37306
  // src/commands/adopt.ts
37170
- import path32 from "path";
37307
+ import path33 from "path";
37171
37308
  async function runAdopt(ctx) {
37172
37309
  const { root } = await requireWorkspace(ctx);
37173
37310
  const domainsFlag = flagString(ctx.flags, "domains") ?? flagString(ctx.flags, "domain");
@@ -37187,11 +37324,11 @@ async function runAdopt(ctx) {
37187
37324
  }
37188
37325
  if (result.createdSpecs.length > 0) {
37189
37326
  lines.push("");
37190
- for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${path32.relative(ctx.cwd, spec)}`);
37327
+ for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${path33.relative(ctx.cwd, spec)}`);
37191
37328
  }
37192
37329
  if (result.reportPath && !result.dryRun) {
37193
37330
  lines.push("");
37194
- lines.push(` Informe: ${path32.relative(ctx.cwd, result.reportPath)}`);
37331
+ lines.push(` Informe: ${path33.relative(ctx.cwd, result.reportPath)}`);
37195
37332
  }
37196
37333
  lines.push("");
37197
37334
  lines.push("Siguiente: fase /satlas-adopt por dominio (requisitos AS-IS) y luego:");
@@ -37204,8 +37341,8 @@ async function runAdopt(ctx) {
37204
37341
  diagnostics: result.diagnostics,
37205
37342
  data: {
37206
37343
  domains: result.domains.map((d) => ({ name: d.name, files: d.files, existingSpec: d.existingSpec })),
37207
- createdSpecs: result.createdSpecs.map((s) => path32.relative(ctx.cwd, s)),
37208
- reportPath: result.reportPath ? path32.relative(ctx.cwd, result.reportPath) : void 0,
37344
+ createdSpecs: result.createdSpecs.map((s) => path33.relative(ctx.cwd, s)),
37345
+ reportPath: result.reportPath ? path33.relative(ctx.cwd, result.reportPath) : void 0,
37209
37346
  stack: result.stack,
37210
37347
  dryRun: result.dryRun
37211
37348
  },
@@ -37254,7 +37391,7 @@ async function runAmend(ctx) {
37254
37391
  }
37255
37392
 
37256
37393
  // src/commands/analyze.ts
37257
- import path33 from "path";
37394
+ import path34 from "path";
37258
37395
  async function runAnalyzeCommand(ctx) {
37259
37396
  const { root } = await requireWorkspace(ctx);
37260
37397
  const slug = ctx.positionals[0];
@@ -37265,23 +37402,23 @@ async function runAnalyzeCommand(ctx) {
37265
37402
  const lines = [`An\xE1lisis \u2014 ${slug}`, "", ` estado: ${result.status}`, ` hallazgos: ${result.summary.errors} errores, ${result.summary.warnings} avisos`, ` evidencia: ${result.evidence.done}/${result.evidence.total} escenarios`];
37266
37403
  if (result.waves) lines.push(` olas: ${result.waves.blocks} bloque(s), ${result.waves.waves} ola(s), ${result.waves.tasks} tarea(s)`);
37267
37404
  if (result.mockups) lines.push(` mockups: ${result.mockups.screens} pantalla(s)${result.mockups.stale ? " (desactualizados)" : ""}`);
37268
- if (result.path) lines.push("", ` informe: ${path33.relative(ctx.cwd, result.path)}`);
37405
+ if (result.path) lines.push("", ` informe: ${path34.relative(ctx.cwd, result.path)}`);
37269
37406
  for (const finding of result.findings) {
37270
37407
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}`);
37271
37408
  }
37272
37409
  return {
37273
37410
  exitCode: result.status === "blocked" ? 1 : 0,
37274
37411
  diagnostics: result.findings,
37275
- data: result.path ? { ...result, path: path33.relative(ctx.cwd, result.path) } : result,
37412
+ data: result.path ? { ...result, path: path34.relative(ctx.cwd, result.path) } : result,
37276
37413
  text: lines
37277
37414
  };
37278
37415
  }
37279
37416
 
37280
37417
  // src/commands/approve.ts
37281
- import path35 from "path";
37418
+ import path36 from "path";
37282
37419
 
37283
37420
  // src/commands/issue.ts
37284
- import path34 from "path";
37421
+ import path35 from "path";
37285
37422
  async function runIssue(ctx) {
37286
37423
  const action = ctx.positionals[0] ?? "status";
37287
37424
  const slug = ctx.positionals[1];
@@ -37309,7 +37446,7 @@ async function runIssue(ctx) {
37309
37446
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-ISSUE-000", severity: "error", message: "Falta el slug: satlas issue status <slug>" }] };
37310
37447
  }
37311
37448
  const change = await loadChange(root, slug);
37312
- const { config } = await loadConfig(path34.join(root, ".sdd"));
37449
+ const { config } = await loadConfig(path35.join(root, ".sdd"));
37313
37450
  const tracker = change.meta?.tracker;
37314
37451
  const lines = [`Issue de ${slug}`, ""];
37315
37452
  if (!tracker || tracker.provider !== "github") {
@@ -37380,7 +37517,7 @@ async function runApprove(ctx) {
37380
37517
  };
37381
37518
  }
37382
37519
  const { root, workspace, config } = await requireWorkspace(ctx);
37383
- const artifact = target.includes("/") || target.includes("\\") ? target : path35.join("changes", target, "spec.md");
37520
+ const artifact = target.includes("/") || target.includes("\\") ? target : path36.join("changes", target, "spec.md");
37384
37521
  const channelFlag = flagString(ctx.flags, "channel");
37385
37522
  const channel = channelFlag === "presentation" || channelFlag === "editor" || channelFlag === "pr" || channelFlag === "tracker" ? channelFlag : "cli";
37386
37523
  const change = workspace.changes.find(
@@ -37421,13 +37558,13 @@ async function runApprove(ctx) {
37421
37558
  return {
37422
37559
  exitCode: hasErrors2 ? 1 : 0,
37423
37560
  diagnostics: result.diagnostics,
37424
- data: result.approval ? { ...result.approval, file: path35.relative(ctx.cwd, result.file) } : void 0,
37561
+ data: result.approval ? { ...result.approval, file: path36.relative(ctx.cwd, result.file) } : void 0,
37425
37562
  text: lines
37426
37563
  };
37427
37564
  }
37428
37565
 
37429
37566
  // src/commands/archive.ts
37430
- import path36 from "path";
37567
+ import path37 from "path";
37431
37568
  async function runArchive(ctx) {
37432
37569
  const slug = ctx.positionals[0];
37433
37570
  if (!slug) {
@@ -37464,7 +37601,7 @@ async function runArchive(ctx) {
37464
37601
  } else {
37465
37602
  lines.push("El delta no contiene operaciones (ADDED/MODIFIED/REMOVED/RENAMED).");
37466
37603
  }
37467
- if (result.archivedTo) lines.push(` archivado en: ${path36.relative(ctx.cwd, result.archivedTo)}`);
37604
+ if (result.archivedTo) lines.push(` archivado en: ${path37.relative(ctx.cwd, result.archivedTo)}`);
37468
37605
  const hasErrors2 = result.diagnostics.some((d) => d.severity === "error");
37469
37606
  return {
37470
37607
  exitCode: hasErrors2 ? 1 : 0,
@@ -37475,7 +37612,7 @@ async function runArchive(ctx) {
37475
37612
  }
37476
37613
 
37477
37614
  // src/commands/ci.ts
37478
- import path37 from "path";
37615
+ import path38 from "path";
37479
37616
  async function runCi(ctx) {
37480
37617
  const { root, config } = await requireWorkspace(ctx);
37481
37618
  const strict = flagBool(ctx.flags, "strict");
@@ -37501,10 +37638,10 @@ async function runCi(ctx) {
37501
37638
  const lines = ["CI de SpecAtlas", ""];
37502
37639
  let sarifWritten;
37503
37640
  if (sarifPath) {
37504
- const target = path37.resolve(ctx.cwd, sarifPath);
37641
+ const target = path38.resolve(ctx.cwd, sarifPath);
37505
37642
  try {
37506
37643
  await writeText(target, toSarifText({ diagnostics: gate.diagnostics, root, version: cliVersion(), failed: gate.failed }));
37507
- sarifWritten = path37.relative(ctx.cwd, target);
37644
+ sarifWritten = path38.relative(ctx.cwd, target);
37508
37645
  } catch (err) {
37509
37646
  diagnostics.push({
37510
37647
  code: "ATLAS-CI-SARIF-001",
@@ -37581,7 +37718,7 @@ async function runClarify(ctx) {
37581
37718
  }
37582
37719
 
37583
37720
  // src/commands/contracts.ts
37584
- import path38 from "path";
37721
+ import path39 from "path";
37585
37722
  async function runContracts(ctx) {
37586
37723
  const slug = ctx.positionals[0];
37587
37724
  if (!slug) {
@@ -37601,7 +37738,7 @@ async function runContracts(ctx) {
37601
37738
  const coverage = contractCoverage(change, config.gates.contracts.mode);
37602
37739
  const findings = [...state.findings, ...coverage];
37603
37740
  for (const file of state.files) {
37604
- lines.push(` ${path38.relative(ctx.cwd, file.path)} (${file.format}) \u2014 ${file.operations.length} operaci\xF3n(es)`);
37741
+ lines.push(` ${path39.relative(ctx.cwd, file.path)} (${file.format}) \u2014 ${file.operations.length} operaci\xF3n(es)`);
37605
37742
  for (const operation of file.operations) lines.push(` ${operation.id}`);
37606
37743
  }
37607
37744
  lines.push("");
@@ -37617,7 +37754,7 @@ async function runContracts(ctx) {
37617
37754
  data: {
37618
37755
  slug,
37619
37756
  mode: config.gates.contracts.mode,
37620
- contracts: state.files.map((file) => ({ path: path38.relative(ctx.cwd, file.path), format: file.format, operations: file.operations.map((operation) => operation.id) })),
37757
+ contracts: state.files.map((file) => ({ path: path39.relative(ctx.cwd, file.path), format: file.format, operations: file.operations.map((operation) => operation.id) })),
37621
37758
  findings
37622
37759
  },
37623
37760
  text: lines
@@ -37625,7 +37762,7 @@ async function runContracts(ctx) {
37625
37762
  }
37626
37763
 
37627
37764
  // src/commands/docs.ts
37628
- import path39 from "path";
37765
+ import path40 from "path";
37629
37766
  async function runDocs(ctx) {
37630
37767
  const slug = ctx.positionals[0];
37631
37768
  if (!slug) {
@@ -37639,9 +37776,9 @@ async function runDocs(ctx) {
37639
37776
  const warnings = result.diagnostics.filter((d) => d.severity === "warning");
37640
37777
  const lines = [msg("docs.title", ctx.language), ""];
37641
37778
  for (const file of result.files) {
37642
- lines.push(` ${file.created ? msg("docs.created", ctx.language) : msg("docs.updated", ctx.language)}: ${path39.relative(ctx.cwd, file.path)}`);
37643
- if (file.htmlPath) lines.push(` html: ${path39.relative(ctx.cwd, file.htmlPath)}`);
37644
- if (file.pdfPath) lines.push(` pdf: ${path39.relative(ctx.cwd, file.pdfPath)}`);
37779
+ lines.push(` ${file.created ? msg("docs.created", ctx.language) : msg("docs.updated", ctx.language)}: ${path40.relative(ctx.cwd, file.path)}`);
37780
+ if (file.htmlPath) lines.push(` html: ${path40.relative(ctx.cwd, file.htmlPath)}`);
37781
+ if (file.pdfPath) lines.push(` pdf: ${path40.relative(ctx.cwd, file.pdfPath)}`);
37645
37782
  }
37646
37783
  for (const warning of warnings) lines.push(` AVISO ${warning.code} \u2014 ${warning.message}`);
37647
37784
  for (const error2 of errors) lines.push(` ERROR ${error2.code} \u2014 ${error2.message}`);
@@ -37653,10 +37790,10 @@ async function runDocs(ctx) {
37653
37790
  tipo,
37654
37791
  files: result.files.map((file) => ({
37655
37792
  tipo: file.tipo,
37656
- path: path39.relative(ctx.cwd, file.path),
37793
+ path: path40.relative(ctx.cwd, file.path),
37657
37794
  created: file.created,
37658
- html: file.htmlPath ? path39.relative(ctx.cwd, file.htmlPath) : void 0,
37659
- pdf: file.pdfPath ? path39.relative(ctx.cwd, file.pdfPath) : void 0
37795
+ html: file.htmlPath ? path40.relative(ctx.cwd, file.htmlPath) : void 0,
37796
+ pdf: file.pdfPath ? path40.relative(ctx.cwd, file.pdfPath) : void 0
37660
37797
  }))
37661
37798
  },
37662
37799
  text: lines
@@ -37664,7 +37801,7 @@ async function runDocs(ctx) {
37664
37801
  }
37665
37802
 
37666
37803
  // src/commands/drift.ts
37667
- import path40 from "path";
37804
+ import path41 from "path";
37668
37805
  async function runDrift(ctx) {
37669
37806
  const { root, config } = await requireWorkspace(ctx);
37670
37807
  const anchors = await loadAllAnchors(root);
@@ -37708,7 +37845,7 @@ async function runDrift(ctx) {
37708
37845
  mode: report.mode,
37709
37846
  domains: report.domains,
37710
37847
  checked: report.checked,
37711
- drifted: report.drifted.map((item) => ({ ...item, specPath: path40.relative(ctx.cwd, item.specPath) }))
37848
+ drifted: report.drifted.map((item) => ({ ...item, specPath: path41.relative(ctx.cwd, item.specPath) }))
37712
37849
  },
37713
37850
  text: lines
37714
37851
  };
@@ -37759,7 +37896,7 @@ async function runImpact(ctx) {
37759
37896
  }
37760
37897
 
37761
37898
  // src/commands/link.ts
37762
- import path41 from "path";
37899
+ import path42 from "path";
37763
37900
  async function runLink(ctx) {
37764
37901
  const { root } = await requireWorkspace(ctx);
37765
37902
  const action = ctx.positionals[0] ?? "list";
@@ -37771,7 +37908,7 @@ async function runLink(ctx) {
37771
37908
  } else {
37772
37909
  for (const entry of state.entries) {
37773
37910
  const status = entry.available ? `${entry.requirements} requisito(s) \xB7 ${entry.domains.join(", ") || "sin dominios"}` : "NO DISPONIBLE";
37774
- lines.push(` ${entry.name} ${path41.relative(ctx.cwd, entry.path)} ${status}`);
37911
+ lines.push(` ${entry.name} ${path42.relative(ctx.cwd, entry.path)} ${status}`);
37775
37912
  }
37776
37913
  }
37777
37914
  return {
@@ -37780,7 +37917,7 @@ async function runLink(ctx) {
37780
37917
  data: {
37781
37918
  links: state.entries.map((entry) => ({
37782
37919
  name: entry.name,
37783
- path: path41.relative(ctx.cwd, entry.path),
37920
+ path: path42.relative(ctx.cwd, entry.path),
37784
37921
  available: entry.available,
37785
37922
  requirements: entry.requirements,
37786
37923
  domains: entry.domains
@@ -37825,12 +37962,12 @@ async function runLink(ctx) {
37825
37962
  }
37826
37963
 
37827
37964
  // src/commands/profile.ts
37828
- import path42 from "path";
37965
+ import path43 from "path";
37829
37966
  async function runProfile(ctx) {
37830
37967
  const action = ctx.positionals[0] ?? "detect";
37831
37968
  const { root, workspace } = await requireWorkspace(ctx);
37832
37969
  const officialDir = await resolveProfilesDir();
37833
- const customDir = path42.join(workspace.sddDir, "profiles", "custom");
37970
+ const customDir = path43.join(workspace.sddDir, "profiles", "custom");
37834
37971
  const profiles = [...officialDir ? await loadProfilesFromDir(officialDir) : [], ...await loadProfilesFromDir(customDir)];
37835
37972
  if (action === "detect") {
37836
37973
  const result = await detectProfiles(root, profiles);
@@ -37859,7 +37996,7 @@ async function runProfile(ctx) {
37859
37996
  if (!name) {
37860
37997
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-001", severity: "error", message: "Falta el nombre del perfil: satlas profile create <nombre>" }] };
37861
37998
  }
37862
- const file = path42.join(customDir, `${name}.yaml`);
37999
+ const file = path43.join(customDir, `${name}.yaml`);
37863
38000
  if (await exists(file)) {
37864
38001
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-002", severity: "error", message: `El perfil ya existe: ${file}` }] };
37865
38002
  }
@@ -37868,7 +38005,7 @@ async function runProfile(ctx) {
37868
38005
  exitCode: 0,
37869
38006
  diagnostics: [],
37870
38007
  data: { file },
37871
- text: [`Perfil creado: ${path42.relative(ctx.cwd, file)}`, "", "Ed\xEDtalo con los comandos reales de tu stack (build/lint/test) y tus anti-patrones."]
38008
+ text: [`Perfil creado: ${path43.relative(ctx.cwd, file)}`, "", "Ed\xEDtalo con los comandos reales de tu stack (build/lint/test) y tus anti-patrones."]
37872
38009
  };
37873
38010
  }
37874
38011
  return {
@@ -37877,7 +38014,7 @@ async function runProfile(ctx) {
37877
38014
  };
37878
38015
  }
37879
38016
  async function isInside(dir, name) {
37880
- return exists(path42.join(dir, `${name}.yaml`));
38017
+ return exists(path43.join(dir, `${name}.yaml`));
37881
38018
  }
37882
38019
  function profileTemplate(name) {
37883
38020
  return `name: ${name}
@@ -37905,7 +38042,7 @@ assumptions: []
37905
38042
  }
37906
38043
 
37907
38044
  // src/commands/doctor.ts
37908
- import path43 from "path";
38045
+ import path44 from "path";
37909
38046
  async function runDoctorCommand(ctx) {
37910
38047
  const { root, config } = await requireWorkspace(ctx);
37911
38048
  const report = await runDoctor(root);
@@ -37930,7 +38067,7 @@ async function runDoctorCommand(ctx) {
37930
38067
  lines.push("Sin hallazgos. El workspace est\xE1 sano.");
37931
38068
  } else {
37932
38069
  for (const finding of report.findings) {
37933
- const location = finding.path ? ` ${path43.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
38070
+ const location = finding.path ? ` ${path44.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
37934
38071
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${location} \u2014 ${finding.message}`);
37935
38072
  if (finding.suggestion) lines.push(` \u21B3 ${finding.suggestion}`);
37936
38073
  }
@@ -38041,7 +38178,7 @@ async function runHelp(ctx, commandName) {
38041
38178
  }
38042
38179
 
38043
38180
  // src/commands/init.ts
38044
- import path44 from "path";
38181
+ import path45 from "path";
38045
38182
  async function runInit(ctx) {
38046
38183
  const languageFlag = flagString(ctx.flags, "language");
38047
38184
  const language = languageFlag === "en" ? "en" : languageFlag === "es" ? "es" : void 0;
@@ -38091,11 +38228,11 @@ async function runInit(ctx) {
38091
38228
  const lines = [msg("init.title", ctx.language), ""];
38092
38229
  if (result.created.length > 0) {
38093
38230
  lines.push(msg("init.done", ctx.language));
38094
- for (const file of result.created) lines.push(` + ${path44.relative(ctx.cwd, file)}`);
38231
+ for (const file of result.created) lines.push(` + ${path45.relative(ctx.cwd, file)}`);
38095
38232
  if (compiled.length > 0) {
38096
38233
  lines.push("");
38097
38234
  lines.push("Adaptadores compilados:");
38098
- for (const file of compiled) lines.push(` + ${path44.relative(ctx.cwd, path44.join(ctx.cwd, file))}`);
38235
+ for (const file of compiled) lines.push(` + ${path45.relative(ctx.cwd, path45.join(ctx.cwd, file))}`);
38099
38236
  }
38100
38237
  lines.push("");
38101
38238
  lines.push("Siguiente: crea un cambio con `satlas new <slug>` y escribe la spec funcional.");
@@ -38105,8 +38242,8 @@ async function runInit(ctx) {
38105
38242
  exitCode: hasErrors2 ? 1 : 0,
38106
38243
  diagnostics: result.diagnostics,
38107
38244
  data: {
38108
- sddDir: path44.relative(ctx.cwd, result.sddDir),
38109
- created: result.created.map((f) => path44.relative(ctx.cwd, f)),
38245
+ sddDir: path45.relative(ctx.cwd, result.sddDir),
38246
+ created: result.created.map((f) => path45.relative(ctx.cwd, f)),
38110
38247
  detected: result.detected?.matches.map((m) => ({ name: m.name, score: m.score })),
38111
38248
  best: result.detected?.best?.name
38112
38249
  },
@@ -38147,7 +38284,7 @@ async function runMetrics(ctx) {
38147
38284
  }
38148
38285
 
38149
38286
  // src/commands/mockup.ts
38150
- import path45 from "path";
38287
+ import path46 from "path";
38151
38288
  async function runMockup(ctx) {
38152
38289
  const { root, workspace, config } = await requireWorkspace(ctx);
38153
38290
  const slug = ctx.positionals[0];
@@ -38197,17 +38334,17 @@ async function runMockup(ctx) {
38197
38334
  ` pantallas: ${plan.screens.length}`,
38198
38335
  ...plan.screens.map((s) => ` - ${s.id}: ${s.title} (${s.illustrates.length} escenario(s))`),
38199
38336
  "",
38200
- ` plan: ${path45.relative(ctx.cwd, planFile)}`,
38201
- ` manifiesto: ${path45.relative(ctx.cwd, manifestFile)}`,
38337
+ ` plan: ${path46.relative(ctx.cwd, planFile)}`,
38338
+ ` manifiesto: ${path46.relative(ctx.cwd, manifestFile)}`,
38202
38339
  "",
38203
38340
  `Siguiente: genera el HTML de cada pantalla (fase ${agentCommand("mockup", slug, config)}) y valida con \`satlas mockup ${slug} --check\`.`
38204
38341
  ];
38205
- return { exitCode: 0, diagnostics: [], data: { plan, planFile: path45.relative(ctx.cwd, planFile), manifestFile: path45.relative(ctx.cwd, manifestFile) }, text: lines };
38342
+ return { exitCode: 0, diagnostics: [], data: { plan, planFile: path46.relative(ctx.cwd, planFile), manifestFile: path46.relative(ctx.cwd, manifestFile) }, text: lines };
38206
38343
  }
38207
38344
 
38208
38345
  // src/mcp/server.ts
38209
38346
  import { promises as fs3 } from "fs";
38210
- import path50 from "path";
38347
+ import path51 from "path";
38211
38348
  import { createInterface } from "readline/promises";
38212
38349
 
38213
38350
  // src/mcp/protocol.ts
@@ -38269,7 +38406,7 @@ function stringArg(args, key) {
38269
38406
  }
38270
38407
 
38271
38408
  // src/mcp/tools/contracts.ts
38272
- import path46 from "path";
38409
+ import path47 from "path";
38273
38410
  async function runAtlasContracts(args, host) {
38274
38411
  const resolved = await host.getWorkspace();
38275
38412
  if (!resolved) return noWorkspaceResult();
@@ -38289,7 +38426,7 @@ async function runAtlasContracts(args, host) {
38289
38426
  slug,
38290
38427
  mode,
38291
38428
  contracts: state.files.map((file) => ({
38292
- path: path46.relative(resolved.root, file.path),
38429
+ path: path47.relative(resolved.root, file.path),
38293
38430
  format: file.format,
38294
38431
  operations: file.operations.map((operation) => operation.id)
38295
38432
  })),
@@ -38325,12 +38462,12 @@ async function runAtlasFixes(_args, host) {
38325
38462
  }
38326
38463
 
38327
38464
  // src/mcp/tools/glossary.ts
38328
- import path47 from "path";
38465
+ import path48 from "path";
38329
38466
  async function runAtlasGlossary(_args, host) {
38330
38467
  const resolved = await host.getWorkspace();
38331
38468
  if (!resolved) return noWorkspaceResult();
38332
38469
  const { root, config } = resolved;
38333
- const glossaryPath = path47.resolve(root, config.spec.glossary);
38470
+ const glossaryPath = path48.resolve(root, config.spec.glossary);
38334
38471
  const raw = await readTextIfExists(glossaryPath);
38335
38472
  if (raw === void 0) {
38336
38473
  return jsonResult({
@@ -38351,7 +38488,7 @@ async function runAtlasGlossary(_args, host) {
38351
38488
  }
38352
38489
 
38353
38490
  // src/mcp/tools/links.ts
38354
- import path48 from "path";
38491
+ import path49 from "path";
38355
38492
  async function runAtlasLinks(_args, host) {
38356
38493
  const resolved = await host.getWorkspace();
38357
38494
  if (!resolved) return noWorkspaceResult();
@@ -38362,7 +38499,7 @@ async function runAtlasLinks(_args, host) {
38362
38499
  return jsonResult({
38363
38500
  links: state.entries.map((entry) => ({
38364
38501
  name: entry.name,
38365
- path: path48.relative(resolved.root, entry.path),
38502
+ path: path49.relative(resolved.root, entry.path),
38366
38503
  available: entry.available,
38367
38504
  requirements: entry.requirements,
38368
38505
  domains: entry.domains
@@ -38399,7 +38536,7 @@ async function runAtlasImpact(args, host) {
38399
38536
  }
38400
38537
 
38401
38538
  // src/evaluate.ts
38402
- import path49 from "path";
38539
+ import path50 from "path";
38403
38540
  function livingRequirementsMap2(specs) {
38404
38541
  const map = /* @__PURE__ */ new Map();
38405
38542
  for (const spec of specs) {
@@ -38408,7 +38545,7 @@ function livingRequirementsMap2(specs) {
38408
38545
  return map;
38409
38546
  }
38410
38547
  async function evaluateChange(workspace, config, change, approvals) {
38411
- const deltaPath = path49.join(change.dir, "spec.md");
38548
+ const deltaPath = path50.join(change.dir, "spec.md");
38412
38549
  const deltaContent = await readTextIfExists(deltaPath);
38413
38550
  const approval = verifyApproval(change, approvals, config, deltaContent ?? void 0);
38414
38551
  const living = livingRequirementsMap2(workspace.specs);
@@ -38704,8 +38841,8 @@ var WorkspaceCache = class {
38704
38841
  }
38705
38842
  };
38706
38843
  async function workspaceStamp(root) {
38707
- const sddDir = path50.join(root, ".sdd");
38708
- const markers = [sddDir, path50.join(sddDir, "config.yaml"), path50.join(sddDir, "changes"), path50.join(sddDir, "specs"), path50.join(sddDir, "approvals.yaml")];
38844
+ const sddDir = path51.join(root, ".sdd");
38845
+ const markers = [sddDir, path51.join(sddDir, "config.yaml"), path51.join(sddDir, "changes"), path51.join(sddDir, "specs"), path51.join(sddDir, "approvals.yaml")];
38709
38846
  let stamp2 = 0;
38710
38847
  for (const marker of markers) {
38711
38848
  try {
@@ -38723,7 +38860,7 @@ async function resolveMcpWorkspace(cwd, cache) {
38723
38860
  const cached2 = cache.get(root);
38724
38861
  if (cached2 && cached2.mtimeMs === stamp2) return cached2.value;
38725
38862
  const { workspace, config } = await loadWorkspace(root);
38726
- const approvals = await loadApprovals(path50.join(root, ".sdd"));
38863
+ const approvals = await loadApprovals(path51.join(root, ".sdd"));
38727
38864
  const value = { root, workspace, config, approvals: approvals.byArtifact };
38728
38865
  cache.set(root, stamp2, value);
38729
38866
  return value;
@@ -38815,7 +38952,7 @@ async function runMcp(ctx) {
38815
38952
  }
38816
38953
 
38817
38954
  // src/commands/packs.ts
38818
- import path51 from "path";
38955
+ import path52 from "path";
38819
38956
  async function runPacks(ctx) {
38820
38957
  const { root, config, workspace } = await requireWorkspace(ctx);
38821
38958
  const slug = flagString(ctx.flags, "check");
@@ -38873,14 +39010,14 @@ async function runPacks(ctx) {
38873
39010
  data: {
38874
39011
  slug,
38875
39012
  packs: evaluations.map((evaluation) => ({ id: evaluation.pack.id, status: evaluation.status, passed: evaluation.passed, failed: evaluation.failed, results: evaluation.results })),
38876
- analyzePath: path51.posix.join(".sdd", "changes", slug, "analyze.md")
39013
+ analyzePath: path52.posix.join(".sdd", "changes", slug, "analyze.md")
38877
39014
  },
38878
39015
  text: lines
38879
39016
  };
38880
39017
  }
38881
39018
 
38882
39019
  // src/commands/pause.ts
38883
- import path52 from "path";
39020
+ import path53 from "path";
38884
39021
  async function runPause(ctx) {
38885
39022
  const slug = ctx.positionals[0];
38886
39023
  if (!slug) {
@@ -38910,7 +39047,7 @@ async function runPause(ctx) {
38910
39047
  ` motivo: ${result.paused?.reason ?? ""}`,
38911
39048
  ` por: ${result.paused?.by ?? ""}`,
38912
39049
  ` fecha: ${result.paused?.at ?? ""}`,
38913
- ` estado: ${path52.relative(ctx.cwd, result.path)}`,
39050
+ ` estado: ${path53.relative(ctx.cwd, result.path)}`,
38914
39051
  "",
38915
39052
  `Ret\xF3malo cuando quieras con \`satlas resume ${slug}\`.`
38916
39053
  ];
@@ -38953,7 +39090,7 @@ async function runResume(ctx) {
38953
39090
  }
38954
39091
 
38955
39092
  // src/commands/new.ts
38956
- import path53 from "path";
39093
+ import path54 from "path";
38957
39094
  async function runNew(ctx) {
38958
39095
  const slug = ctx.positionals[0];
38959
39096
  if (!slug) {
@@ -38979,14 +39116,14 @@ async function runNew(ctx) {
38979
39116
  msg("new.title", ctx.language),
38980
39117
  "",
38981
39118
  msg("new.done", ctx.language),
38982
- ...result.files.map((f) => ` + ${path53.relative(ctx.cwd, f)}`),
39119
+ ...result.files.map((f) => ` + ${path54.relative(ctx.cwd, f)}`),
38983
39120
  "",
38984
39121
  `Siguiente: ${agentCommand("specify", result.slug, config)} \u2014 escribe la especificaci\xF3n 100% funcional y de negocio.`
38985
39122
  ];
38986
39123
  return {
38987
39124
  exitCode: hasErrors2 ? 1 : 0,
38988
39125
  diagnostics: result.diagnostics,
38989
- data: { slug: result.slug, files: result.files.map((f) => path53.relative(ctx.cwd, f)) },
39126
+ data: { slug: result.slug, files: result.files.map((f) => path54.relative(ctx.cwd, f)) },
38990
39127
  text: lines
38991
39128
  };
38992
39129
  }
@@ -39119,7 +39256,7 @@ async function runNext(ctx) {
39119
39256
  }
39120
39257
 
39121
39258
  // src/commands/present.ts
39122
- import path54 from "path";
39259
+ import path55 from "path";
39123
39260
  async function runPresentCommand(ctx) {
39124
39261
  const { root } = await requireWorkspace(ctx);
39125
39262
  const slug = ctx.positionals[0];
@@ -39131,7 +39268,7 @@ async function runPresentCommand(ctx) {
39131
39268
  if (result.path) {
39132
39269
  lines.push("Propuesta generada");
39133
39270
  lines.push("");
39134
- lines.push(` archivo: ${path54.relative(ctx.cwd, result.path)}`);
39271
+ lines.push(` archivo: ${path55.relative(ctx.cwd, result.path)}`);
39135
39272
  if (result.hash) lines.push(` hash de la spec: ${shortHash(result.hash)}`);
39136
39273
  lines.push("");
39137
39274
  lines.push("\xC1brela en el navegador y comp\xE1rtela con el stakeholder.");
@@ -39141,13 +39278,13 @@ async function runPresentCommand(ctx) {
39141
39278
  return {
39142
39279
  exitCode: hasErrors2 ? 1 : 0,
39143
39280
  diagnostics: result.diagnostics,
39144
- data: result.path ? { path: path54.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
39281
+ data: result.path ? { path: path55.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
39145
39282
  text: lines
39146
39283
  };
39147
39284
  }
39148
39285
 
39149
39286
  // src/commands/review.ts
39150
- import path55 from "path";
39287
+ import path56 from "path";
39151
39288
  async function runReview(ctx) {
39152
39289
  const slug = ctx.positionals[0];
39153
39290
  if (!slug) {
@@ -39159,7 +39296,7 @@ async function runReview(ctx) {
39159
39296
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-REVIEW-000", severity: "error", message: `No existe el cambio "${slug}"` }] };
39160
39297
  }
39161
39298
  const mode = config.gates.review.mode;
39162
- const reviewFile = path55.join(change.dir, "review.md");
39299
+ const reviewFile = path56.join(change.dir, "review.md");
39163
39300
  if (!await exists(reviewFile)) {
39164
39301
  const templates = templatesFor(config.project.language);
39165
39302
  await writeText(reviewFile, renderTemplate(templates.review, { TITLE: change.meta?.title ?? slug, SLUG: slug }));
@@ -39167,7 +39304,7 @@ async function runReview(ctx) {
39167
39304
  "Revisi\xF3n del cambio",
39168
39305
  "",
39169
39306
  ` modo: ${mode}`,
39170
- ` creado: ${path55.relative(ctx.cwd, reviewFile)}`,
39307
+ ` creado: ${path56.relative(ctx.cwd, reviewFile)}`,
39171
39308
  "",
39172
39309
  `Revisa el c\xF3digo con la fase del agente: ${agentCommand("review", slug, config)}`,
39173
39310
  "Al cerrar, deja `- resultado: pass` en el veredicto y marca los hallazgos resueltos."
@@ -39180,7 +39317,7 @@ async function runReview(ctx) {
39180
39317
  const passed = reviewPassed(review);
39181
39318
  const lines = ["Revisi\xF3n del cambio", ""];
39182
39319
  lines.push(` modo: ${mode}`);
39183
- lines.push(` archivo: ${path55.relative(ctx.cwd, reviewFile)}`);
39320
+ lines.push(` archivo: ${path56.relative(ctx.cwd, reviewFile)}`);
39184
39321
  lines.push(` resultado: ${review?.verdict ?? "pendiente"}${review?.by ? ` \xB7 por ${review.by}` : ""}${review?.date ? ` \xB7 ${review.date}` : ""}`);
39185
39322
  lines.push(` hallazgos: ${review?.findings.length ?? 0} (${open.length} sin resolver, ${blocking.length} bloqueante(s))`);
39186
39323
  if (open.length > 0) {
@@ -39282,6 +39419,124 @@ function usage(usageText) {
39282
39419
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-RUN-000", severity: "error", message: `Uso: ${usageText}` }] };
39283
39420
  }
39284
39421
 
39422
+ // src/commands/self-update.ts
39423
+ function rutaEjecutable() {
39424
+ return process.argv[1] ?? process.execPath;
39425
+ }
39426
+ async function instalarUltima(instalacion, cwd) {
39427
+ if (!instalacion.comando) return { ok: false, salida: instalacion.motivo ?? "no se puede actualizar esta instalaci\xF3n" };
39428
+ const resultado = await runProcess(instalacion.comando, { cwd, timeoutMs: 18e4 });
39429
+ return { ok: resultado.ok, salida: `${resultado.stdout}${resultado.stderr}`.trim() };
39430
+ }
39431
+ async function runSelfUpdate(ctx) {
39432
+ const instalada = cliVersion();
39433
+ const soloConsultar = flagBool(ctx.flags, "check");
39434
+ const auto = flagString(ctx.flags, "auto");
39435
+ if (auto !== void 0) {
39436
+ const valor = auto.toLowerCase();
39437
+ if (valor !== "on" && valor !== "off") {
39438
+ return {
39439
+ exitCode: 2,
39440
+ diagnostics: [{ code: "ATLAS-SELFUPDATE-000", severity: "error", message: "Valor inv\xE1lido para --auto", suggestion: "satlas self-update --auto on | off" }]
39441
+ };
39442
+ }
39443
+ const preferencias2 = await leerPreferencias();
39444
+ const nuevas = { ...preferencias2, automatica: valor === "on" };
39445
+ const file = await escribirPreferencias(nuevas);
39446
+ return {
39447
+ exitCode: 0,
39448
+ diagnostics: [],
39449
+ data: { automatica: nuevas.automatica, archivo: file },
39450
+ text: [
39451
+ "Actualizaci\xF3n desatendida",
39452
+ "",
39453
+ ` ${nuevas.automatica ? "activada: la herramienta se actualizar\xE1 sola al detectar una versi\xF3n nueva" : "desactivada: solo se avisa cuando hay versi\xF3n nueva"}`,
39454
+ ` preferencia: ${file}`
39455
+ ]
39456
+ };
39457
+ }
39458
+ const publicada = await consultarVersionPublicada();
39459
+ const instalacion = detectarInstalacion(rutaEjecutable());
39460
+ const preferencias = await leerPreferencias();
39461
+ if (publicada === void 0) {
39462
+ return {
39463
+ exitCode: 1,
39464
+ diagnostics: [
39465
+ {
39466
+ code: "ATLAS-SELFUPDATE-001",
39467
+ severity: "error",
39468
+ message: "No se pudo consultar la versi\xF3n publicada",
39469
+ suggestion: `Comprueba tu conexi\xF3n o instala a mano: ${instalacion.comando ?? "npm install -g specatlas@latest"}`
39470
+ }
39471
+ ]
39472
+ };
39473
+ }
39474
+ const base = [
39475
+ "Actualizaci\xF3n de SpecAtlas",
39476
+ "",
39477
+ ` instalada: ${instalada}`,
39478
+ ` publicada: ${publicada}`,
39479
+ ` instalada con: ${instalacion.gestor}`,
39480
+ ` desatendida: ${preferencias.automatica ? "activada" : "desactivada"}`
39481
+ ];
39482
+ if (!hayVersionNueva(instalada, publicada)) {
39483
+ return { exitCode: 0, diagnostics: [], data: { instalada, publicada, actualizada: false, alDia: true }, text: [...base, "", " Ya est\xE1s en la \xFAltima versi\xF3n."] };
39484
+ }
39485
+ if (soloConsultar) {
39486
+ return {
39487
+ exitCode: 0,
39488
+ diagnostics: [],
39489
+ data: { instalada, publicada, actualizada: false, alDia: false, comando: instalacion.comando },
39490
+ text: [...base, "", ` Hay una versi\xF3n nueva. Actualiza con \`satlas self-update\`${instalacion.comando ? ` (o \`${instalacion.comando}\`)` : ""}.`]
39491
+ };
39492
+ }
39493
+ if (!instalacion.comando) {
39494
+ return {
39495
+ exitCode: 0,
39496
+ diagnostics: [{ code: "ATLAS-SELFUPDATE-002", severity: "info", message: instalacion.motivo ?? "esta instalaci\xF3n no se actualiza sola" }],
39497
+ data: { instalada, publicada, actualizada: false, motivo: instalacion.motivo },
39498
+ text: [...base, "", ` ${instalacion.motivo}`]
39499
+ };
39500
+ }
39501
+ const resultado = await instalarUltima(instalacion, ctx.cwd);
39502
+ if (!resultado.ok) {
39503
+ return {
39504
+ exitCode: 1,
39505
+ diagnostics: [
39506
+ {
39507
+ code: "ATLAS-SELFUPDATE-003",
39508
+ severity: "error",
39509
+ message: `No se pudo instalar ${publicada}: ${resultado.salida.split("\n")[0] ?? "el gestor fall\xF3"}`,
39510
+ suggestion: `Ejec\xFAtalo a mano: ${instalacion.comando}`
39511
+ }
39512
+ ],
39513
+ data: { instalada, publicada, actualizada: false, comando: instalacion.comando },
39514
+ text: [...base, "", ` La instalaci\xF3n fall\xF3; sigues con ${instalada}.`]
39515
+ };
39516
+ }
39517
+ return {
39518
+ exitCode: 0,
39519
+ diagnostics: [],
39520
+ data: { instalada, publicada, actualizada: true, comando: instalacion.comando },
39521
+ text: [...base, "", ` Actualizada: ${instalada} \u2192 ${publicada}.`, " Abre una terminal nueva si el comando sigue apuntando a la versi\xF3n anterior."]
39522
+ };
39523
+ }
39524
+ async function avisoDeActualizacion(json2, cwd) {
39525
+ try {
39526
+ const aviso = await comprobarActualizacion({ instalada: cliVersion(), rutaEjecutable: rutaEjecutable(), json: json2 });
39527
+ if (!aviso) return [];
39528
+ const preferencias = await leerPreferencias();
39529
+ if (!preferencias.automatica) {
39530
+ return ["", `Hay una versi\xF3n nueva de SpecAtlas: ${aviso.instalada} \u2192 ${aviso.publicada}. Actualiza con \`satlas self-update\`.`];
39531
+ }
39532
+ const instalacion = detectarInstalacion(rutaEjecutable());
39533
+ const resultado = await instalarUltima(instalacion, cwd);
39534
+ return resultado.ok ? ["", `SpecAtlas se actualiz\xF3 solo: ${aviso.instalada} \u2192 ${aviso.publicada}.`] : ["", `Hay una versi\xF3n nueva (${aviso.publicada}) y la actualizaci\xF3n autom\xE1tica fall\xF3. Ejecuta \`${aviso.comando}\`.`];
39535
+ } catch {
39536
+ return [];
39537
+ }
39538
+ }
39539
+
39285
39540
  // src/commands/status.ts
39286
39541
  async function runStatus(ctx) {
39287
39542
  const { root, workspace, config, approvals } = await requireWorkspace(ctx);
@@ -39356,7 +39611,7 @@ async function runStatus(ctx) {
39356
39611
  }
39357
39612
 
39358
39613
  // src/commands/trace.ts
39359
- import path56 from "path";
39614
+ import path57 from "path";
39360
39615
  async function runTrace(ctx) {
39361
39616
  const { workspace, config } = await requireWorkspace(ctx);
39362
39617
  const slug = flagString(ctx.flags, "change");
@@ -39376,7 +39631,7 @@ async function runTrace(ctx) {
39376
39631
  data.push({ slug: change.slug, nodes: result.graph.nodes.length, edges: result.graph.edges.length, summary: result.summary, findings: result.findings });
39377
39632
  lines.push(` ${change.slug}: ${result.graph.nodes.length} nodos, ${result.graph.edges.length} aristas, ${errors} errores, ${result.summary.warnings} avisos`);
39378
39633
  for (const finding of result.findings) {
39379
- const location = finding.path ? ` (${path56.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
39634
+ const location = finding.path ? ` (${path57.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
39380
39635
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}${location}`);
39381
39636
  }
39382
39637
  }
@@ -39386,7 +39641,7 @@ async function runTrace(ctx) {
39386
39641
  }
39387
39642
 
39388
39643
  // src/commands/upgrade.ts
39389
- import path57 from "path";
39644
+ import path58 from "path";
39390
39645
  async function runUpgrade(ctx) {
39391
39646
  const { root } = await requireWorkspace(ctx);
39392
39647
  const apply2 = flagBool(ctx.flags, "apply");
@@ -39440,7 +39695,7 @@ function renderPreview(plan, root, ctx) {
39440
39695
  }
39441
39696
  for (const item of plan.unreadable) {
39442
39697
  lines.push(` Ilegible: "${item.artifact}" \u2014 ${item.reason}`);
39443
- lines.push(` \u21B3 ${path57.relative(ctx.cwd, item.path)}`);
39698
+ lines.push(` \u21B3 ${path58.relative(ctx.cwd, item.path)}`);
39444
39699
  }
39445
39700
  return { exitCode: 0, diagnostics: [], data: summarizable(plan), text: lines };
39446
39701
  }
@@ -39503,7 +39758,7 @@ function renderRollback(report, ctx) {
39503
39758
  }
39504
39759
 
39505
39760
  // src/commands/validate.ts
39506
- import path58 from "path";
39761
+ import path59 from "path";
39507
39762
  async function runValidate(ctx) {
39508
39763
  const { root, workspace, config, approvals } = await requireWorkspace(ctx);
39509
39764
  const slug = flagString(ctx.flags, "change");
@@ -39530,7 +39785,7 @@ async function runValidate(ctx) {
39530
39785
  for (const finding of evaluation.lintFindings) {
39531
39786
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${finding.line ? `:${finding.line}` : ""} \u2014 ${finding.message}`);
39532
39787
  }
39533
- lines.push(` archivo: ${path58.relative(ctx.cwd, path58.join(change.dir, "spec.md"))}`);
39788
+ lines.push(` archivo: ${path59.relative(ctx.cwd, path59.join(change.dir, "spec.md"))}`);
39534
39789
  }
39535
39790
  if (targets.length === 0) lines.push("Sin cambios activos.");
39536
39791
  if (advisory && advisory.plan.pending.length > 0) {
@@ -39558,7 +39813,7 @@ async function runValidate(ctx) {
39558
39813
  }
39559
39814
 
39560
39815
  // src/commands/verify.ts
39561
- import path59 from "path";
39816
+ import path60 from "path";
39562
39817
  async function runVerify(ctx) {
39563
39818
  const { root, workspace, config } = await requireWorkspace(ctx);
39564
39819
  const slug = ctx.positionals[0];
@@ -39601,7 +39856,7 @@ async function runVerify(ctx) {
39601
39856
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-VERIFY-001", severity: "error", message: "Falta --by <nombre> (o define meta.owner)" }] };
39602
39857
  }
39603
39858
  const profilesDir = await resolveProfilesDir();
39604
- const profile = await loadActiveProfile(path59.join(root, ".sdd"), [profilesDir ?? "", path59.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
39859
+ const profile = await loadActiveProfile(path60.join(root, ".sdd"), [profilesDir ?? "", path60.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
39605
39860
  const record = await recordEvidence({
39606
39861
  root,
39607
39862
  slug,
@@ -39623,7 +39878,7 @@ async function runVerify(ctx) {
39623
39878
  if (record.evidence.command) lines.push(` comando: ${record.evidence.command}`);
39624
39879
  lines.push(` resultado: ${record.evidence.result}`);
39625
39880
  if (record.evidence.outputHash) lines.push(` hash: ${record.evidence.outputHash}`);
39626
- lines.push(` archivo: ${path59.relative(ctx.cwd, record.path)}`);
39881
+ lines.push(` archivo: ${path60.relative(ctx.cwd, record.path)}`);
39627
39882
  if (record.stdout) {
39628
39883
  lines.push("");
39629
39884
  lines.push(" salida (\xFAltimas l\xEDneas):");
@@ -39651,14 +39906,14 @@ async function runVerify(ctx) {
39651
39906
  return {
39652
39907
  exitCode: record.exitCode === 0 ? 0 : 1,
39653
39908
  diagnostics: [...record.diagnostics, ...mockupDiagnostics],
39654
- data: { evidence: record.evidence, path: path59.relative(ctx.cwd, record.path), exitCode: record.exitCode },
39909
+ data: { evidence: record.evidence, path: path60.relative(ctx.cwd, record.path), exitCode: record.exitCode },
39655
39910
  text: lines
39656
39911
  };
39657
39912
  }
39658
39913
 
39659
39914
  // src/commands/watch.ts
39660
39915
  import { watch } from "fs";
39661
- import path60 from "path";
39916
+ import path61 from "path";
39662
39917
  var DEBOUNCE_MS = 250;
39663
39918
  function stamp(now) {
39664
39919
  return now.toTimeString().slice(0, 8);
@@ -39672,7 +39927,7 @@ async function runWatch(ctx) {
39672
39927
  diagnostics: [{ code: "ATLAS-WATCH-001", severity: "error", message: "`satlas watch` es interactivo y no admite --json", suggestion: "Usa `satlas ci --json` en una tuber\xEDa" }]
39673
39928
  };
39674
39929
  }
39675
- const sddDir = path60.join(root, ".sdd");
39930
+ const sddDir = path61.join(root, ".sdd");
39676
39931
  const write = (line) => {
39677
39932
  process.stdout.write(`${line}
39678
39933
  `);
@@ -39690,7 +39945,7 @@ async function runWatch(ctx) {
39690
39945
  } else {
39691
39946
  write(` ${counts.errors} errores, ${counts.warnings} avisos`);
39692
39947
  for (const finding of findings.slice(0, 8)) {
39693
- const where = finding.path ? ` ${path60.relative(root, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
39948
+ const where = finding.path ? ` ${path61.relative(root, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
39694
39949
  write(` ${finding.severity === "error" ? "ERROR" : "AVISO"} ${finding.code}${where} \u2014 ${finding.message}`);
39695
39950
  }
39696
39951
  if (findings.length > 8) write(` \u2026 y ${findings.length - 8} m\xE1s (satlas validate)`);
@@ -39701,7 +39956,7 @@ async function runWatch(ctx) {
39701
39956
  }
39702
39957
  if (changes.length === 0) write(" sin cambios activos");
39703
39958
  };
39704
- write(`Vigilando ${path60.relative(ctx.cwd, sddDir) || ".sdd"} \u2014 Ctrl+C para salir`);
39959
+ write(`Vigilando ${path61.relative(ctx.cwd, sddDir) || ".sdd"} \u2014 Ctrl+C para salir`);
39705
39960
  await pass();
39706
39961
  await new Promise((resolve) => {
39707
39962
  let timer;
@@ -39805,6 +40060,7 @@ var HANDLERS2 = {
39805
40060
  pause: runPause,
39806
40061
  resume: runResume,
39807
40062
  archive: runArchive,
40063
+ "self-update": runSelfUpdate,
39808
40064
  version: async () => ({ exitCode: 0, data: { version: cliVersion() }, diagnostics: [], text: [`specatlas ${cliVersion()}`] }),
39809
40065
  help: (ctx) => runHelp(ctx)
39810
40066
  };
@@ -39834,7 +40090,7 @@ async function requireWorkspace(ctx) {
39834
40090
  throw new CliError("ATLAS-WS-001", msg("cli.noWorkspace", ctx.language), 2);
39835
40091
  }
39836
40092
  const { workspace, config } = await loadWorkspace(root);
39837
- const approvals = await loadApprovals(path61.join(root, ".sdd"));
40093
+ const approvals = await loadApprovals(path62.join(root, ".sdd"));
39838
40094
  return { root, workspace, config, approvals: approvals.byArtifact };
39839
40095
  }
39840
40096
  var CliError = class extends Error {
@@ -39885,6 +40141,11 @@ async function main(argv) {
39885
40141
  try {
39886
40142
  const result = await command.handler(ctx);
39887
40143
  printResult(parsed.command, result, json2, ctx.language);
40144
+ if (parsed.command !== "self-update" && parsed.command !== "mcp") {
40145
+ const aviso = await avisoDeActualizacion(json2, ctx.cwd);
40146
+ if (aviso.length > 0) process.stdout.write(`${aviso.join("\n")}
40147
+ `);
40148
+ }
39888
40149
  return result.exitCode;
39889
40150
  } catch (err) {
39890
40151
  if (err instanceof CliError) {
@@ -39903,7 +40164,7 @@ function printResult(command, result, json2, language) {
39903
40164
  }
39904
40165
  const lines = [...result.text ?? []];
39905
40166
  for (const d of result.diagnostics) {
39906
- const location = d.path ? ` ${path61.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
40167
+ const location = d.path ? ` ${path62.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
39907
40168
  lines.push(`${d.severity === "error" ? "ERROR" : d.severity === "warning" ? "AVISO" : "NOTA"} ${d.code}${location} \u2014 ${d.message}`);
39908
40169
  if (d.suggestion) lines.push(` \u21B3 ${d.suggestion}`);
39909
40170
  }