specatlas 0.1.25 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -6,13 +6,14 @@ import {
6
6
  localCompact,
7
7
  localDate,
8
8
  localMonth,
9
+ localOffset,
9
10
  localStamp,
10
11
  parseApprovals,
11
12
  parseChangeMeta,
12
13
  sha256,
13
14
  shortHash,
14
15
  signApproval
15
- } from "./chunk-HYN75DTZ.js";
16
+ } from "./chunk-L2BWIT44.js";
16
17
  import {
17
18
  __commonJS,
18
19
  __toESM,
@@ -1585,7 +1586,7 @@ var require_core = __commonJS({
1585
1586
  });
1586
1587
 
1587
1588
  // src/cli.ts
1588
- import path41 from "path";
1589
+ import path46 from "path";
1589
1590
 
1590
1591
  // ../core/dist/index.js
1591
1592
  import path from "path";
@@ -1598,23 +1599,25 @@ import path2 from "path";
1598
1599
  import path3 from "path";
1599
1600
  import { parse as parseYaml4 } from "yaml";
1600
1601
  import { z as z3 } from "zod";
1602
+ import path5 from "path";
1601
1603
  import path4 from "path";
1602
1604
  import { stringify as stringifyYaml3 } from "yaml";
1603
- import path5 from "path";
1605
+ import { stringify as stringifyYaml4 } from "yaml";
1606
+ import path6 from "path";
1604
1607
  import { parse as parseYaml5 } from "yaml";
1605
1608
  import { execFile } from "child_process";
1606
- import path6 from "path";
1607
1609
  import path7 from "path";
1608
- import { randomBytes } from "crypto";
1609
- import path10 from "path";
1610
1610
  import path8 from "path";
1611
- import { parse as parseYaml6, stringify as stringifyYaml4 } from "yaml";
1612
- import { z as z4 } from "zod";
1611
+ import { randomBytes } from "crypto";
1612
+ import path11 from "path";
1613
1613
  import path9 from "path";
1614
+ import { parse as parseYaml6, stringify as stringifyYaml5 } from "yaml";
1615
+ import { z as z4 } from "zod";
1616
+ import path10 from "path";
1614
1617
  import { parse as parseYaml7 } from "yaml";
1615
1618
  import { z as z5 } from "zod";
1616
- import path11 from "path";
1617
1619
  import path12 from "path";
1620
+ import path13 from "path";
1618
1621
 
1619
1622
  // ../../node_modules/.pnpm/highlight.js@11.12.0/node_modules/highlight.js/es/core.js
1620
1623
  var import_core = __toESM(require_core(), 1);
@@ -9019,14 +9022,18 @@ function renderMarkdown(markdown2, opts = {}) {
9019
9022
  }
9020
9023
 
9021
9024
  // ../core/dist/index.js
9022
- import path13 from "path";
9023
9025
  import path14 from "path";
9024
9026
  import path15 from "path";
9027
+ import path16 from "path";
9025
9028
  import { promises as fs } from "fs";
9026
9029
  import { cp, rename, rm } from "fs/promises";
9027
- import path16 from "path";
9028
9030
  import path17 from "path";
9031
+ import { promises as fs2 } from "fs";
9029
9032
  import path18 from "path";
9033
+ import { parse as parseYaml8, stringify as stringifyYaml6 } from "yaml";
9034
+ import path19 from "path";
9035
+ import path20 from "path";
9036
+ import path21 from "path";
9030
9037
  var CORE_VERSION = "0.0.1";
9031
9038
  var REQ_ID_RE = /^REQ-[A-Z0-9]+(?:-[A-Z0-9]+)*-\d{3}$/;
9032
9039
  var SCENARIO_ID_RE = /^REQ-[A-Z0-9]+(?:-[A-Z0-9]+)*-\d{3}-S\d+$/;
@@ -9742,14 +9749,14 @@ function wordBoundary(text, term) {
9742
9749
  const re = new RegExp(`(?<![\\p{L}\\p{N}])${escaped}(?![\\p{L}\\p{N}])`, "iu");
9743
9750
  return re.test(text);
9744
9751
  }
9745
- function lintText(text, code, terms, label, path192, line) {
9752
+ function lintText(text, code, terms, label, path222, line) {
9746
9753
  const out = [];
9747
9754
  const lower = text.toLowerCase();
9748
9755
  for (const term of terms) {
9749
9756
  if (wordBoundary(lower, term)) {
9750
9757
  out.push(
9751
9758
  diag(code, "error", `${label}: "${term}"`, {
9752
- path: path192,
9759
+ path: path222,
9753
9760
  line,
9754
9761
  suggestion: "La especificaci\xF3n es funcional y de negocio: describe comportamiento, no tecnolog\xEDa ni adjetivos vagos"
9755
9762
  })
@@ -9758,7 +9765,7 @@ function lintText(text, code, terms, label, path192, line) {
9758
9765
  }
9759
9766
  return out;
9760
9767
  }
9761
- function lintRequirement(req, path192, opts = {}) {
9768
+ function lintRequirement(req, path222, opts = {}) {
9762
9769
  const out = [];
9763
9770
  const vague = opts.language === "en" ? VAGUE_EN : VAGUE_ES;
9764
9771
  const tech = opts.language === "en" ? TECH_EN : TECH_ES;
@@ -9769,32 +9776,32 @@ function lintRequirement(req, path192, opts = {}) {
9769
9776
  ...req.scenarios.flatMap((s) => [...s.when.map((w) => ({ text: w, line: s.line })), ...s.then.map((t) => ({ text: t, line: s.line }))])
9770
9777
  ];
9771
9778
  for (const part of parts) {
9772
- out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path192, part.line));
9779
+ out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path222, part.line));
9773
9780
  if (opts.businessOnly !== false) {
9774
- out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path192, part.line));
9781
+ out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path222, part.line));
9775
9782
  }
9776
9783
  }
9777
9784
  if (req.scenarios.length === 0) {
9778
- out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path192, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
9785
+ out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path222, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
9779
9786
  }
9780
9787
  return out;
9781
9788
  }
9782
- function lintDelta(delta, livingRequirements, path192, opts = {}) {
9789
+ function lintDelta(delta, livingRequirements, path222, opts = {}) {
9783
9790
  const out = [...delta.diagnostics];
9784
9791
  for (const req of [...delta.added, ...delta.modified]) {
9785
- out.push(...lintRequirement(req, path192, opts));
9792
+ out.push(...lintRequirement(req, path222, opts));
9786
9793
  }
9787
9794
  for (const req of delta.modified) {
9788
9795
  const living = livingRequirements.get(req.id);
9789
9796
  if (!living) {
9790
- out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path192, line: req.line }));
9797
+ out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path222, line: req.line }));
9791
9798
  continue;
9792
9799
  }
9793
9800
  for (const existing of living.scenarios) {
9794
9801
  if (!req.scenarios.some((s) => s.id === existing.id)) {
9795
9802
  out.push(
9796
9803
  diag("TRACE-007", "error", `MODIFIED ${req.id} pierde el escenario ${existing.id}: copia el bloque completo`, {
9797
- path: path192,
9804
+ path: path222,
9798
9805
  line: req.line,
9799
9806
  suggestion: "Copia el bloque completo de la spec viva y ed\xEDtalo; para quitarlo, decl\xE1ralo en REMOVED"
9800
9807
  })
@@ -9805,15 +9812,15 @@ function lintDelta(delta, livingRequirements, path192, opts = {}) {
9805
9812
  for (const req of delta.removed) {
9806
9813
  const living = livingRequirements.get(req.id);
9807
9814
  if (!living) {
9808
- out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path192, line: req.line }));
9815
+ out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path222, line: req.line }));
9809
9816
  }
9810
9817
  }
9811
9818
  for (const rename2 of delta.renamed) {
9812
9819
  if (rename2.from.id !== rename2.to.id) {
9813
- out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path192, line: rename2.line }));
9820
+ out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path222, line: rename2.line }));
9814
9821
  }
9815
9822
  if (!livingRequirements.has(rename2.from.id)) {
9816
- out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path192, line: rename2.line }));
9823
+ out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path222, line: rename2.line }));
9817
9824
  }
9818
9825
  }
9819
9826
  return out;
@@ -9837,7 +9844,7 @@ var MERMAID_KEYWORDS = [
9837
9844
  "architecture-beta"
9838
9845
  ];
9839
9846
  var MERMAID_BLOCKS = /^\s*(alt|loop|opt|par|rect|critical|break|subgraph)\b/;
9840
- function lintPlan(planText, path192) {
9847
+ function lintPlan(planText, path222) {
9841
9848
  const out = [];
9842
9849
  const blocks = [...planText.matchAll(/```mermaid\r?\n([\s\S]*?)```/g)];
9843
9850
  for (const [index, block] of blocks.entries()) {
@@ -9847,7 +9854,7 @@ function lintPlan(planText, path192) {
9847
9854
  if (!MERMAID_KEYWORDS.some((keyword) => first.startsWith(keyword))) {
9848
9855
  out.push(
9849
9856
  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)}"`, {
9850
- path: path192,
9857
+ path: path222,
9851
9858
  suggestion: "Corrige el tipo del diagrama o elimina el bloque"
9852
9859
  })
9853
9860
  );
@@ -9861,7 +9868,7 @@ function lintPlan(planText, path192) {
9861
9868
  if (open !== 0) {
9862
9869
  out.push(
9863
9870
  diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: faltan ${Math.abs(open)} \`end\` (bloques alt/loop/subgraph sin cerrar)`, {
9864
- path: path192,
9871
+ path: path222,
9865
9872
  suggestion: "Cierra cada bloque alt/loop/opt/par/rect/subgraph con `end`"
9866
9873
  })
9867
9874
  );
@@ -9873,7 +9880,7 @@ function lintPlan(planText, path192) {
9873
9880
  if (message.includes(";")) {
9874
9881
  out.push(
9875
9882
  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`, {
9876
- path: path192,
9883
+ path: path222,
9877
9884
  suggestion: "Sustituye `;` por `\xB7` o `,` en los mensajes de sequenceDiagram"
9878
9885
  })
9879
9886
  );
@@ -10028,6 +10035,16 @@ function checkTrace(input) {
10028
10035
  findings.push(diag("TRACE-006", "warning", `Evidencia hu\xE9rfana: ${ev.scenario} no existe`, { path: change.verify?.path, line: ev.line }));
10029
10036
  }
10030
10037
  }
10038
+ for (const cover of change.fixCovers ?? []) {
10039
+ if (!livingReqs.has(cover)) {
10040
+ findings.push(
10041
+ diag("TRACE-011", "warning", `El fix ${change.slug} declara ${cover}, que no existe en las specs vivas`, {
10042
+ path: change.fix?.path,
10043
+ suggestion: "Corrige el identificador o qu\xEDtalo: la declaraci\xF3n es opcional"
10044
+ })
10045
+ );
10046
+ }
10047
+ }
10031
10048
  const summary = countBySeverity(findings);
10032
10049
  return { graph: buildTraceGraph(input), findings, summary };
10033
10050
  }
@@ -10391,34 +10408,110 @@ function detectionToYaml(result, generatedAt) {
10391
10408
  }
10392
10409
  return lines.join("\n") + "\n";
10393
10410
  }
10411
+ var LIVING_FIXES_DIR = path4.join(".sdd", "fixes");
10412
+ var COMMENT_RE = /<!--[\s\S]*?-->/g;
10413
+ function parseFixCovers(content) {
10414
+ const clean = content.replace(COMMENT_RE, "");
10415
+ const covers = [];
10416
+ for (const line of clean.replace(/\r\n?/g, "\n").split("\n")) {
10417
+ const match = /^\s*-?\s*Cubre:\s*(.+?)\s*$/.exec(line);
10418
+ if (!match) continue;
10419
+ for (const raw of (match[1] ?? "").split(",")) {
10420
+ const id = raw.trim().toUpperCase();
10421
+ if (/^REQ-[A-Z0-9-]+$/.test(id) && !covers.includes(id)) covers.push(id);
10422
+ }
10423
+ }
10424
+ return covers;
10425
+ }
10426
+ function coversOf(value) {
10427
+ if (Array.isArray(value)) {
10428
+ return value.map((v) => String(v).trim().toUpperCase()).filter((v) => /^REQ-[A-Z0-9-]+$/.test(v));
10429
+ }
10430
+ if (typeof value === "string") {
10431
+ return value.split(",").map((v) => v.trim().toUpperCase()).filter((v) => /^REQ-[A-Z0-9-]+$/.test(v));
10432
+ }
10433
+ return [];
10434
+ }
10435
+ function parseLivingFix(content, file) {
10436
+ const fm = parseFrontmatter(content, file);
10437
+ const data = fm.data;
10438
+ const base = path4.basename(file).replace(/\.md$/i, "");
10439
+ const slug = typeof data["slug"] === "string" && data["slug"].trim() !== "" ? data["slug"].trim() : base.replace(/^\d{4}-\d{2}-/, "");
10440
+ const fix = {
10441
+ slug,
10442
+ file,
10443
+ date: typeof data["date"] === "string" ? data["date"] : "",
10444
+ result: typeof data["result"] === "string" ? data["result"] : "pass",
10445
+ covers: coversOf(data["covers"]),
10446
+ content: fm.body.trimStart()
10447
+ };
10448
+ if (typeof data["domain"] === "string") fix.domain = data["domain"];
10449
+ if (typeof data["title"] === "string") fix.title = data["title"];
10450
+ return fix;
10451
+ }
10452
+ async function loadLivingFixes(root) {
10453
+ const dir = path4.join(root, LIVING_FIXES_DIR);
10454
+ const entries = (await listDir(dir)).filter((entry) => entry.toLowerCase().endsWith(".md")).sort();
10455
+ const fixes = [];
10456
+ for (const entry of entries) {
10457
+ const file = path4.join(dir, entry);
10458
+ const content = await readTextIfExists(file);
10459
+ if (content === void 0) continue;
10460
+ fixes.push(parseLivingFix(content, file));
10461
+ }
10462
+ return fixes.sort((a, b) => b.date.localeCompare(a.date) || b.slug.localeCompare(a.slug));
10463
+ }
10464
+ async function writeLivingFix(root, input) {
10465
+ const dir = path4.join(root, LIVING_FIXES_DIR);
10466
+ const month = input.date.slice(0, 7);
10467
+ const file = path4.join(dir, `${month}-${input.slug}.md`);
10468
+ const relativePath = toPosix(path4.relative(root, file));
10469
+ if (await exists(file)) return { file, relativePath, created: false };
10470
+ const data = {
10471
+ slug: input.slug,
10472
+ date: input.date,
10473
+ result: input.result ?? "pass"
10474
+ };
10475
+ if (input.domain) data["domain"] = input.domain;
10476
+ if (input.title) data["title"] = input.title;
10477
+ if (input.covers && input.covers.length > 0) data["covers"] = input.covers;
10478
+ const body = input.content.replace(/^[\r\n]+/, "").trimEnd();
10479
+ const text = `---
10480
+ ${stringifyYaml3(data, { lineWidth: 120 })}---
10481
+
10482
+ ${body}
10483
+ `;
10484
+ await writeText(file, text);
10485
+ return { file, relativePath, created: true };
10486
+ }
10394
10487
  var SDD_DIR = ".sdd";
10395
10488
  async function findWorkspaceRoot(start) {
10396
- let current = path4.resolve(start);
10489
+ let current = path5.resolve(start);
10397
10490
  for (let i = 0; i < 40; i += 1) {
10398
- if (await isDirectory(path4.join(current, SDD_DIR))) return current;
10399
- const parent = path4.dirname(current);
10491
+ if (await isDirectory(path5.join(current, SDD_DIR))) return current;
10492
+ const parent = path5.dirname(current);
10400
10493
  if (parent === current) return void 0;
10401
10494
  current = parent;
10402
10495
  }
10403
10496
  return void 0;
10404
10497
  }
10405
10498
  async function loadApprovals(sddDir) {
10406
- const file = path4.join(sddDir, "approvals.yaml");
10499
+ const file = path5.join(sddDir, "approvals.yaml");
10407
10500
  const raw = await readTextIfExists(file);
10408
10501
  if (raw === void 0) return { byArtifact: /* @__PURE__ */ new Map(), diagnostics: [] };
10409
10502
  const parsed = parseApprovals(raw, file);
10410
10503
  const map = /* @__PURE__ */ new Map();
10411
10504
  for (const a of parsed.approvals?.approvals ?? []) {
10412
- map.set(a.artifact.split(path4.sep).join("/"), { hash: a.artifactHash, by: a.approvedBy, at: a.approvedAt });
10505
+ map.set(a.artifact.split(path5.sep).join("/"), { hash: a.artifactHash, by: a.approvedBy, at: a.approvedAt });
10413
10506
  }
10414
10507
  return { byArtifact: map, diagnostics: parsed.diagnostics };
10415
10508
  }
10416
10509
  async function loadSpecs(sddDir) {
10417
- const specsDir = path4.join(sddDir, "specs");
10510
+ const specsDir = path5.join(sddDir, "specs");
10418
10511
  const domains = await listDirs(specsDir);
10419
10512
  const out = [];
10420
10513
  for (const domain of domains) {
10421
- const file = path4.join(specsDir, domain, "spec.md");
10514
+ const file = path5.join(specsDir, domain, "spec.md");
10422
10515
  if (!await exists(file)) continue;
10423
10516
  const content = await readText(file);
10424
10517
  out.push({ domain, path: file, spec: parseSpecFile(content, file) });
@@ -10426,10 +10519,10 @@ async function loadSpecs(sddDir) {
10426
10519
  return out;
10427
10520
  }
10428
10521
  async function loadChange(root, slug, relDir) {
10429
- const dir = path4.join(root, SDD_DIR, "changes", relDir ?? slug);
10522
+ const dir = path5.join(root, SDD_DIR, "changes", relDir ?? slug);
10430
10523
  const diagnostics = [];
10431
10524
  const change = { slug, dir, diagnostics };
10432
- const metaFile = path4.join(dir, "meta.yaml");
10525
+ const metaFile = path5.join(dir, "meta.yaml");
10433
10526
  const metaRaw = await readTextIfExists(metaFile);
10434
10527
  if (metaRaw === void 0) {
10435
10528
  diagnostics.push(diag("ATLAS-FILES-001", "error", `El cambio "${slug}" no tiene meta.yaml`, { path: metaFile, suggestion: "Crea meta.yaml con slug, lane y dominio" }));
@@ -10438,7 +10531,7 @@ async function loadChange(root, slug, relDir) {
10438
10531
  diagnostics.push(...parsed.diagnostics);
10439
10532
  if (parsed.meta) change.meta = parsed.meta;
10440
10533
  }
10441
- const deltaFile = path4.join(dir, "spec.md");
10534
+ const deltaFile = path5.join(dir, "spec.md");
10442
10535
  const deltaRaw = await readTextIfExists(deltaFile);
10443
10536
  if (deltaRaw !== void 0) {
10444
10537
  change.delta = parseDelta(deltaRaw, deltaFile);
@@ -10446,41 +10539,42 @@ async function loadChange(root, slug, relDir) {
10446
10539
  } else {
10447
10540
  diagnostics.push(diag("ATLAS-FILES-002", "warning", `El cambio "${slug}" no tiene spec.md (delta)`, { path: deltaFile }));
10448
10541
  }
10449
- const planFile = path4.join(dir, "plan.md");
10542
+ const planFile = path5.join(dir, "plan.md");
10450
10543
  if (await exists(planFile)) change.planPath = planFile;
10451
- const reviewFile = path4.join(dir, "review.md");
10544
+ const reviewFile = path5.join(dir, "review.md");
10452
10545
  if (await exists(reviewFile)) change.reviewPath = reviewFile;
10453
- const presentationFile = path4.join(dir, "presentation", "index.html");
10546
+ const presentationFile = path5.join(dir, "presentation", "index.html");
10454
10547
  if (await exists(presentationFile)) change.presentationPath = presentationFile;
10455
- const tasksFile = path4.join(dir, "tasks.md");
10548
+ const tasksFile = path5.join(dir, "tasks.md");
10456
10549
  const tasksRaw = await readTextIfExists(tasksFile);
10457
10550
  if (tasksRaw !== void 0) {
10458
10551
  change.tasks = parseTasksFile(tasksRaw, tasksFile);
10459
10552
  diagnostics.push(...change.tasks.diagnostics);
10460
10553
  }
10461
- const verifyFile = path4.join(dir, "verify.md");
10554
+ const verifyFile = path5.join(dir, "verify.md");
10462
10555
  const verifyRaw = await readTextIfExists(verifyFile);
10463
10556
  if (verifyRaw !== void 0) {
10464
10557
  change.verify = parseVerifyFile(verifyRaw, verifyFile);
10465
10558
  diagnostics.push(...change.verify.diagnostics);
10466
10559
  }
10467
- const fixFile = path4.join(dir, "fix.md");
10560
+ const fixFile = path5.join(dir, "fix.md");
10468
10561
  const fixRaw = await readTextIfExists(fixFile);
10469
10562
  if (fixRaw !== void 0) {
10470
10563
  change.fix = parseVerifyFile(fixRaw, fixFile);
10471
10564
  diagnostics.push(...change.fix.diagnostics);
10565
+ change.fixCovers = parseFixCovers(fixRaw);
10472
10566
  }
10473
- const mockupManifest = path4.join(dir, "mockups", "manifest.yaml");
10567
+ const mockupManifest = path5.join(dir, "mockups", "manifest.yaml");
10474
10568
  if (await exists(mockupManifest)) change.mockupManifestPath = mockupManifest;
10475
10569
  return change;
10476
10570
  }
10477
10571
  async function listChangeSlugs(root, opts = {}) {
10478
- const changesDir = path4.join(root, SDD_DIR, "changes");
10572
+ const changesDir = path5.join(root, SDD_DIR, "changes");
10479
10573
  const dirs = await listDirs(changesDir);
10480
10574
  return dirs.filter((d) => opts.includeArchived ? true : d !== "archive");
10481
10575
  }
10482
10576
  async function loadWorkspace(root) {
10483
- const sddDir = path4.join(root, SDD_DIR);
10577
+ const sddDir = path5.join(root, SDD_DIR);
10484
10578
  const { config, diagnostics: configDiags } = await loadConfig(sddDir);
10485
10579
  const diagnostics = [...configDiags];
10486
10580
  const specs = await loadSpecs(sddDir);
@@ -10493,20 +10587,20 @@ async function loadWorkspace(root) {
10493
10587
  diagnostics.push(...change.diagnostics);
10494
10588
  }
10495
10589
  const archived = [];
10496
- const archivedDir = path4.join(sddDir, "changes", "archive");
10590
+ const archivedDir = path5.join(sddDir, "changes", "archive");
10497
10591
  if (await isDirectory(archivedDir)) {
10498
10592
  for (const entry of await listDirs(archivedDir)) {
10499
- archived.push(await loadChange(root, entry.replace(/^\d{4}-\d{2}-/, ""), path4.join("archive", entry)));
10593
+ archived.push(await loadChange(root, entry.replace(/^\d{4}-\d{2}-/, ""), path5.join("archive", entry)));
10500
10594
  }
10501
10595
  }
10502
10596
  return { workspace: { root, sddDir, specs, changes, archived, diagnostics }, config };
10503
10597
  }
10504
10598
  async function ensureSddDirs(sddDir) {
10505
10599
  const created = [];
10506
- const dirs = ["specs", "changes", "runs", "metrics", path4.join("profiles", "custom")];
10600
+ const dirs = ["specs", "changes", "runs", "metrics", "fixes", path5.join("profiles", "custom")];
10507
10601
  const { ensureDir: ensureDir2 } = await import("./fsx-VF2P7ALA-XKEWTMDH.js");
10508
10602
  for (const d of dirs) {
10509
- const full = path4.join(sddDir, d);
10603
+ const full = path5.join(sddDir, d);
10510
10604
  if (!await exists(full)) {
10511
10605
  await ensureDir2(full);
10512
10606
  created.push(full);
@@ -10674,6 +10768,10 @@ notes: 12/12 casos
10674
10768
 
10675
10769
  ## Evidencia
10676
10770
 
10771
+ <!-- Opcional: declara los requisitos que este fix afecta (aparecen en su trazabilidad).
10772
+ Cubre: REQ-DOMINIO-001
10773
+ -->
10774
+
10677
10775
  <!-- Registra la evidencia real con:
10678
10776
  satlas verify <slug> --file fix --scenario REQ-DOMINIO-001-S1 --command "<comando>" --by "<nombre>"
10679
10777
  o, si es manual:
@@ -10797,6 +10895,10 @@ by: your-name
10797
10895
  ## Rollback
10798
10896
  ## Evidence
10799
10897
 
10898
+ <!-- Optional: declare the requirements this fix affects (they show up in its traceability).
10899
+ Cubre: REQ-DOMAIN-001
10900
+ -->
10901
+
10800
10902
  <!-- Register real evidence with:
10801
10903
  satlas verify <slug> --file fix --scenario REQ-DOMAIN-001-S1 --command "<command>" --by "<name>"
10802
10904
  -->
@@ -10821,7 +10923,7 @@ function changeMetaYaml(meta) {
10821
10923
  if (meta.owner) doc["owner"] = meta.owner;
10822
10924
  if (meta.mockups) doc["mockups"] = meta.mockups;
10823
10925
  return `# Estado del cambio. La fase se DERIVA de los artefactos; aqu\xED solo hechos.
10824
- ` + stringifyYaml3(doc, { lineWidth: 120 });
10926
+ ` + stringifyYaml4(doc, { lineWidth: 120 });
10825
10927
  }
10826
10928
  function indexMarkdown(input) {
10827
10929
  const es = input.language !== "en";
@@ -10835,6 +10937,15 @@ function indexMarkdown(input) {
10835
10937
  if (input.specs.length === 0) lines.push(es ? "_Sin specs todav\xEDa._" : "_No specs yet._");
10836
10938
  for (const spec of input.specs) lines.push(`- \`${spec.domain}\` \u2014 ${spec.requirements} ${es ? "requisitos" : "requirements"}`);
10837
10939
  lines.push("");
10940
+ lines.push(es ? "## Fixes vivos" : "## Living fixes");
10941
+ lines.push("");
10942
+ const fixes = input.fixes ?? [];
10943
+ if (fixes.length === 0) lines.push(es ? "_Sin fixes archivados todav\xEDa._" : "_No archived fixes yet._");
10944
+ for (const fix of fixes) {
10945
+ const parts = [fix.date, fix.domain ? `\`${fix.domain}\`` : "", `\`${fix.slug}\``, fix.result].filter((part) => part !== "");
10946
+ lines.push(`- ${parts.join(" \xB7 ")}`);
10947
+ }
10948
+ lines.push("");
10838
10949
  lines.push(es ? "## Cambios activos" : "## Active changes");
10839
10950
  lines.push("");
10840
10951
  if (input.changes.length === 0) lines.push(es ? "_Sin cambios activos._" : "_No active changes._");
@@ -10986,8 +11097,8 @@ function issueBody(input) {
10986
11097
  lines.push(`- Siguiente: \`${input.next}\` \u2014 ${input.nextDescription}`);
10987
11098
  lines.push("");
10988
11099
  lines.push("### Artefactos");
10989
- lines.push(`- Spec: \`${path5.posix.join(".sdd", "changes", change.slug, "spec.md")}\``);
10990
- if (change.planPath) lines.push(`- Plan: \`${path5.posix.join(".sdd", "changes", change.slug, "plan.md")}\``);
11100
+ lines.push(`- Spec: \`${path6.posix.join(".sdd", "changes", change.slug, "spec.md")}\``);
11101
+ if (change.planPath) lines.push(`- Plan: \`${path6.posix.join(".sdd", "changes", change.slug, "plan.md")}\``);
10991
11102
  if (change.tasks) lines.push(`- Tareas: ${change.tasks.counts.done}/${change.tasks.counts.total}`);
10992
11103
  if (change.verify) lines.push(`- Evidencia registrada: ${change.verify.evidence.length}`);
10993
11104
  lines.push("");
@@ -11051,10 +11162,10 @@ async function issueLabels(root, number, runner = defaultRunner) {
11051
11162
  }
11052
11163
  }
11053
11164
  async function patchChangeMeta(root, slug, patch) {
11054
- const file = path5.join(path5.resolve(root), ".sdd", "changes", slug, "meta.yaml");
11165
+ const file = path6.join(path6.resolve(root), ".sdd", "changes", slug, "meta.yaml");
11055
11166
  const raw = await readTextIfExists(file);
11056
11167
  if (raw === void 0) {
11057
- return { path: file, diagnostics: [diag("ATLAS-GH-007", "error", `No existe ${path5.relative(root, file)}`)] };
11168
+ return { path: file, diagnostics: [diag("ATLAS-GH-007", "error", `No existe ${path6.relative(root, file)}`)] };
11058
11169
  }
11059
11170
  let data;
11060
11171
  try {
@@ -11105,10 +11216,10 @@ async function approveFromGithub(opts) {
11105
11216
  if (!by) {
11106
11217
  return { approved: false, label, issueNumber: number, diagnostics: [diag("ATLAS-GH-011", "error", "No se pudo determinar qui\xE9n aprueba (--by ni `gh api user`)")] };
11107
11218
  }
11108
- const { signApproval: signApproval2 } = await import("./approvals-2PO6223I-54L7NGZ5.js");
11219
+ const { signApproval: signApproval2 } = await import("./approvals-2PO6223I-DENKLEXU.js");
11109
11220
  const signed = await signApproval2({
11110
11221
  root: opts.root,
11111
- artifact: path5.posix.join("changes", opts.slug, "spec.md"),
11222
+ artifact: path6.posix.join("changes", opts.slug, "spec.md"),
11112
11223
  by,
11113
11224
  channel: "tracker",
11114
11225
  note: `Aprobado v\xEDa GitHub issue #${number} (etiqueta "${label}")`
@@ -11203,10 +11314,10 @@ function buildEvidenceYaml(evidence) {
11203
11314
  return lines.join("\n");
11204
11315
  }
11205
11316
  async function recordEvidence(opts) {
11206
- const root = path6.resolve(opts.root);
11317
+ const root = path7.resolve(opts.root);
11207
11318
  const slug = opts.slug;
11208
11319
  const fileKind = opts.file ?? "verify";
11209
- const file = path6.join(root, ".sdd", "changes", slug, fileKind === "fix" ? "fix.md" : "verify.md");
11320
+ const file = path7.join(root, ".sdd", "changes", slug, fileKind === "fix" ? "fix.md" : "verify.md");
11210
11321
  const diagnostics = [];
11211
11322
  const scenario = opts.scenario.toUpperCase();
11212
11323
  if (!SCENARIO_ID_RE.test(scenario)) {
@@ -11296,7 +11407,7 @@ function generateRunId(now = /* @__PURE__ */ new Date()) {
11296
11407
  return `${iso.slice(0, 8)}-${iso.slice(8, 14)}-${randomBytes(3).toString("hex")}`;
11297
11408
  }
11298
11409
  function runsDir(root) {
11299
- return path7.join(path7.resolve(root), ".sdd", "runs");
11410
+ return path8.join(path8.resolve(root), ".sdd", "runs");
11300
11411
  }
11301
11412
  async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Date()) {
11302
11413
  const runId = generateRunId(now);
@@ -11309,25 +11420,25 @@ async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Da
11309
11420
  updatedAt: localStamp(now)
11310
11421
  };
11311
11422
  if (inputs) state.inputs = inputs;
11312
- const dir = path7.join(runsDir(root), runId);
11423
+ const dir = path8.join(runsDir(root), runId);
11313
11424
  await ensureDir(dir);
11314
- await writeText(path7.join(dir, "state.json"), `${JSON.stringify(state, null, 2)}
11425
+ await writeText(path8.join(dir, "state.json"), `${JSON.stringify(state, null, 2)}
11315
11426
  `);
11316
- await writeText(path7.join(dir, "events.jsonl"), "");
11427
+ await writeText(path8.join(dir, "events.jsonl"), "");
11317
11428
  await appendRunEvent(root, runId, "run_started", { slug, phase });
11318
11429
  return state;
11319
11430
  }
11320
11431
  async function appendRunEvent(root, runId, type, data, now = /* @__PURE__ */ new Date()) {
11321
11432
  const event = { eventId: randomBytes(4).toString("hex"), at: localStamp(now), type };
11322
11433
  if (data) event.data = data;
11323
- const file = path7.join(runsDir(root), runId, "events.jsonl");
11434
+ const file = path8.join(runsDir(root), runId, "events.jsonl");
11324
11435
  const previous = await readTextIfExists(file) ?? "";
11325
11436
  await writeText(file, `${previous}${JSON.stringify(event)}
11326
11437
  `);
11327
11438
  return event;
11328
11439
  }
11329
11440
  async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Date()) {
11330
- const file = path7.join(runsDir(root), runId, "state.json");
11441
+ const file = path8.join(runsDir(root), runId, "state.json");
11331
11442
  const raw = await readTextIfExists(file);
11332
11443
  if (raw === void 0) return;
11333
11444
  const state = JSON.parse(raw);
@@ -11337,13 +11448,13 @@ async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Da
11337
11448
  `);
11338
11449
  }
11339
11450
  async function readRun(root, runId) {
11340
- const dir = path7.join(runsDir(root), runId);
11341
- const stateRaw = await readTextIfExists(path7.join(dir, "state.json"));
11451
+ const dir = path8.join(runsDir(root), runId);
11452
+ const stateRaw = await readTextIfExists(path8.join(dir, "state.json"));
11342
11453
  if (stateRaw === void 0) {
11343
11454
  return { diagnostics: [diag("ATLAS-RUN-001", "error", `No existe el run ${runId}`, { path: dir })] };
11344
11455
  }
11345
11456
  const state = JSON.parse(stateRaw);
11346
- const eventsRaw = await readTextIfExists(path7.join(dir, "events.jsonl")) ?? "";
11457
+ const eventsRaw = await readTextIfExists(path8.join(dir, "events.jsonl")) ?? "";
11347
11458
  const events = [];
11348
11459
  for (const line of eventsRaw.split("\n")) {
11349
11460
  if (line.trim() === "") continue;
@@ -11360,7 +11471,7 @@ async function listRuns(root, filter = {}) {
11360
11471
  const ids = await listDirs(dir);
11361
11472
  const out = [];
11362
11473
  for (const id of ids) {
11363
- const raw = await readTextIfExists(path7.join(dir, id, "state.json"));
11474
+ const raw = await readTextIfExists(path8.join(dir, id, "state.json"));
11364
11475
  if (raw === void 0) continue;
11365
11476
  try {
11366
11477
  const state = JSON.parse(raw);
@@ -11395,20 +11506,20 @@ var mockupManifestSchema = z4.object({
11395
11506
  });
11396
11507
  var TOKEN_CANDIDATES = ["design/tokens.json", "DESIGN.md", "design/DESIGN.md", ".sdd/design/tokens.json"];
11397
11508
  function mockupsDir(root, slug) {
11398
- return path8.join(path8.resolve(root), ".sdd", "changes", slug, "mockups");
11509
+ return path9.join(path9.resolve(root), ".sdd", "changes", slug, "mockups");
11399
11510
  }
11400
11511
  async function tokensFile(root) {
11401
11512
  for (const candidate of TOKEN_CANDIDATES) {
11402
- const abs = path8.join(root, candidate);
11513
+ const abs = path9.join(root, candidate);
11403
11514
  if (await exists(abs)) return candidate;
11404
11515
  }
11405
11516
  return void 0;
11406
11517
  }
11407
11518
  async function computeInputsHash(root, change) {
11408
- const deltaPath = path8.join(change.dir, "spec.md");
11519
+ const deltaPath = path9.join(change.dir, "spec.md");
11409
11520
  const delta = await readTextIfExists(deltaPath) ?? "";
11410
11521
  const tokens = await tokensFile(root);
11411
- const tokensContent = tokens ? await readTextIfExists(path8.join(root, tokens)) ?? "" : "";
11522
+ const tokensContent = tokens ? await readTextIfExists(path9.join(root, tokens)) ?? "" : "";
11412
11523
  return artifactHash(`${delta}
11413
11524
  ---tokens---
11414
11525
  ${tokensContent}`);
@@ -11433,7 +11544,7 @@ function planMockups(change, platform = "web") {
11433
11544
  return { platform, screens };
11434
11545
  }
11435
11546
  async function readMockupManifest(root, slug) {
11436
- const file = path8.join(mockupsDir(root, slug), "manifest.yaml");
11547
+ const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
11437
11548
  const raw = await readTextIfExists(file);
11438
11549
  if (raw === void 0) return { diagnostics: [] };
11439
11550
  let data;
@@ -11525,7 +11636,7 @@ async function checkMockups(root, slug, change) {
11525
11636
  const { manifest, path: manifestPath, diagnostics } = await readMockupManifest(root, slug);
11526
11637
  const findings = [...diagnostics];
11527
11638
  if (!manifest || !manifestPath) {
11528
- return { findings: [...findings, diag("LINT-MKP-004", "error", "No existe mockups/manifest.yaml", { path: path8.join(dir, "manifest.yaml") })], stale: false };
11639
+ return { findings: [...findings, diag("LINT-MKP-004", "error", "No existe mockups/manifest.yaml", { path: path9.join(dir, "manifest.yaml") })], stale: false };
11529
11640
  }
11530
11641
  const known = /* @__PURE__ */ new Set();
11531
11642
  for (const req of [...change.delta?.added ?? [], ...change.delta?.modified ?? []]) {
@@ -11533,12 +11644,12 @@ async function checkMockups(root, slug, change) {
11533
11644
  }
11534
11645
  findings.push(...lintMockupManifest(manifest, known, manifestPath));
11535
11646
  for (const screen of manifest.screens) {
11536
- const html = await readTextIfExists(path8.join(dir, screen.file));
11647
+ const html = await readTextIfExists(path9.join(dir, screen.file));
11537
11648
  if (html === void 0) {
11538
- findings.push(diag("ATLAS-MKP-004", "error", `Falta el archivo del mockup: ${screen.file}`, { path: path8.join(dir, screen.file) }));
11649
+ findings.push(diag("ATLAS-MKP-004", "error", `Falta el archivo del mockup: ${screen.file}`, { path: path9.join(dir, screen.file) }));
11539
11650
  continue;
11540
11651
  }
11541
- findings.push(...lintMockupHtml(html, path8.join(dir, screen.file)));
11652
+ findings.push(...lintMockupHtml(html, path9.join(dir, screen.file)));
11542
11653
  }
11543
11654
  const inputsHash = await computeInputsHash(root, change);
11544
11655
  const stale = manifest.inputsHash !== void 0 && manifest.inputsHash !== inputsHash;
@@ -11553,7 +11664,7 @@ async function checkMockups(root, slug, change) {
11553
11664
  return { findings, stale, manifest, manifestPath };
11554
11665
  }
11555
11666
  async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date()) {
11556
- const file = path8.join(mockupsDir(root, slug), "plan.yaml");
11667
+ const file = path9.join(mockupsDir(root, slug), "plan.yaml");
11557
11668
  if (await exists(file)) return file;
11558
11669
  const doc = {
11559
11670
  schema_version: 1,
@@ -11561,11 +11672,11 @@ async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date(
11561
11672
  platform: plan.platform,
11562
11673
  screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints }))
11563
11674
  };
11564
- await writeText(file, stringifyYaml4(doc, { lineWidth: 120 }));
11675
+ await writeText(file, stringifyYaml5(doc, { lineWidth: 120 }));
11565
11676
  return file;
11566
11677
  }
11567
11678
  async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PURE__ */ new Date()) {
11568
- const file = path8.join(mockupsDir(root, slug), "manifest.yaml");
11679
+ const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
11569
11680
  if (await exists(file)) return file;
11570
11681
  const doc = {
11571
11682
  schema_version: 1,
@@ -11577,16 +11688,16 @@ async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PUR
11577
11688
  screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints })),
11578
11689
  screenshots: []
11579
11690
  };
11580
- await writeText(file, stringifyYaml4(doc, { lineWidth: 120 }));
11691
+ await writeText(file, stringifyYaml5(doc, { lineWidth: 120 }));
11581
11692
  return file;
11582
11693
  }
11583
11694
  async function updateMockupScreenshots(root, slug, screenshots) {
11584
- const file = path8.join(mockupsDir(root, slug), "manifest.yaml");
11695
+ const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
11585
11696
  const raw = await readTextIfExists(file);
11586
11697
  if (raw === void 0) return void 0;
11587
11698
  const data = parseYaml6(raw) ?? {};
11588
11699
  data["screenshots"] = screenshots;
11589
- await writeText(file, stringifyYaml4(data, { lineWidth: 120 }));
11700
+ await writeText(file, stringifyYaml5(data, { lineWidth: 120 }));
11590
11701
  return file;
11591
11702
  }
11592
11703
  async function captureMockups(root, slug, manifest, now = /* @__PURE__ */ new Date()) {
@@ -11611,9 +11722,9 @@ async function captureMockups(root, slug, manifest, now = /* @__PURE__ */ new Da
11611
11722
  const page2 = await browser.newPage();
11612
11723
  for (const bp of screen.breakpoints && screen.breakpoints.length > 0 ? screen.breakpoints : [1440]) {
11613
11724
  await page2.setViewportSize({ width: bp, height: 900 });
11614
- await page2.goto(`file://${path8.join(dir, screen.file).split(path8.sep).join("/")}`);
11615
- const rel = path8.posix.join("screens", `${screen.id}-${bp}.png`);
11616
- await page2.screenshot({ path: path8.join(dir, rel) });
11725
+ await page2.goto(`file://${path9.join(dir, screen.file).split(path9.sep).join("/")}`);
11726
+ const rel = path9.posix.join("screens", `${screen.id}-${bp}.png`);
11727
+ await page2.screenshot({ path: path9.join(dir, rel) });
11617
11728
  screenshots.push(rel);
11618
11729
  }
11619
11730
  await page2.close();
@@ -11629,7 +11740,7 @@ async function mockupsReady(root, slug, change) {
11629
11740
  return Boolean(check.manifest && check.manifest.screens.length > 0 && !check.stale);
11630
11741
  }
11631
11742
  async function setMockupRequirement(root, slug, value) {
11632
- const file = path8.join(root, ".sdd", "changes", slug, "meta.yaml");
11743
+ const file = path9.join(root, ".sdd", "changes", slug, "meta.yaml");
11633
11744
  const raw = await readTextIfExists(file) ?? `schema_version: 1
11634
11745
  slug: ${slug}
11635
11746
  lane: standard
@@ -11859,12 +11970,12 @@ ${texts.scenarios}`);
11859
11970
  }
11860
11971
  }
11861
11972
  async function loadProjectPacks(sddDir) {
11862
- const dir = path9.join(sddDir, "packs");
11973
+ const dir = path10.join(sddDir, "packs");
11863
11974
  const diagnostics = [];
11864
11975
  const packs = [];
11865
11976
  for (const entry of await listDir(dir)) {
11866
11977
  if (!/\.ya?ml$/i.test(entry)) continue;
11867
- const file = path9.join(dir, entry);
11978
+ const file = path10.join(dir, entry);
11868
11979
  const raw = await readTextIfExists(file);
11869
11980
  if (raw === void 0) continue;
11870
11981
  let data;
@@ -11951,7 +12062,7 @@ function packFindings(evaluations, change) {
11951
12062
  }
11952
12063
  var UI_DOMAINS = /* @__PURE__ */ new Set(["frontend", "mobile", "fullstack"]);
11953
12064
  async function runAnalyze(opts) {
11954
- const root = path10.resolve(opts.root);
12065
+ const root = path11.resolve(opts.root);
11955
12066
  const { workspace, config } = await loadWorkspace(root);
11956
12067
  const change = workspace.changes.find((c) => c.slug === opts.slug);
11957
12068
  if (!change) {
@@ -11969,7 +12080,7 @@ async function runAnalyze(opts) {
11969
12080
  for (const req of spec.spec.requirements) living.set(req.id, req);
11970
12081
  }
11971
12082
  if (change.delta) {
11972
- findings.push(...lintDelta(change.delta, living, path10.join(change.dir, "spec.md"), { language: config.spec.language }));
12083
+ findings.push(...lintDelta(change.delta, living, path11.join(change.dir, "spec.md"), { language: config.spec.language }));
11973
12084
  }
11974
12085
  const trace = checkTrace({ specs: workspace.specs, change, requireEvidence: false });
11975
12086
  findings.push(...trace.findings);
@@ -12018,7 +12129,7 @@ async function runAnalyze(opts) {
12018
12129
  if (mockups) result.mockups = mockups;
12019
12130
  if (packs) result.packs = packs;
12020
12131
  if (opts.write !== false) {
12021
- const file = path10.join(change.dir, "analyze.md");
12132
+ const file = path11.join(change.dir, "analyze.md");
12022
12133
  await writeText(file, renderAnalyze(change, result, localStamp(opts.now)));
12023
12134
  result.path = file;
12024
12135
  }
@@ -12060,7 +12171,7 @@ function renderAnalyze(change, result, generatedAt) {
12060
12171
  return lines.join("\n");
12061
12172
  }
12062
12173
  async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
12063
- const resolved = path11.resolve(root);
12174
+ const resolved = path12.resolve(root);
12064
12175
  const { workspace, config } = await loadWorkspace(resolved);
12065
12176
  const approvals = await loadApprovals(workspace.sddDir);
12066
12177
  const living = /* @__PURE__ */ new Map();
@@ -12075,7 +12186,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
12075
12186
  const deltaContent = await readDelta(change);
12076
12187
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent);
12077
12188
  const findings = [];
12078
- if (change.delta) findings.push(...lintDelta(change.delta, living, path11.join(change.dir, "spec.md"), { language: config.spec.language }));
12189
+ if (change.delta) findings.push(...lintDelta(change.delta, living, path12.join(change.dir, "spec.md"), { language: config.spec.language }));
12079
12190
  const trace = checkTrace({
12080
12191
  specs: workspace.specs,
12081
12192
  change,
@@ -12140,7 +12251,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
12140
12251
  attention.push({ slug: change.slug, kind: "missing-evidence", detail: `evidencia ${change.scenariosPassed}/${change.scenariosTotal}` });
12141
12252
  }
12142
12253
  }
12143
- const archiveDir = path11.join(workspace.sddDir, "changes", "archive");
12254
+ const archiveDir = path12.join(workspace.sddDir, "changes", "archive");
12144
12255
  const archivedEntries = await listDirs(archiveDir);
12145
12256
  const throughputByMonth = {};
12146
12257
  for (const entry of archivedEntries) {
@@ -12178,10 +12289,10 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
12178
12289
  }
12179
12290
  async function readDelta(change) {
12180
12291
  const { readTextIfExists: readTextIfExists2 } = await import("./fsx-VF2P7ALA-XKEWTMDH.js");
12181
- return readTextIfExists2(path11.join(change.dir, "spec.md"));
12292
+ return readTextIfExists2(path12.join(change.dir, "spec.md"));
12182
12293
  }
12183
12294
  async function generatePresentation(opts) {
12184
- const root = path12.resolve(opts.root);
12295
+ const root = path13.resolve(opts.root);
12185
12296
  const { workspace, config } = await loadWorkspace(root);
12186
12297
  const language = config.project.language;
12187
12298
  const change = workspace.changes.find((c) => c.slug === opts.slug);
@@ -12191,17 +12302,17 @@ async function generatePresentation(opts) {
12191
12302
  if (!change.delta) {
12192
12303
  return { slug: opts.slug, diagnostics: [diag("ATLAS-PRESENT-001", "error", `El cambio "${opts.slug}" no tiene spec.md (delta)`)] };
12193
12304
  }
12194
- const deltaPath = path12.join(change.dir, "spec.md");
12305
+ const deltaPath = path13.join(change.dir, "spec.md");
12195
12306
  const deltaContent = await readTextIfExists(deltaPath) ?? "";
12196
12307
  const hash = artifactHash(deltaContent);
12197
12308
  const now = opts.now ?? /* @__PURE__ */ new Date();
12198
- const presentationDir = path12.join(change.dir, "presentation");
12199
- const mockupsCopyDir = path12.join(presentationDir, "mockups");
12309
+ const presentationDir = path13.join(change.dir, "presentation");
12310
+ const mockupsCopyDir = path13.join(presentationDir, "mockups");
12200
12311
  await ensureDir(presentationDir);
12201
12312
  const mockupInfo = await copyMockups(root, change, mockupsCopyDir);
12202
- const proposalRaw = await readTextIfExists(path12.join(change.dir, "proposal.md"));
12313
+ const proposalRaw = await readTextIfExists(path13.join(change.dir, "proposal.md"));
12203
12314
  const proposalBody = proposalRaw ? parseFrontmatter(proposalRaw).body : "";
12204
- const approvals = await loadApprovals(path12.join(root, ".sdd"));
12315
+ const approvals = await loadApprovals(path13.join(root, ".sdd"));
12205
12316
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent);
12206
12317
  const labels = labelsFor(language);
12207
12318
  const html = page({
@@ -12222,7 +12333,7 @@ async function generatePresentation(opts) {
12222
12333
  approveHint: `satlas approve ${change.slug} --by "<nombre>" --channel presentation`,
12223
12334
  ...approval.status === "valid" && approval.approvedBy ? { approval: { by: approval.approvedBy, at: approval.approvedAt ?? "" } } : {}
12224
12335
  });
12225
- const outFile = path12.join(presentationDir, "index.html");
12336
+ const outFile = path13.join(presentationDir, "index.html");
12226
12337
  await writeText(outFile, html);
12227
12338
  return { slug: change.slug, path: outFile, hash, diagnostics: mockupInfo.diagnostics };
12228
12339
  }
@@ -12234,12 +12345,12 @@ async function copyMockups(root, change, destDir) {
12234
12345
  await ensureDir(destDir);
12235
12346
  const screens = [];
12236
12347
  for (const screen of manifest.screens) {
12237
- const src = path12.join(dir, screen.file);
12348
+ const src = path13.join(dir, screen.file);
12238
12349
  if (!await exists(src)) {
12239
12350
  diagnostics.push(diag("ATLAS-PRESENT-002", "warning", `El mockup ${screen.file} no existe y no se incluir\xE1`, { path: src }));
12240
12351
  continue;
12241
12352
  }
12242
- await copyFile(src, path12.join(destDir, screen.file));
12353
+ await copyFile(src, path13.join(destDir, screen.file));
12243
12354
  screens.push({
12244
12355
  id: screen.id,
12245
12356
  title: screen.title ?? screen.id,
@@ -12249,13 +12360,13 @@ async function copyMockups(root, change, destDir) {
12249
12360
  });
12250
12361
  }
12251
12362
  const screenshots = [];
12252
- const shotsDir = path12.join(dir, "screens");
12363
+ const shotsDir = path13.join(dir, "screens");
12253
12364
  if (await exists(shotsDir)) {
12254
- await ensureDir(path12.join(destDir, "screens"));
12365
+ await ensureDir(path13.join(destDir, "screens"));
12255
12366
  const { listDir: listDir4 } = await import("./fsx-VF2P7ALA-XKEWTMDH.js");
12256
12367
  for (const file of await listDir4(shotsDir)) {
12257
12368
  if (!/\.(png|jpe?g|webp)$/i.test(file)) continue;
12258
- await copyFile(path12.join(shotsDir, file), path12.join(destDir, "screens", file));
12369
+ await copyFile(path13.join(shotsDir, file), path13.join(destDir, "screens", file));
12259
12370
  screenshots.push(`mockups/screens/${file}`);
12260
12371
  }
12261
12372
  }
@@ -12471,11 +12582,11 @@ var SOURCE_EXT = /* @__PURE__ */ new Set([
12471
12582
  ".groovy"
12472
12583
  ]);
12473
12584
  async function adoptWorkspace(opts) {
12474
- const root = path13.resolve(opts.root);
12585
+ const root = path14.resolve(opts.root);
12475
12586
  const dryRun = opts.dryRun ?? false;
12476
12587
  const diagnostics = [];
12477
- const sddDir = path13.join(root, ".sdd");
12478
- if (!await exists(path13.join(sddDir, "config.yaml"))) {
12588
+ const sddDir = path14.join(root, ".sdd");
12589
+ if (!await exists(path14.join(sddDir, "config.yaml"))) {
12479
12590
  return {
12480
12591
  root,
12481
12592
  domains: [],
@@ -12486,8 +12597,8 @@ async function adoptWorkspace(opts) {
12486
12597
  }
12487
12598
  const { config } = await loadConfig(sddDir);
12488
12599
  const files = await walkFiles(root, { skipDirs: [...SKIP_DIRS] });
12489
- const inventoried = files.map((f) => toPosix(path13.relative(root, f.path)));
12490
- const relative = inventoried.filter((f) => SOURCE_EXT.has(path13.extname(f).toLowerCase()));
12600
+ const inventoried = files.map((f) => toPosix(path14.relative(root, f.path)));
12601
+ const relative = inventoried.filter((f) => SOURCE_EXT.has(path14.extname(f).toLowerCase()));
12491
12602
  if (relative.length === 0) {
12492
12603
  diagnostics.push(diag("ATLAS-ADOPT-002", "warning", "No se encontraron archivos de c\xF3digo para inventariar", { path: root, suggestion: "A\xF1ade el dominio a mano con --domains <nombre>" }));
12493
12604
  }
@@ -12515,13 +12626,13 @@ async function adoptWorkspace(opts) {
12515
12626
  const createdSpecs = [];
12516
12627
  for (const name of domainNames.sort()) {
12517
12628
  const domainFiles = (counts.get(name) ?? []).sort();
12518
- const existingSpec = await exists(path13.join(sddDir, "specs", name, "spec.md"));
12629
+ const existingSpec = await exists(path14.join(sddDir, "specs", name, "spec.md"));
12519
12630
  domains.push({ name, files: domainFiles.length, samples: domainFiles.slice(0, 8), existingSpec });
12520
12631
  if (existingSpec) {
12521
- diagnostics.push(diag("ATLAS-ADOPT-003", "info", `La spec de "${name}" ya existe; se conserva`, { path: path13.join(sddDir, "specs", name, "spec.md") }));
12632
+ diagnostics.push(diag("ATLAS-ADOPT-003", "info", `La spec de "${name}" ya existe; se conserva`, { path: path14.join(sddDir, "specs", name, "spec.md") }));
12522
12633
  continue;
12523
12634
  }
12524
- const specPath = path13.join(sddDir, "specs", name, "spec.md");
12635
+ const specPath = path14.join(sddDir, "specs", name, "spec.md");
12525
12636
  if (!dryRun) {
12526
12637
  await writeText(specPath, baselineSpec(name, domainFiles));
12527
12638
  createdSpecs.push(specPath);
@@ -12529,7 +12640,7 @@ async function adoptWorkspace(opts) {
12529
12640
  createdSpecs.push(specPath);
12530
12641
  }
12531
12642
  }
12532
- const reportPath = path13.join(sddDir, "adopt-report.md");
12643
+ const reportPath = path14.join(sddDir, "adopt-report.md");
12533
12644
  if (!dryRun) {
12534
12645
  await writeText(reportPath, adoptReport({ root, config, domains, detection: detection?.best, language: config.project.language, files: relative.length, inventoried: inventoried.length, now: opts.now ?? /* @__PURE__ */ new Date() }));
12535
12646
  }
@@ -12549,12 +12660,12 @@ async function loadProjectProfiles(root, dirs) {
12549
12660
  if (!await exists(dir)) continue;
12550
12661
  out.push(...await loadProfilesFromDir(dir));
12551
12662
  }
12552
- const custom = path13.join(root, ".sdd", "profiles", "custom");
12663
+ const custom = path14.join(root, ".sdd", "profiles", "custom");
12553
12664
  if (await exists(custom)) out.push(...await loadProfilesFromDir(custom));
12554
12665
  return out;
12555
12666
  }
12556
12667
  async function fallbackDomainName(root, projectName) {
12557
- const pkg = await readTextIfExists(path13.join(root, "package.json"));
12668
+ const pkg = await readTextIfExists(path14.join(root, "package.json"));
12558
12669
  if (pkg) {
12559
12670
  try {
12560
12671
  const data = JSON.parse(pkg);
@@ -12641,11 +12752,11 @@ function adoptReport(input) {
12641
12752
  return lines.join("\n");
12642
12753
  }
12643
12754
  async function initWorkspace(opts) {
12644
- const root = path14.resolve(opts.root);
12645
- const sddDir = path14.join(root, ".sdd");
12755
+ const root = path15.resolve(opts.root);
12756
+ const sddDir = path15.join(root, ".sdd");
12646
12757
  const diagnostics = [];
12647
12758
  const created = [];
12648
- if (await exists(path14.join(sddDir, "config.yaml"))) {
12759
+ if (await exists(path15.join(sddDir, "config.yaml"))) {
12649
12760
  return {
12650
12761
  sddDir,
12651
12762
  created,
@@ -12655,29 +12766,29 @@ async function initWorkspace(opts) {
12655
12766
  created.push(...await ensureSddDirs(sddDir));
12656
12767
  const language = opts.language ?? "es";
12657
12768
  const cfg = defaultConfig({ name: opts.name, language });
12658
- cfg.project.name = opts.name ?? path14.basename(root);
12769
+ cfg.project.name = opts.name ?? path15.basename(root);
12659
12770
  await writeConfig(sddDir, cfg);
12660
- created.push(path14.join(sddDir, "config.yaml"));
12771
+ created.push(path15.join(sddDir, "config.yaml"));
12661
12772
  const templates = templatesFor(language);
12662
- await writeText(path14.join(sddDir, "constitution.md"), templates.constitution);
12663
- created.push(path14.join(sddDir, "constitution.md"));
12664
- await writeText(path14.join(sddDir, "glossary.md"), templates.glossary);
12665
- created.push(path14.join(sddDir, "glossary.md"));
12773
+ await writeText(path15.join(sddDir, "constitution.md"), templates.constitution);
12774
+ created.push(path15.join(sddDir, "constitution.md"));
12775
+ await writeText(path15.join(sddDir, "glossary.md"), templates.glossary);
12776
+ created.push(path15.join(sddDir, "glossary.md"));
12666
12777
  const detected = await detectIfPossible(root, opts.profilesDir);
12667
12778
  if (detected) {
12668
12779
  const yaml2 = detectionToYaml(detected, localStamp(opts.now));
12669
- await writeText(path14.join(sddDir, "profiles", "detected.yaml"), yaml2);
12670
- created.push(path14.join(sddDir, "profiles", "detected.yaml"));
12780
+ await writeText(path15.join(sddDir, "profiles", "detected.yaml"), yaml2);
12781
+ created.push(path15.join(sddDir, "profiles", "detected.yaml"));
12671
12782
  if (detected.best) {
12672
12783
  diagnostics.push(diag("ATLAS-INIT-002", "info", `Stack detectado: ${detected.best.displayName} (${detected.best.score} puntos)`, { suggestion: "Revisa .sdd/profiles/detected.yaml" }));
12673
12784
  } else {
12674
12785
  diagnostics.push(diag("ATLAS-INIT-003", "info", "No se detect\xF3 un stack conocido: se usar\xE1 el perfil gen\xE9rico", { suggestion: "Crea un perfil propio con `satlas profile` (F2) o edita .sdd/profiles/custom/" }));
12675
12786
  }
12676
12787
  }
12677
- await writeText(path14.join(sddDir, "INDEX.md"), initialIndex(language, cfg.project.name));
12678
- created.push(path14.join(sddDir, "INDEX.md"));
12788
+ await writeText(path15.join(sddDir, "INDEX.md"), initialIndex(language, cfg.project.name));
12789
+ created.push(path15.join(sddDir, "INDEX.md"));
12679
12790
  if (opts.local) {
12680
- const gitignore = path14.join(root, ".gitignore");
12791
+ const gitignore = path15.join(root, ".gitignore");
12681
12792
  const current = await readTextIfExists(gitignore) ?? "";
12682
12793
  if (!/(^|\n)\.sdd\/?(\n|$)/.test(current)) {
12683
12794
  const next = current.endsWith("\n") || current === "" ? `${current}.sdd/
@@ -12715,7 +12826,7 @@ async function createChange(opts) {
12715
12826
  const diagnostics = [];
12716
12827
  const slug = opts.slug.trim().toLowerCase();
12717
12828
  const language = opts.language ?? opts.cfg?.project.language ?? "es";
12718
- const dir = path15.join(path15.resolve(opts.root), ".sdd", "changes", slug);
12829
+ const dir = path16.join(path16.resolve(opts.root), ".sdd", "changes", slug);
12719
12830
  if (!SLUG_RE.test(slug)) {
12720
12831
  return {
12721
12832
  slug,
@@ -12746,20 +12857,20 @@ async function createChange(opts) {
12746
12857
  await ensureDir(dir);
12747
12858
  const templates = templatesFor(language);
12748
12859
  const files = [];
12749
- const metaFile = path15.join(dir, "meta.yaml");
12860
+ const metaFile = path16.join(dir, "meta.yaml");
12750
12861
  await writeText(metaFile, changeMetaYaml(meta));
12751
12862
  files.push(metaFile);
12752
12863
  if (lane === "fix") {
12753
- const fixFile = path15.join(dir, "fix.md");
12864
+ const fixFile = path16.join(dir, "fix.md");
12754
12865
  await writeText(fixFile, renderTemplate(templates.fix, { TITLE: title }));
12755
12866
  files.push(fixFile);
12756
12867
  return { slug, dir, files, diagnostics };
12757
12868
  }
12758
- const proposalFile = path15.join(dir, "proposal.md");
12869
+ const proposalFile = path16.join(dir, "proposal.md");
12759
12870
  await writeText(proposalFile, renderTemplate(templates.proposal, { TITLE: title }));
12760
12871
  files.push(proposalFile);
12761
12872
  const domainUpper = domain.replace(/[^a-z0-9]/gi, "").toUpperCase() || "GEN";
12762
- const deltaFile = path15.join(dir, "spec.md");
12873
+ const deltaFile = path16.join(dir, "spec.md");
12763
12874
  await writeText(deltaFile, templates.specDelta({ title, domainUpper, domain }));
12764
12875
  files.push(deltaFile);
12765
12876
  return { slug, dir, files, diagnostics };
@@ -12872,7 +12983,7 @@ async function restoreFile(file, previous) {
12872
12983
  }
12873
12984
  }
12874
12985
  async function archiveChange(opts) {
12875
- const root = path16.resolve(opts.root);
12986
+ const root = path17.resolve(opts.root);
12876
12987
  const diagnostics = [];
12877
12988
  const { config } = await loadWorkspace(root);
12878
12989
  const language = opts.language ?? config.project.language;
@@ -12888,16 +12999,43 @@ async function archiveChange(opts) {
12888
12999
  return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
12889
13000
  }
12890
13001
  const nowFix = opts.now ?? /* @__PURE__ */ new Date();
12891
- const targetFix = path16.join(root, ".sdd", "changes", "archive", `${localMonth(nowFix)}-${opts.slug}`);
13002
+ const targetFix = path17.join(root, ".sdd", "changes", "archive", `${localMonth(nowFix)}-${opts.slug}`);
12892
13003
  if (await exists(targetFix)) {
12893
13004
  diagnostics.push(diag("ATLAS-ARCH-002", "error", `Ya existe un cambio archivado en ${targetFix}`, { path: targetFix }));
12894
13005
  return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
12895
13006
  }
13007
+ let livingFix;
13008
+ let livingCreated = false;
13009
+ if (!opts.dryRun) {
13010
+ const fixRaw = await readTextIfExists(path17.join(change.dir, "fix.md")) ?? "";
13011
+ try {
13012
+ const written = await writeLivingFix(root, {
13013
+ slug: opts.slug,
13014
+ date: localDate(nowFix),
13015
+ result: "pass",
13016
+ ...change.meta.domain !== void 0 ? { domain: change.meta.domain } : {},
13017
+ ...change.meta.title !== void 0 ? { title: change.meta.title } : {},
13018
+ covers: change.fixCovers ?? [],
13019
+ content: fixRaw
13020
+ });
13021
+ livingFix = written.relativePath;
13022
+ livingCreated = written.created;
13023
+ } catch (error) {
13024
+ diagnostics.push(
13025
+ diag("ATLAS-ARCH-006", "error", `No se pudo conservar el fix vivo: ${error.message}`, {
13026
+ path: path17.join(root, ".sdd", "fixes"),
13027
+ suggestion: "Revisa los permisos de .sdd/fixes y vuelve a intentar; el fix sigue sin archivar"
13028
+ })
13029
+ );
13030
+ return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: false };
13031
+ }
13032
+ }
12896
13033
  if (!opts.dryRun) {
12897
- await ensureDir(path16.dirname(targetFix));
13034
+ await ensureDir(path17.dirname(targetFix));
12898
13035
  try {
12899
13036
  await moveDirectory(change.dir, targetFix);
12900
13037
  } catch (error) {
13038
+ if (livingCreated && livingFix) await removeFile(path17.join(root, livingFix));
12901
13039
  diagnostics.push(
12902
13040
  diag("ATLAS-ARCH-004", "error", `No se pudo mover el cambio al hist\xF3rico: ${error.message}`, {
12903
13041
  path: change.dir,
@@ -12906,9 +13044,16 @@ async function archiveChange(opts) {
12906
13044
  );
12907
13045
  return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: false };
12908
13046
  }
12909
- await regenerateIndex(root, config);
13047
+ await regenerateIndex(root, config, nowFix);
12910
13048
  }
12911
- return { slug: opts.slug, archivedTo: targetFix, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
13049
+ return {
13050
+ slug: opts.slug,
13051
+ archivedTo: targetFix,
13052
+ ...livingFix !== void 0 ? { livingFix } : {},
13053
+ fold: emptyFold,
13054
+ diagnostics,
13055
+ dryRun: opts.dryRun ?? false
13056
+ };
12912
13057
  }
12913
13058
  if (!change.delta) {
12914
13059
  diagnostics.push(diag("ATLAS-ARCH-003", "error", `El cambio "${opts.slug}" no tiene spec.md (delta): no hay nada que plegar`, { path: change.dir }));
@@ -12916,10 +13061,10 @@ async function archiveChange(opts) {
12916
13061
  }
12917
13062
  const domain = change.meta.domain;
12918
13063
  if (!domain) {
12919
- diagnostics.push(diag("ATLAS-ARCH-001", "error", `El cambio "${opts.slug}" no declara domain en meta.yaml`, { path: path16.join(change.dir, "meta.yaml") }));
13064
+ diagnostics.push(diag("ATLAS-ARCH-001", "error", `El cambio "${opts.slug}" no declara domain en meta.yaml`, { path: path17.join(change.dir, "meta.yaml") }));
12920
13065
  return { slug: opts.slug, fold: { content: "", applied: { added: [], modified: [], removed: [], renamed: [] }, diagnostics }, diagnostics, dryRun: opts.dryRun ?? false };
12921
13066
  }
12922
- const specFile = path16.join(root, ".sdd", "specs", domain, "spec.md");
13067
+ const specFile = path17.join(root, ".sdd", "specs", domain, "spec.md");
12923
13068
  const existing = await readTextIfExists(specFile);
12924
13069
  const base = existing ?? `---
12925
13070
  domain: ${domain}
@@ -12949,8 +13094,8 @@ ${Object.entries(nextFm).map(([k, v]) => `${k}: ${String(v)}`).join("\n")}
12949
13094
 
12950
13095
  ${body}`;
12951
13096
  const month = localMonth(now);
12952
- const archiveDir = path16.join(root, ".sdd", "changes", "archive");
12953
- const target = path16.join(archiveDir, `${month}-${opts.slug}`);
13097
+ const archiveDir = path17.join(root, ".sdd", "changes", "archive");
13098
+ const target = path17.join(archiveDir, `${month}-${opts.slug}`);
12954
13099
  if (await exists(target)) {
12955
13100
  diagnostics.push(diag("ATLAS-ARCH-002", "error", `Ya existe un cambio archivado en ${target}`, { path: target }));
12956
13101
  return { slug: opts.slug, domain, fold, diagnostics, dryRun: opts.dryRun ?? false };
@@ -12976,17 +13121,370 @@ ${body}`;
12976
13121
  }
12977
13122
  async function regenerateIndex(root, cfg, now = /* @__PURE__ */ new Date()) {
12978
13123
  const { workspace, config } = cfg ? { workspace: (await loadWorkspace(root)).workspace, config: cfg } : await loadWorkspace(root);
12979
- const archiveDir = path16.join(root, ".sdd", "changes", "archive");
13124
+ const archiveDir = path17.join(root, ".sdd", "changes", "archive");
12980
13125
  const archived = await exists(archiveDir) ? (await fs.readdir(archiveDir)).filter((e) => !e.startsWith(".")).length : 0;
13126
+ const fixes = await loadLivingFixes(root);
12981
13127
  const markdown2 = indexMarkdown({
12982
13128
  projectName: config.project.name,
12983
13129
  language: config.project.language,
12984
13130
  specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length })),
12985
13131
  changes: workspace.changes.map((c) => ({ slug: c.slug, lane: c.meta?.lane ?? config.lanes.default })),
13132
+ fixes: fixes.map((fix) => ({ slug: fix.slug, date: fix.date, result: fix.result, ...fix.domain !== void 0 ? { domain: fix.domain } : {} })),
12986
13133
  archived
12987
13134
  });
12988
13135
  void now;
12989
- await writeText(path16.join(root, ".sdd", "INDEX.md"), markdown2);
13136
+ await writeText(path17.join(root, ".sdd", "INDEX.md"), markdown2);
13137
+ }
13138
+ async function removeFile(file) {
13139
+ try {
13140
+ await rm(file, { force: true });
13141
+ } catch {
13142
+ }
13143
+ }
13144
+ var SCHEMA_VERSION = 1;
13145
+ var BACKUP_DIRNAME = ".backup";
13146
+ var BACKUP_POINTER = ".latest";
13147
+ var SCHEMA_MIGRATIONS = [
13148
+ {
13149
+ id: "0001-sellar-version-de-esquema",
13150
+ description: "Sella la versi\xF3n de esquema vigente en los elementos que no la declaran",
13151
+ from: 0,
13152
+ to: SCHEMA_VERSION
13153
+ }
13154
+ ];
13155
+ var FIXED_ARTIFACTS = ["config.yaml", "approvals.yaml", path18.join("profiles", "detected.yaml")];
13156
+ var CHANGE_ARTIFACTS = ["meta.yaml", path18.join("mockups", "manifest.yaml")];
13157
+ async function pushChangeArtifacts(out, sddDir, relDir) {
13158
+ for (const rel of CHANGE_ARTIFACTS) {
13159
+ const abs = path18.join(sddDir, relDir, rel);
13160
+ if (await exists(abs)) out.push({ artifact: toPosix(path18.join(relDir, rel)), path: abs });
13161
+ }
13162
+ }
13163
+ async function collectVersionedArtifacts(root) {
13164
+ const sddDir = path18.join(root, ".sdd");
13165
+ const out = [];
13166
+ for (const rel of FIXED_ARTIFACTS) {
13167
+ const abs = path18.join(sddDir, rel);
13168
+ if (await exists(abs)) out.push({ artifact: toPosix(rel), path: abs });
13169
+ }
13170
+ const changesDir = path18.join(sddDir, "changes");
13171
+ for (const slug of await listDirs(changesDir)) {
13172
+ if (slug === "archive") continue;
13173
+ await pushChangeArtifacts(out, sddDir, path18.join("changes", slug));
13174
+ }
13175
+ for (const entry of await listDirs(path18.join(changesDir, "archive"))) {
13176
+ await pushChangeArtifacts(out, sddDir, path18.join("changes", "archive", entry));
13177
+ }
13178
+ return out;
13179
+ }
13180
+ function readArtifactVersion(content) {
13181
+ let data;
13182
+ try {
13183
+ data = parseYaml8(content);
13184
+ } catch (err) {
13185
+ return { state: "unreadable", reason: `YAML inv\xE1lido (${err.message})` };
13186
+ }
13187
+ if (data === null || data === void 0) return { state: "absent" };
13188
+ if (typeof data !== "object" || Array.isArray(data)) return { state: "unreadable", reason: "la ra\xEDz no es un mapa de claves y valores" };
13189
+ const raw = data["schema_version"];
13190
+ if (raw === void 0) return { state: "absent" };
13191
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) return { state: "unreadable", reason: `schema_version con valor no v\xE1lido: ${String(raw)}` };
13192
+ return { state: "ok", version: raw };
13193
+ }
13194
+ function stampSchemaVersion(content, version) {
13195
+ const line = `schema_version: ${version}`;
13196
+ const bom = content.startsWith("\uFEFF") ? "\uFEFF" : "";
13197
+ const body = bom ? content.slice(1) : content;
13198
+ if (body.trim() === "") return `${bom}${line}
13199
+ `;
13200
+ const eol = body.includes("\r\n") ? "\r\n" : "\n";
13201
+ const lines = body.split(/\r?\n/);
13202
+ const trailing = lines.length > 1 && lines[lines.length - 1] === "";
13203
+ let index = 0;
13204
+ while (index < lines.length) {
13205
+ const trimmed = (lines[index] ?? "").trim();
13206
+ if (trimmed === "" || trimmed.startsWith("#")) {
13207
+ index += 1;
13208
+ continue;
13209
+ }
13210
+ break;
13211
+ }
13212
+ if ((lines[index] ?? "").trim() === "---") index += 1;
13213
+ const insertAt = trailing ? Math.min(index, lines.length - 1) : index;
13214
+ lines.splice(insertAt, 0, line);
13215
+ const next = bom + lines.join(eol);
13216
+ const check = readArtifactVersion(next);
13217
+ if (check.state !== "ok" || check.version !== version) return void 0;
13218
+ return next;
13219
+ }
13220
+ async function planUpgrade(root) {
13221
+ const artifacts = await collectVersionedArtifacts(root);
13222
+ const pending = [];
13223
+ const newer = [];
13224
+ const unreadable = [];
13225
+ for (const found of artifacts) {
13226
+ const content = await readTextIfExists(found.path);
13227
+ if (content === void 0) continue;
13228
+ const state = readArtifactVersion(content);
13229
+ if (state.state === "unreadable") {
13230
+ unreadable.push({ ...found, reason: state.reason });
13231
+ continue;
13232
+ }
13233
+ if (state.state === "ok") {
13234
+ if (state.version > SCHEMA_VERSION) {
13235
+ newer.push({ ...found, reason: `produce la versi\xF3n ${state.version}, m\xE1s nueva que la vigente (${SCHEMA_VERSION})` });
13236
+ continue;
13237
+ }
13238
+ if (state.version === SCHEMA_VERSION) continue;
13239
+ }
13240
+ const from = state.state === "ok" ? state.version : 0;
13241
+ const migration = SCHEMA_MIGRATIONS.find((m) => m.from === from);
13242
+ if (!migration) {
13243
+ unreadable.push({ ...found, reason: `no hay migraci\xF3n registrada desde la versi\xF3n ${from}` });
13244
+ continue;
13245
+ }
13246
+ const stamped = stampSchemaVersion(content, migration.to);
13247
+ if (stamped === void 0) {
13248
+ unreadable.push({ ...found, reason: "no se pudo sellar con seguridad sin alterar el resto del contenido" });
13249
+ continue;
13250
+ }
13251
+ pending.push({ ...found, from, to: migration.to, migration: migration.id, summary: migration.description });
13252
+ }
13253
+ return { root, currentVersion: SCHEMA_VERSION, pending, newer, unreadable, upToDate: pending.length === 0 };
13254
+ }
13255
+ async function removeDir(dir) {
13256
+ try {
13257
+ await fs2.rm(dir, { recursive: true, force: true });
13258
+ } catch {
13259
+ }
13260
+ }
13261
+ async function removeFile2(file) {
13262
+ try {
13263
+ await fs2.rm(file, { force: true });
13264
+ } catch {
13265
+ }
13266
+ }
13267
+ function backupName(now) {
13268
+ return `${localCompact(now)}${localOffset(now).replace(":", "")}`;
13269
+ }
13270
+ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
13271
+ const plan = await planUpgrade(root);
13272
+ if (plan.pending.length === 0) return { status: "up-to-date", plan, applied: [] };
13273
+ const sddDir = path18.join(root, ".sdd");
13274
+ const backupRoot = path18.join(sddDir, BACKUP_DIRNAME);
13275
+ const name = backupName(now);
13276
+ const backupDir = path18.join(backupRoot, name);
13277
+ const pointerFile = path18.join(backupRoot, BACKUP_POINTER);
13278
+ const previous = [];
13279
+ try {
13280
+ for (const item of plan.pending) {
13281
+ const content = await readTextIfExists(item.path);
13282
+ if (content === void 0) throw new Error("el elemento desapareci\xF3 mientras se respaldaba");
13283
+ previous.push({ item, contents: content });
13284
+ await writeText(path18.join(backupDir, "files", ...item.artifact.split("/")), content);
13285
+ }
13286
+ await writeText(
13287
+ path18.join(backupDir, "backup.yaml"),
13288
+ stringifyYaml6(
13289
+ {
13290
+ schema_version: SCHEMA_VERSION,
13291
+ created_at: localStamp(now),
13292
+ to_version: SCHEMA_VERSION,
13293
+ files: plan.pending.map((i) => ({ artifact: i.artifact, from: i.from, to: i.to, migration: i.migration }))
13294
+ },
13295
+ { lineWidth: 120 }
13296
+ )
13297
+ );
13298
+ await writeText(pointerFile, `${name}
13299
+ `);
13300
+ } catch (err) {
13301
+ await removeDir(backupDir);
13302
+ await removeFile2(pointerFile);
13303
+ return { status: "failed", plan, applied: [], failure: { artifact: "(respaldo)", message: err.message } };
13304
+ }
13305
+ const written = [];
13306
+ for (const { item, contents } of previous) {
13307
+ try {
13308
+ const next = stampSchemaVersion(contents, item.to);
13309
+ if (next === void 0) throw new Error("no se pudo sellar con seguridad");
13310
+ await writeText(item.path, next);
13311
+ written.push(item);
13312
+ } catch (err) {
13313
+ for (const w of written) {
13314
+ const prev = previous.find((p) => p.item.artifact === w.artifact);
13315
+ if (prev) {
13316
+ try {
13317
+ await writeText(prev.item.path, prev.contents);
13318
+ } catch {
13319
+ }
13320
+ }
13321
+ }
13322
+ await removeFile2(pointerFile);
13323
+ await removeDir(backupDir);
13324
+ return { status: "failed", plan, applied: [], failure: { artifact: item.artifact, message: err.message } };
13325
+ }
13326
+ }
13327
+ for (const dir of await listDirs(backupRoot)) {
13328
+ if (dir !== name) await removeDir(path18.join(backupRoot, dir));
13329
+ }
13330
+ return {
13331
+ status: "applied",
13332
+ plan,
13333
+ applied: written,
13334
+ backup: {
13335
+ name,
13336
+ dir: backupDir,
13337
+ relativeDir: toPosix(path18.relative(root, backupDir)),
13338
+ createdAt: localStamp(now),
13339
+ files: plan.pending.map((i) => i.artifact)
13340
+ }
13341
+ };
13342
+ }
13343
+ async function rollbackUpgrade(root) {
13344
+ const sddDir = path18.join(root, ".sdd");
13345
+ const backupRoot = path18.join(sddDir, BACKUP_DIRNAME);
13346
+ const pointerFile = path18.join(backupRoot, BACKUP_POINTER);
13347
+ const name = (await readTextIfExists(pointerFile))?.trim();
13348
+ if (!name) return { status: "no-backup", restored: [] };
13349
+ const backupDir = path18.join(backupRoot, name);
13350
+ const manifestRaw = await readTextIfExists(path18.join(backupDir, "backup.yaml"));
13351
+ if (manifestRaw === void 0) return { status: "no-backup", restored: [] };
13352
+ let files = [];
13353
+ try {
13354
+ const parsed = parseYaml8(manifestRaw);
13355
+ files = (parsed?.files ?? []).filter((f) => typeof f?.artifact === "string");
13356
+ } catch {
13357
+ return { status: "no-backup", restored: [] };
13358
+ }
13359
+ const restored = [];
13360
+ for (const file of files) {
13361
+ const from = path18.join(backupDir, "files", ...file.artifact.split("/"));
13362
+ const to = path18.join(sddDir, ...file.artifact.split("/"));
13363
+ const content = await readTextIfExists(from);
13364
+ if (content === void 0) continue;
13365
+ try {
13366
+ await writeText(to, content);
13367
+ restored.push(file.artifact);
13368
+ } catch (err) {
13369
+ return { status: "failed", restored, backup: name, failure: { artifact: file.artifact, message: err.message } };
13370
+ }
13371
+ }
13372
+ await removeDir(backupDir);
13373
+ await removeFile2(pointerFile);
13374
+ return { status: "restored", restored, backup: name };
13375
+ }
13376
+ async function upgradeAdvisory(root) {
13377
+ const plan = await planUpgrade(root);
13378
+ const diagnostics = [];
13379
+ if (plan.pending.length > 0) {
13380
+ diagnostics.push(
13381
+ diag("ATLAS-UPGRADE-001", "warning", `El estado del proyecto no corresponde a la versi\xF3n vigente: ${plan.pending.length} elemento(s) por actualizar`, {
13382
+ suggestion: "Vista previa: `satlas upgrade` \xB7 Aplicar: `satlas upgrade --apply`"
13383
+ })
13384
+ );
13385
+ }
13386
+ for (const item of plan.newer) {
13387
+ diagnostics.push(
13388
+ diag("ATLAS-UPGRADE-002", "warning", `El proyecto fue producido por una versi\xF3n m\xE1s nueva: "${item.artifact}" (${item.reason})`, {
13389
+ path: item.path,
13390
+ suggestion: "Actualiza la herramienta; no se actualiza hacia atr\xE1s"
13391
+ })
13392
+ );
13393
+ }
13394
+ for (const item of plan.unreadable) {
13395
+ diagnostics.push(
13396
+ diag("ATLAS-UPGRADE-003", "warning", `No se pudo interpretar "${item.artifact}": ${item.reason}`, {
13397
+ path: item.path,
13398
+ suggestion: "Revisa el elemento; no se modificar\xE1"
13399
+ })
13400
+ );
13401
+ }
13402
+ return { plan, diagnostics };
13403
+ }
13404
+ var SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
13405
+ var SARIF_VERSION = "2.1.0";
13406
+ var TOOL_NAME = "SpecAtlas";
13407
+ var TOOL_URL = "https://github.com/AlonsoAM/specatlas";
13408
+ var RULE_FAMILIES = [
13409
+ [/^LINT-STR-/, "Estructura de los artefactos"],
13410
+ [/^LINT-BIZ-/, "Lenguaje de negocio de la especificaci\xF3n"],
13411
+ [/^LINT-DLT-/, "Delta de especificaci\xF3n"],
13412
+ [/^LINT-EVD-/, "Evidencia registrada"],
13413
+ [/^LINT-TSK-/, "Tareas declaradas"],
13414
+ [/^LINT-PLN-/, "Plan t\xE9cnico"],
13415
+ [/^(LINT-MKP|MKP)-/, "Mockups"],
13416
+ [/^TRACE-/, "Trazabilidad requisito, escenario, tarea y evidencia"],
13417
+ [/^PACK-/, "Pack de cumplimiento"],
13418
+ [/^ATLAS-UPGRADE-/, "Actualizaci\xF3n del estado del proyecto"],
13419
+ [/^ATLAS-ADAPTERS-/, "Adaptadores de agente"],
13420
+ [/^ATLAS-CI-/, "Comprobaci\xF3n continua"],
13421
+ [/^ATLAS-/, "Estado del proyecto"]
13422
+ ];
13423
+ function ruleDescription(code) {
13424
+ for (const [pattern, description] of RULE_FAMILIES) {
13425
+ if (pattern.test(code)) return description;
13426
+ }
13427
+ return code;
13428
+ }
13429
+ function sarifLevel(severity) {
13430
+ if (severity === "error") return "error";
13431
+ if (severity === "warning") return "warning";
13432
+ return "note";
13433
+ }
13434
+ function resultOf(diagnostic, root) {
13435
+ const result = {
13436
+ ruleId: diagnostic.code,
13437
+ level: sarifLevel(diagnostic.severity),
13438
+ message: { text: diagnostic.message }
13439
+ };
13440
+ if (diagnostic.path) {
13441
+ const rel = path19.relative(root, diagnostic.path);
13442
+ if (rel !== "") {
13443
+ const physicalLocation = {
13444
+ artifactLocation: { uri: toPosix(rel) },
13445
+ ...diagnostic.line ? { region: { startLine: diagnostic.line } } : {}
13446
+ };
13447
+ result.locations = [{ physicalLocation }];
13448
+ }
13449
+ }
13450
+ return result;
13451
+ }
13452
+ function toSarifReport(opts) {
13453
+ const rules = /* @__PURE__ */ new Map();
13454
+ const results = [];
13455
+ for (const diagnostic of opts.diagnostics) {
13456
+ let rule = rules.get(diagnostic.code);
13457
+ if (!rule) {
13458
+ rule = { id: diagnostic.code, shortDescription: { text: ruleDescription(diagnostic.code) } };
13459
+ if (diagnostic.suggestion) rule.help = { text: diagnostic.suggestion };
13460
+ rules.set(diagnostic.code, rule);
13461
+ } else if (!rule.help && diagnostic.suggestion) {
13462
+ rule.help = { text: diagnostic.suggestion };
13463
+ }
13464
+ results.push(resultOf(diagnostic, opts.root));
13465
+ }
13466
+ return {
13467
+ $schema: SARIF_SCHEMA,
13468
+ version: SARIF_VERSION,
13469
+ runs: [
13470
+ {
13471
+ tool: {
13472
+ driver: {
13473
+ name: TOOL_NAME,
13474
+ informationUri: TOOL_URL,
13475
+ version: opts.version,
13476
+ rules: [...rules.values()]
13477
+ }
13478
+ },
13479
+ results,
13480
+ invocations: [{ executionSuccessful: !(opts.failed ?? false) }]
13481
+ }
13482
+ ]
13483
+ };
13484
+ }
13485
+ function toSarifText(opts) {
13486
+ return `${JSON.stringify(toSarifReport(opts), null, 2)}
13487
+ `;
12990
13488
  }
12991
13489
  async function runDoctor(root) {
12992
13490
  const findings = [];
@@ -12995,7 +13493,7 @@ async function runDoctor(root) {
12995
13493
  const approvals = await loadApprovals(workspace.sddDir);
12996
13494
  findings.push(...approvals.diagnostics);
12997
13495
  for (const change of workspace.changes) {
12998
- const deltaPath = path17.join(change.dir, "spec.md");
13496
+ const deltaPath = path20.join(change.dir, "spec.md");
12999
13497
  const deltaContent = await readTextIfExists(deltaPath);
13000
13498
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
13001
13499
  if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
@@ -13017,7 +13515,7 @@ async function runDoctor(root) {
13017
13515
  }
13018
13516
  for (const override of change.meta?.overrides ?? []) {
13019
13517
  if (!override.reason.trim() || !override.by.trim()) {
13020
- findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path17.join(change.dir, "meta.yaml") }));
13518
+ findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path20.join(change.dir, "meta.yaml") }));
13021
13519
  }
13022
13520
  }
13023
13521
  }
@@ -13049,7 +13547,7 @@ function livingRequirementsMap(specs) {
13049
13547
  return map;
13050
13548
  }
13051
13549
  async function runCiGate(opts) {
13052
- const root = path18.resolve(opts.root);
13550
+ const root = path21.resolve(opts.root);
13053
13551
  const { workspace, config } = await loadWorkspace(root);
13054
13552
  const diagnostics = [];
13055
13553
  const checks = [];
@@ -13060,7 +13558,7 @@ async function runCiGate(opts) {
13060
13558
  let changesErrors = 0;
13061
13559
  let changesWarnings = 0;
13062
13560
  for (const change of workspace.changes) {
13063
- const lintFindings = change.delta ? lintDelta(change.delta, living, path18.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
13561
+ const lintFindings = change.delta ? lintDelta(change.delta, living, path21.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
13064
13562
  const trace = checkTrace({
13065
13563
  specs: workspace.specs,
13066
13564
  change,
@@ -13169,6 +13667,7 @@ var CATALOG = [
13169
13667
  { name: "trace", description: "Verifica la trazabilidad requisito \u2192 escenario \u2192 tarea \u2192 evidencia", flags: ["change", "require-evidence", "json"], usage: "satlas trace [--change <slug>] [--require-evidence] [--json]" },
13170
13668
  { name: "waves", description: "Calcula las olas paralelas de construcci\xF3n", flags: ["change", "max-parallel", "json"], usage: "satlas waves [--change <slug>] [--max-parallel N] [--json]" },
13171
13669
  { name: "doctor", description: "Diagn\xF3stico de salud del workspace", flags: ["json"], usage: "satlas doctor [--json]" },
13670
+ { name: "upgrade", description: "Actualiza el estado del proyecto a la versi\xF3n vigente (vista previa por defecto)", flags: ["apply", "rollback", "json"], usage: "satlas upgrade [--apply | --rollback] [--json]" },
13172
13671
  { name: "approve", description: "Firma la aprobaci\xF3n de un artefacto (spec): local o desde la etiqueta de un issue de GitHub", flags: ["by", "channel", "note", "dry-run", "from-github", "label", "json"], usage: 'satlas approve <slug|ruta> --by "<nombre>" | satlas approve <slug> --from-github' },
13173
13672
  { name: "issue", description: "Sincroniza el cambio con un issue de GitHub (tracker, no gate)", flags: ["labels", "json"], usage: "satlas issue sync <slug> [--labels a,b] | satlas issue status <slug>" },
13174
13673
  { name: "adapters", description: "Compila (o verifica) los prompts a artefactos de agente", flags: ["targets", "check", "json"], usage: "satlas adapters [--targets opencode,claude-code,cursor,copilot,gemini,codex,generic] [--check]" },
@@ -13179,7 +13678,7 @@ var CATALOG = [
13179
13678
  { name: "analyze", description: "Chequeo cruzado (lint + trace + waves + mockups) y escribe analyze.md", flags: ["json"], usage: "satlas analyze <slug>" },
13180
13679
  { name: "mockup", description: "Planifica, valida o captura los mockups del cambio", flags: ["plan", "check", "capture", "require", "json"], usage: "satlas mockup <slug> [--plan|--check|--capture|--require]" },
13181
13680
  { name: "present", description: "Genera el paquete de propuesta para el stakeholder (HTML autocontenido)", flags: ["json"], usage: "satlas present <slug>" },
13182
- { name: "ci", description: "Gate de pipeline: specs + cambios + doctor + adaptadores (sin agente)", flags: ["strict", "json"], usage: "satlas ci [--strict]" },
13681
+ { name: "ci", description: "Gate de pipeline: specs + cambios + doctor + adaptadores (sin agente)", flags: ["strict", "sarif", "json"], usage: "satlas ci [--strict] [--sarif <ruta>]" },
13183
13682
  { name: "mcp", description: "V\xEDa de consulta de solo lectura para asistentes (protocolo MCP sobre entrada/salida est\xE1ndar)", flags: [], usage: "satlas mcp" },
13184
13683
  { name: "metrics", description: "M\xE9tricas locales del workspace (progreso, WIP, throughput, evidencia)", flags: ["json"], usage: "satlas metrics [--json]" },
13185
13684
  { 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" },
@@ -13201,6 +13700,7 @@ var ES2 = {
13201
13700
  "status.title": "Estado de los cambios",
13202
13701
  "status.changes": "Cambios activos",
13203
13702
  "status.specs": "Specs vivas",
13703
+ "status.fixes": "Fixes vivos",
13204
13704
  "status.next": "siguiente:",
13205
13705
  "status.tasks": "tareas",
13206
13706
  "status.evidence": "evidencia",
@@ -13216,6 +13716,8 @@ var ES2 = {
13216
13716
  "init.done": "Workspace SDD creado.",
13217
13717
  "new.title": "Nuevo cambio",
13218
13718
  "new.done": "Cambio creado. Siguiente: escribe la spec funcional.",
13719
+ "upgrade.title": "Actualizar el estado del proyecto",
13720
+ "upgrade.preview": "Vista previa",
13219
13721
  "summary.findings": "hallazgos",
13220
13722
  "label.errors": "errores",
13221
13723
  "label.warnings": "avisos",
@@ -13233,6 +13735,7 @@ var EN2 = {
13233
13735
  "status.title": "Change status",
13234
13736
  "status.changes": "Active changes",
13235
13737
  "status.specs": "Living specs",
13738
+ "status.fixes": "Living fixes",
13236
13739
  "status.next": "next:",
13237
13740
  "status.tasks": "tasks",
13238
13741
  "status.evidence": "evidence",
@@ -13248,6 +13751,8 @@ var EN2 = {
13248
13751
  "init.done": "SDD workspace created.",
13249
13752
  "new.title": "New change",
13250
13753
  "new.done": "Change created. Next: write the business spec.",
13754
+ "upgrade.title": "Upgrade project state",
13755
+ "upgrade.preview": "Preview",
13251
13756
  "summary.findings": "findings",
13252
13757
  "label.errors": "errors",
13253
13758
  "label.warnings": "warnings",
@@ -13275,26 +13780,26 @@ function cliVersion() {
13275
13780
  }
13276
13781
 
13277
13782
  // src/commands/adapters.ts
13278
- import path21 from "path";
13783
+ import path25 from "path";
13279
13784
 
13280
13785
  // ../adapters/dist/index.js
13281
- import path19 from "path";
13282
- import { stringify as stringifyYaml5 } from "yaml";
13283
13786
  import path22 from "path";
13787
+ import { stringify as stringifyYaml7 } from "yaml";
13788
+ import path23 from "path";
13284
13789
  async function loadWorkflow(workflowDir) {
13285
- const phasesDir = path19.join(workflowDir, "phases");
13286
- const snippetsDir = path19.join(workflowDir, "snippets");
13790
+ const phasesDir = path22.join(workflowDir, "phases");
13791
+ const snippetsDir = path22.join(workflowDir, "snippets");
13287
13792
  const phases = [];
13288
13793
  const snippets = /* @__PURE__ */ new Map();
13289
13794
  const hashParts = [];
13290
13795
  for (const entry of (await listDir(phasesDir)).sort()) {
13291
13796
  if (!entry.endsWith(".md")) continue;
13292
- const filePath = path19.join(phasesDir, entry);
13797
+ const filePath = path22.join(phasesDir, entry);
13293
13798
  const raw = await readText(filePath);
13294
13799
  hashParts.push(raw);
13295
13800
  const fm = parseFrontmatter(raw, filePath);
13296
13801
  const data = fm.data;
13297
- const id = typeof data["id"] === "string" ? data["id"] : path19.basename(entry, ".md");
13802
+ const id = typeof data["id"] === "string" ? data["id"] : path22.basename(entry, ".md");
13298
13803
  phases.push({
13299
13804
  id,
13300
13805
  title: typeof data["title"] === "string" ? data["title"] : id,
@@ -13308,9 +13813,9 @@ async function loadWorkflow(workflowDir) {
13308
13813
  }
13309
13814
  for (const entry of (await listDir(snippetsDir)).sort()) {
13310
13815
  if (!entry.endsWith(".md")) continue;
13311
- const raw = await readTextIfExists(path19.join(snippetsDir, entry)) ?? "";
13816
+ const raw = await readTextIfExists(path22.join(snippetsDir, entry)) ?? "";
13312
13817
  hashParts.push(raw);
13313
- snippets.set(path19.basename(entry, ".md"), raw.trim());
13818
+ snippets.set(path22.basename(entry, ".md"), raw.trim());
13314
13819
  }
13315
13820
  return { phases: phases.sort((a, b) => a.id.localeCompare(b.id)), snippets, sourceHash: sha256(hashParts.join("\n---\n")) };
13316
13821
  }
@@ -13334,7 +13839,7 @@ function vars(language, slugToken, commandPrefix) {
13334
13839
  return { SLUG: slugToken, LANGUAGE: language, LANGUAGE_NAME: LANGUAGE_NAMES[language], SDD_DIR: ".sdd", COMMAND_PREFIX: commandPrefix };
13335
13840
  }
13336
13841
  function frontmatter(data, body) {
13337
- const yaml2 = stringifyYaml5(data, { lineWidth: 0 }).trimEnd();
13842
+ const yaml2 = stringifyYaml7(data, { lineWidth: 0 }).trimEnd();
13338
13843
  return `---
13339
13844
  ${yaml2}
13340
13845
  ---
@@ -13492,11 +13997,11 @@ Reglas: la spec es funcional y de negocio (sin tecnolog\xEDa); la trazabilidad e
13492
13997
  function tomlString(value) {
13493
13998
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
13494
13999
  }
13495
- var MANIFEST_REL = path22.join(".sdd", ".generated", "manifest.json");
14000
+ var MANIFEST_REL = path23.join(".sdd", ".generated", "manifest.json");
13496
14001
  var BEGIN = "<!-- BEGIN specatlas -->";
13497
14002
  var END = "<!-- END specatlas -->";
13498
14003
  async function compileTargets(opts) {
13499
- const root = path22.resolve(opts.root);
14004
+ const root = path23.resolve(opts.root);
13500
14005
  const language = opts.language ?? "es";
13501
14006
  const diagnostics = [];
13502
14007
  const sources = await loadWorkflow(opts.workflowDir);
@@ -13504,7 +14009,7 @@ async function compileTargets(opts) {
13504
14009
  diagnostics.push({
13505
14010
  code: "ATLAS-ADAPTERS-002",
13506
14011
  severity: "error",
13507
- message: `No hay fases en ${path22.join(opts.workflowDir, "phases")}`,
14012
+ message: `No hay fases en ${path23.join(opts.workflowDir, "phases")}`,
13508
14013
  suggestion: "Revisa la carpeta workflow/phases del proyecto"
13509
14014
  });
13510
14015
  }
@@ -13516,7 +14021,7 @@ async function compileTargets(opts) {
13516
14021
  }
13517
14022
  }
13518
14023
  const compiled = [...deduped.values()];
13519
- const manifestPath = path22.join(root, MANIFEST_REL);
14024
+ const manifestPath = path23.join(root, MANIFEST_REL);
13520
14025
  const previous = await readManifest(manifestPath);
13521
14026
  const previousHashes = /* @__PURE__ */ new Map();
13522
14027
  for (const files2 of Object.values(previous?.targets ?? {})) {
@@ -13527,7 +14032,7 @@ async function compileTargets(opts) {
13527
14032
  const stale = [];
13528
14033
  const missing = [];
13529
14034
  for (const file of compiled) {
13530
- const abs = path22.join(root, file.path);
14035
+ const abs = path23.join(root, file.path);
13531
14036
  const finalContent = file.path === "AGENTS.md" ? mergeAgentsBlock(await readTextIfExists(abs) ?? "", file.content) : file.content;
13532
14037
  const hash = artifactHash(finalContent);
13533
14038
  const existing = await readTextIfExists(abs);
@@ -13543,7 +14048,7 @@ async function compileTargets(opts) {
13543
14048
  stale.push(file.path);
13544
14049
  }
13545
14050
  if (!opts.check && status !== "unchanged") {
13546
- await ensureDir(path22.dirname(abs));
14051
+ await ensureDir(path23.dirname(abs));
13547
14052
  await writeText(abs, finalContent);
13548
14053
  written.push(file.path);
13549
14054
  }
@@ -13597,7 +14102,7 @@ async function readManifest(manifestPath) {
13597
14102
  }
13598
14103
  }
13599
14104
  async function checkAdapters(opts) {
13600
- const manifestPath = path22.join(path22.resolve(opts.root), MANIFEST_REL);
14105
+ const manifestPath = path23.join(path23.resolve(opts.root), MANIFEST_REL);
13601
14106
  const manifestRaw = await readTextIfExists(manifestPath);
13602
14107
  if (manifestRaw === void 0) return { ok: false, manifest: false, stale: [], missing: [], orphaned: [] };
13603
14108
  const manifest = await readManifest(manifestPath);
@@ -13614,7 +14119,7 @@ async function checkAdapters(opts) {
13614
14119
  }
13615
14120
 
13616
14121
  // src/paths.ts
13617
- import path20 from "path";
14122
+ import path24 from "path";
13618
14123
  import { fileURLToPath as fileURLToPath2 } from "url";
13619
14124
  async function resolveProfilesDir() {
13620
14125
  const env = process.env["SPECATLAS_PROFILES_DIR"];
@@ -13624,16 +14129,16 @@ async function resolveProfilesDir() {
13624
14129
  async function resolveWorkflowDir() {
13625
14130
  const env = process.env["SPECATLAS_WORKFLOW_DIR"];
13626
14131
  if (env && await exists(env)) return env;
13627
- return walkUpFor(path20.join("workflow", "phases"));
14132
+ return walkUpFor(path24.join("workflow", "phases"));
13628
14133
  }
13629
14134
  async function walkUpFor(relative) {
13630
- let dir = path20.dirname(fileURLToPath2(import.meta.url));
14135
+ let dir = path24.dirname(fileURLToPath2(import.meta.url));
13631
14136
  for (let i = 0; i < 8; i += 1) {
13632
- const candidate = path20.join(dir, relative);
14137
+ const candidate = path24.join(dir, relative);
13633
14138
  if (await exists(candidate)) {
13634
- return relative.includes(path20.sep) ? path20.dirname(candidate) : candidate;
14139
+ return relative.includes(path24.sep) ? path24.dirname(candidate) : candidate;
13635
14140
  }
13636
- const parent = path20.dirname(dir);
14141
+ const parent = path24.dirname(dir);
13637
14142
  if (parent === dir) break;
13638
14143
  dir = parent;
13639
14144
  }
@@ -13711,7 +14216,7 @@ async function runAdapters(ctx) {
13711
14216
  for (const [target, count2] of byTarget) lines.push(` ${target}: ${count2} archivo(s)`);
13712
14217
  if (report.written.length > 0) {
13713
14218
  lines.push("");
13714
- for (const p of report.written) lines.push(` + ${path21.relative(ctx.cwd, path21.join(root, p))}`);
14219
+ for (const p of report.written) lines.push(` + ${path25.relative(ctx.cwd, path25.join(root, p))}`);
13715
14220
  } else {
13716
14221
  lines.push("");
13717
14222
  lines.push("Sin cambios: los adaptadores ya estaban al d\xEDa.");
@@ -13722,7 +14227,7 @@ async function runAdapters(ctx) {
13722
14227
  }
13723
14228
 
13724
14229
  // src/commands/adopt.ts
13725
- import path23 from "path";
14230
+ import path26 from "path";
13726
14231
  async function runAdopt(ctx) {
13727
14232
  const { root } = await requireWorkspace(ctx);
13728
14233
  const domainsFlag = flagString(ctx.flags, "domains") ?? flagString(ctx.flags, "domain");
@@ -13742,11 +14247,11 @@ async function runAdopt(ctx) {
13742
14247
  }
13743
14248
  if (result.createdSpecs.length > 0) {
13744
14249
  lines.push("");
13745
- for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${path23.relative(ctx.cwd, spec)}`);
14250
+ for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${path26.relative(ctx.cwd, spec)}`);
13746
14251
  }
13747
14252
  if (result.reportPath && !result.dryRun) {
13748
14253
  lines.push("");
13749
- lines.push(` Informe: ${path23.relative(ctx.cwd, result.reportPath)}`);
14254
+ lines.push(` Informe: ${path26.relative(ctx.cwd, result.reportPath)}`);
13750
14255
  }
13751
14256
  lines.push("");
13752
14257
  lines.push("Siguiente: fase /satlas-adopt por dominio (requisitos AS-IS) y luego:");
@@ -13759,8 +14264,8 @@ async function runAdopt(ctx) {
13759
14264
  diagnostics: result.diagnostics,
13760
14265
  data: {
13761
14266
  domains: result.domains.map((d) => ({ name: d.name, files: d.files, existingSpec: d.existingSpec })),
13762
- createdSpecs: result.createdSpecs.map((s) => path23.relative(ctx.cwd, s)),
13763
- reportPath: result.reportPath ? path23.relative(ctx.cwd, result.reportPath) : void 0,
14267
+ createdSpecs: result.createdSpecs.map((s) => path26.relative(ctx.cwd, s)),
14268
+ reportPath: result.reportPath ? path26.relative(ctx.cwd, result.reportPath) : void 0,
13764
14269
  stack: result.stack,
13765
14270
  dryRun: result.dryRun
13766
14271
  },
@@ -13769,7 +14274,7 @@ async function runAdopt(ctx) {
13769
14274
  }
13770
14275
 
13771
14276
  // src/commands/analyze.ts
13772
- import path24 from "path";
14277
+ import path27 from "path";
13773
14278
  async function runAnalyzeCommand(ctx) {
13774
14279
  const { root } = await requireWorkspace(ctx);
13775
14280
  const slug = ctx.positionals[0];
@@ -13780,23 +14285,23 @@ async function runAnalyzeCommand(ctx) {
13780
14285
  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`];
13781
14286
  if (result.waves) lines.push(` olas: ${result.waves.blocks} bloque(s), ${result.waves.waves} ola(s), ${result.waves.tasks} tarea(s)`);
13782
14287
  if (result.mockups) lines.push(` mockups: ${result.mockups.screens} pantalla(s)${result.mockups.stale ? " (desactualizados)" : ""}`);
13783
- if (result.path) lines.push("", ` informe: ${path24.relative(ctx.cwd, result.path)}`);
14288
+ if (result.path) lines.push("", ` informe: ${path27.relative(ctx.cwd, result.path)}`);
13784
14289
  for (const finding of result.findings) {
13785
14290
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}`);
13786
14291
  }
13787
14292
  return {
13788
14293
  exitCode: result.status === "blocked" ? 1 : 0,
13789
14294
  diagnostics: result.findings,
13790
- data: result.path ? { ...result, path: path24.relative(ctx.cwd, result.path) } : result,
14295
+ data: result.path ? { ...result, path: path27.relative(ctx.cwd, result.path) } : result,
13791
14296
  text: lines
13792
14297
  };
13793
14298
  }
13794
14299
 
13795
14300
  // src/commands/approve.ts
13796
- import path26 from "path";
14301
+ import path29 from "path";
13797
14302
 
13798
14303
  // src/commands/issue.ts
13799
- import path25 from "path";
14304
+ import path28 from "path";
13800
14305
  async function runIssue(ctx) {
13801
14306
  const action = ctx.positionals[0] ?? "status";
13802
14307
  const slug = ctx.positionals[1];
@@ -13824,7 +14329,7 @@ async function runIssue(ctx) {
13824
14329
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-ISSUE-000", severity: "error", message: "Falta el slug: satlas issue status <slug>" }] };
13825
14330
  }
13826
14331
  const change = await loadChange(root, slug);
13827
- const { config } = await loadConfig(path25.join(root, ".sdd"));
14332
+ const { config } = await loadConfig(path28.join(root, ".sdd"));
13828
14333
  const tracker = change.meta?.tracker;
13829
14334
  const lines = [`Issue de ${slug}`, ""];
13830
14335
  if (!tracker || tracker.provider !== "github") {
@@ -13895,7 +14400,7 @@ async function runApprove(ctx) {
13895
14400
  };
13896
14401
  }
13897
14402
  const { root, workspace, config } = await requireWorkspace(ctx);
13898
- const artifact = target.includes("/") || target.includes("\\") ? target : path26.join("changes", target, "spec.md");
14403
+ const artifact = target.includes("/") || target.includes("\\") ? target : path29.join("changes", target, "spec.md");
13899
14404
  const channelFlag = flagString(ctx.flags, "channel");
13900
14405
  const channel = channelFlag === "presentation" || channelFlag === "editor" || channelFlag === "pr" || channelFlag === "tracker" ? channelFlag : "cli";
13901
14406
  const change = workspace.changes.find(
@@ -13936,13 +14441,13 @@ async function runApprove(ctx) {
13936
14441
  return {
13937
14442
  exitCode: hasErrors2 ? 1 : 0,
13938
14443
  diagnostics: result.diagnostics,
13939
- data: result.approval ? { ...result.approval, file: path26.relative(ctx.cwd, result.file) } : void 0,
14444
+ data: result.approval ? { ...result.approval, file: path29.relative(ctx.cwd, result.file) } : void 0,
13940
14445
  text: lines
13941
14446
  };
13942
14447
  }
13943
14448
 
13944
14449
  // src/commands/archive.ts
13945
- import path27 from "path";
14450
+ import path30 from "path";
13946
14451
  async function runArchive(ctx) {
13947
14452
  const slug = ctx.positionals[0];
13948
14453
  if (!slug) {
@@ -13966,29 +14471,35 @@ async function runArchive(ctx) {
13966
14471
  }
13967
14472
  const result = await archiveChange({ root, slug, language: config.project.language, dryRun });
13968
14473
  const lines = [msg("archive.title", ctx.language), ""];
13969
- if (result.fold.applied.added.length + result.fold.applied.modified.length + result.fold.applied.removed.length + result.fold.applied.renamed.length > 0) {
14474
+ const operations = result.fold.applied.added.length + result.fold.applied.modified.length + result.fold.applied.removed.length + result.fold.applied.renamed.length;
14475
+ if (operations > 0) {
13970
14476
  lines.push(`${dryRun ? "[dry-run] " : ""}${msg("archive.done", ctx.language)}`);
13971
14477
  lines.push(` agregados: ${result.fold.applied.added.join(", ") || "\u2014"}`);
13972
14478
  lines.push(` modificados: ${result.fold.applied.modified.join(", ") || "\u2014"}`);
13973
14479
  lines.push(` eliminados: ${result.fold.applied.removed.join(", ") || "\u2014"}`);
13974
14480
  lines.push(` renombrados: ${result.fold.applied.renamed.join(", ") || "\u2014"}`);
13975
- if (result.archivedTo) lines.push(` archivado en: ${path27.relative(ctx.cwd, result.archivedTo)}`);
14481
+ } else if (result.livingFix) {
14482
+ lines.push(`${dryRun ? "[dry-run] " : ""}Fix archivado y conservado como fix vivo.`);
14483
+ lines.push(` fix vivo: ${result.livingFix}`);
13976
14484
  } else {
13977
14485
  lines.push("El delta no contiene operaciones (ADDED/MODIFIED/REMOVED/RENAMED).");
13978
14486
  }
14487
+ if (result.archivedTo) lines.push(` archivado en: ${path30.relative(ctx.cwd, result.archivedTo)}`);
13979
14488
  const hasErrors2 = result.diagnostics.some((d) => d.severity === "error");
13980
14489
  return {
13981
14490
  exitCode: hasErrors2 ? 1 : 0,
13982
14491
  diagnostics: result.diagnostics,
13983
- data: { slug: result.slug, domain: result.domain, archivedTo: result.archivedTo, applied: result.fold.applied, dryRun: result.dryRun },
14492
+ data: { slug: result.slug, domain: result.domain, archivedTo: result.archivedTo, livingFix: result.livingFix, applied: result.fold.applied, dryRun: result.dryRun },
13984
14493
  text: lines
13985
14494
  };
13986
14495
  }
13987
14496
 
13988
14497
  // src/commands/ci.ts
14498
+ import path31 from "path";
13989
14499
  async function runCi(ctx) {
13990
14500
  const { root, config } = await requireWorkspace(ctx);
13991
14501
  const strict = flagBool(ctx.flags, "strict");
14502
+ const sarifPath = flagString(ctx.flags, "sarif");
13992
14503
  const extra = [];
13993
14504
  const workflowDir = await resolveWorkflowDir();
13994
14505
  if (workflowDir) {
@@ -14006,28 +14517,54 @@ async function runCi(ctx) {
14006
14517
  });
14007
14518
  }
14008
14519
  const gate = await runCiGate({ root, strict, extra });
14520
+ const diagnostics = [...gate.diagnostics];
14009
14521
  const lines = ["CI de SpecAtlas", ""];
14522
+ let sarifWritten;
14523
+ if (sarifPath) {
14524
+ const target = path31.resolve(ctx.cwd, sarifPath);
14525
+ try {
14526
+ await writeText(target, toSarifText({ diagnostics: gate.diagnostics, root, version: cliVersion(), failed: gate.failed }));
14527
+ sarifWritten = path31.relative(ctx.cwd, target);
14528
+ } catch (err) {
14529
+ diagnostics.push({
14530
+ code: "ATLAS-CI-SARIF-001",
14531
+ severity: "warning",
14532
+ message: `No se pudo escribir el informe SARIF en "${sarifPath}": ${err.message}`,
14533
+ suggestion: "Revisa que la ruta sea escribible; el veredicto de la comprobaci\xF3n no cambia"
14534
+ });
14535
+ }
14536
+ }
14010
14537
  for (const check of gate.checks) {
14011
14538
  lines.push(` ${check.errors === 0 && check.warnings === 0 ? "OK " : "FALLA"} ${check.name}: ${check.errors} errores, ${check.warnings} avisos`);
14012
14539
  }
14013
14540
  lines.push("");
14014
14541
  lines.push(gate.failed ? "Resultado: BLOQUEADO" : "Resultado: OK");
14015
14542
  if (strict) lines.push("(modo estricto: los avisos tambi\xE9n bloquean)");
14543
+ if (sarifPath) {
14544
+ lines.push(sarifWritten ? `Informe SARIF: ${sarifWritten} (${gate.diagnostics.length} hallazgo(s))` : `Informe SARIF: no se pudo escribir en "${sarifPath}"`);
14545
+ }
14016
14546
  return {
14017
14547
  exitCode: gate.failed ? 1 : 0,
14018
- diagnostics: gate.diagnostics,
14019
- data: { checks: gate.checks, errors: gate.errors, warnings: gate.warnings, strict, failed: gate.failed },
14548
+ diagnostics,
14549
+ data: {
14550
+ checks: gate.checks,
14551
+ errors: gate.errors,
14552
+ warnings: gate.warnings,
14553
+ strict,
14554
+ failed: gate.failed,
14555
+ ...sarifWritten ? { sarif: sarifWritten } : {}
14556
+ },
14020
14557
  text: lines
14021
14558
  };
14022
14559
  }
14023
14560
 
14024
14561
  // src/commands/profile.ts
14025
- import path28 from "path";
14562
+ import path32 from "path";
14026
14563
  async function runProfile(ctx) {
14027
14564
  const action = ctx.positionals[0] ?? "detect";
14028
14565
  const { root, workspace } = await requireWorkspace(ctx);
14029
14566
  const officialDir = await resolveProfilesDir();
14030
- const customDir = path28.join(workspace.sddDir, "profiles", "custom");
14567
+ const customDir = path32.join(workspace.sddDir, "profiles", "custom");
14031
14568
  const profiles = [...officialDir ? await loadProfilesFromDir(officialDir) : [], ...await loadProfilesFromDir(customDir)];
14032
14569
  if (action === "detect") {
14033
14570
  const result = await detectProfiles(root, profiles);
@@ -14056,7 +14593,7 @@ async function runProfile(ctx) {
14056
14593
  if (!name) {
14057
14594
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-001", severity: "error", message: "Falta el nombre del perfil: satlas profile create <nombre>" }] };
14058
14595
  }
14059
- const file = path28.join(customDir, `${name}.yaml`);
14596
+ const file = path32.join(customDir, `${name}.yaml`);
14060
14597
  if (await exists(file)) {
14061
14598
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-002", severity: "error", message: `El perfil ya existe: ${file}` }] };
14062
14599
  }
@@ -14065,7 +14602,7 @@ async function runProfile(ctx) {
14065
14602
  exitCode: 0,
14066
14603
  diagnostics: [],
14067
14604
  data: { file },
14068
- text: [`Perfil creado: ${path28.relative(ctx.cwd, file)}`, "", "Ed\xEDtalo con los comandos reales de tu stack (build/lint/test) y tus anti-patrones."]
14605
+ text: [`Perfil creado: ${path32.relative(ctx.cwd, file)}`, "", "Ed\xEDtalo con los comandos reales de tu stack (build/lint/test) y tus anti-patrones."]
14069
14606
  };
14070
14607
  }
14071
14608
  return {
@@ -14074,7 +14611,7 @@ async function runProfile(ctx) {
14074
14611
  };
14075
14612
  }
14076
14613
  async function isInside(dir, name) {
14077
- return exists(path28.join(dir, `${name}.yaml`));
14614
+ return exists(path32.join(dir, `${name}.yaml`));
14078
14615
  }
14079
14616
  function profileTemplate(name) {
14080
14617
  return `name: ${name}
@@ -14102,7 +14639,7 @@ assumptions: []
14102
14639
  }
14103
14640
 
14104
14641
  // src/commands/doctor.ts
14105
- import path29 from "path";
14642
+ import path33 from "path";
14106
14643
  async function runDoctorCommand(ctx) {
14107
14644
  const { root, config } = await requireWorkspace(ctx);
14108
14645
  const report = await runDoctor(root);
@@ -14119,12 +14656,15 @@ async function runDoctorCommand(ctx) {
14119
14656
  report.summary.warnings += 1;
14120
14657
  }
14121
14658
  }
14659
+ const advisory = await upgradeAdvisory(root);
14660
+ report.findings.push(...advisory.diagnostics);
14661
+ report.summary = countBySeverity(report.findings);
14122
14662
  const lines = [msg("doctor.title", ctx.language), ""];
14123
14663
  if (report.findings.length === 0) {
14124
14664
  lines.push("Sin hallazgos. El workspace est\xE1 sano.");
14125
14665
  } else {
14126
14666
  for (const finding of report.findings) {
14127
- const location = finding.path ? ` ${path29.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
14667
+ const location = finding.path ? ` ${path33.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
14128
14668
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${location} \u2014 ${finding.message}`);
14129
14669
  if (finding.suggestion) lines.push(` \u21B3 ${finding.suggestion}`);
14130
14670
  }
@@ -14198,7 +14738,7 @@ async function runHelp(ctx, commandName) {
14198
14738
  }
14199
14739
 
14200
14740
  // src/commands/init.ts
14201
- import path30 from "path";
14741
+ import path34 from "path";
14202
14742
  async function runInit(ctx) {
14203
14743
  const languageFlag = flagString(ctx.flags, "language");
14204
14744
  const language = languageFlag === "en" ? "en" : languageFlag === "es" ? "es" : void 0;
@@ -14245,11 +14785,11 @@ async function runInit(ctx) {
14245
14785
  const lines = [msg("init.title", ctx.language), ""];
14246
14786
  if (result.created.length > 0) {
14247
14787
  lines.push(msg("init.done", ctx.language));
14248
- for (const file of result.created) lines.push(` + ${path30.relative(ctx.cwd, file)}`);
14788
+ for (const file of result.created) lines.push(` + ${path34.relative(ctx.cwd, file)}`);
14249
14789
  if (compiled.length > 0) {
14250
14790
  lines.push("");
14251
14791
  lines.push("Adaptadores compilados:");
14252
- for (const file of compiled) lines.push(` + ${path30.relative(ctx.cwd, path30.join(ctx.cwd, file))}`);
14792
+ for (const file of compiled) lines.push(` + ${path34.relative(ctx.cwd, path34.join(ctx.cwd, file))}`);
14253
14793
  }
14254
14794
  lines.push("");
14255
14795
  lines.push("Siguiente: crea un cambio con `satlas new <slug>` y escribe la spec funcional.");
@@ -14259,8 +14799,8 @@ async function runInit(ctx) {
14259
14799
  exitCode: hasErrors2 ? 1 : 0,
14260
14800
  diagnostics: result.diagnostics,
14261
14801
  data: {
14262
- sddDir: path30.relative(ctx.cwd, result.sddDir),
14263
- created: result.created.map((f) => path30.relative(ctx.cwd, f)),
14802
+ sddDir: path34.relative(ctx.cwd, result.sddDir),
14803
+ created: result.created.map((f) => path34.relative(ctx.cwd, f)),
14264
14804
  detected: result.detected?.matches.map((m) => ({ name: m.name, score: m.score })),
14265
14805
  best: result.detected?.best?.name
14266
14806
  },
@@ -14301,7 +14841,7 @@ async function runMetrics(ctx) {
14301
14841
  }
14302
14842
 
14303
14843
  // src/commands/mockup.ts
14304
- import path31 from "path";
14844
+ import path35 from "path";
14305
14845
  async function runMockup(ctx) {
14306
14846
  const { root, workspace, config } = await requireWorkspace(ctx);
14307
14847
  const slug = ctx.positionals[0];
@@ -14350,17 +14890,17 @@ async function runMockup(ctx) {
14350
14890
  ` pantallas: ${plan.screens.length}`,
14351
14891
  ...plan.screens.map((s) => ` - ${s.id}: ${s.title} (${s.illustrates.length} escenario(s))`),
14352
14892
  "",
14353
- ` plan: ${path31.relative(ctx.cwd, planFile)}`,
14354
- ` manifiesto: ${path31.relative(ctx.cwd, manifestFile)}`,
14893
+ ` plan: ${path35.relative(ctx.cwd, planFile)}`,
14894
+ ` manifiesto: ${path35.relative(ctx.cwd, manifestFile)}`,
14355
14895
  "",
14356
14896
  "Siguiente: genera el HTML de cada pantalla (fase /satlas-mockup) y valida con `satlas mockup " + slug + " --check`."
14357
14897
  ];
14358
- return { exitCode: 0, diagnostics: [], data: { plan, planFile: path31.relative(ctx.cwd, planFile), manifestFile: path31.relative(ctx.cwd, manifestFile) }, text: lines };
14898
+ return { exitCode: 0, diagnostics: [], data: { plan, planFile: path35.relative(ctx.cwd, planFile), manifestFile: path35.relative(ctx.cwd, manifestFile) }, text: lines };
14359
14899
  }
14360
14900
 
14361
14901
  // src/mcp/server.ts
14362
- import { promises as fs2 } from "fs";
14363
- import path34 from "path";
14902
+ import { promises as fs3 } from "fs";
14903
+ import path38 from "path";
14364
14904
  import { createInterface } from "readline/promises";
14365
14905
 
14366
14906
  // src/mcp/protocol.ts
@@ -14421,13 +14961,39 @@ function stringArg(args, key) {
14421
14961
  return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
14422
14962
  }
14423
14963
 
14964
+ // src/mcp/tools/fixes.ts
14965
+ async function runAtlasFixes(_args, host) {
14966
+ const resolved = await host.getWorkspace();
14967
+ if (!resolved) return noWorkspaceResult();
14968
+ const fixes = await loadLivingFixes(resolved.root);
14969
+ if (fixes.length === 0) {
14970
+ const activeFix = resolved.workspace.changes.find((change) => change.meta?.lane === "fix");
14971
+ return jsonResult({
14972
+ fixes: [],
14973
+ message: "No hay fixes vivos registrados.",
14974
+ action: activeFix ? `satlas archive ${activeFix.slug}` : "satlas new <slug> --lane fix"
14975
+ });
14976
+ }
14977
+ return jsonResult({
14978
+ fixes: fixes.map((fix) => ({
14979
+ slug: fix.slug,
14980
+ date: fix.date,
14981
+ result: fix.result,
14982
+ domain: fix.domain,
14983
+ title: fix.title,
14984
+ covers: fix.covers,
14985
+ content: fix.content
14986
+ }))
14987
+ });
14988
+ }
14989
+
14424
14990
  // src/mcp/tools/glossary.ts
14425
- import path32 from "path";
14991
+ import path36 from "path";
14426
14992
  async function runAtlasGlossary(_args, host) {
14427
14993
  const resolved = await host.getWorkspace();
14428
14994
  if (!resolved) return noWorkspaceResult();
14429
14995
  const { root, config } = resolved;
14430
- const glossaryPath = path32.resolve(root, config.spec.glossary);
14996
+ const glossaryPath = path36.resolve(root, config.spec.glossary);
14431
14997
  const raw = await readTextIfExists(glossaryPath);
14432
14998
  if (raw === void 0) {
14433
14999
  return jsonResult({
@@ -14473,7 +15039,7 @@ async function runAtlasImpact(args, host) {
14473
15039
  }
14474
15040
 
14475
15041
  // src/evaluate.ts
14476
- import path33 from "path";
15042
+ import path37 from "path";
14477
15043
  function livingRequirementsMap2(specs) {
14478
15044
  const map = /* @__PURE__ */ new Map();
14479
15045
  for (const spec of specs) {
@@ -14482,7 +15048,7 @@ function livingRequirementsMap2(specs) {
14482
15048
  return map;
14483
15049
  }
14484
15050
  async function evaluateChange(workspace, config, change, approvals) {
14485
- const deltaPath = path33.join(change.dir, "spec.md");
15051
+ const deltaPath = path37.join(change.dir, "spec.md");
14486
15052
  const deltaContent = await readTextIfExists(deltaPath);
14487
15053
  const approval = verifyApproval(change, approvals, config, deltaContent ?? void 0);
14488
15054
  const living = livingRequirementsMap2(workspace.specs);
@@ -14658,7 +15224,8 @@ var HANDLERS = {
14658
15224
  atlas_validate: runAtlasValidate,
14659
15225
  atlas_trace: runAtlasTrace,
14660
15226
  atlas_impact: runAtlasImpact,
14661
- atlas_glossary: runAtlasGlossary
15227
+ atlas_glossary: runAtlasGlossary,
15228
+ atlas_fixes: runAtlasFixes
14662
15229
  };
14663
15230
  async function runToolByName(name, args, host) {
14664
15231
  const handler = HANDLERS[name];
@@ -14727,6 +15294,15 @@ function listTools() {
14727
15294
  properties: {},
14728
15295
  additionalProperties: false
14729
15296
  }
15297
+ },
15298
+ {
15299
+ name: "atlas_fixes",
15300
+ description: "Fixes vivos (carril express ya archivado): identidad, resultado de su evidencia, contenido y requisitos que declaran. Solo lectura.",
15301
+ inputSchema: {
15302
+ type: "object",
15303
+ properties: {},
15304
+ additionalProperties: false
15305
+ }
14730
15306
  }
14731
15307
  ];
14732
15308
  }
@@ -14744,12 +15320,12 @@ var WorkspaceCache = class {
14744
15320
  }
14745
15321
  };
14746
15322
  async function workspaceStamp(root) {
14747
- const sddDir = path34.join(root, ".sdd");
14748
- const markers = [sddDir, path34.join(sddDir, "config.yaml"), path34.join(sddDir, "changes"), path34.join(sddDir, "specs"), path34.join(sddDir, "approvals.yaml")];
15323
+ const sddDir = path38.join(root, ".sdd");
15324
+ const markers = [sddDir, path38.join(sddDir, "config.yaml"), path38.join(sddDir, "changes"), path38.join(sddDir, "specs"), path38.join(sddDir, "approvals.yaml")];
14749
15325
  let stamp = 0;
14750
15326
  for (const marker of markers) {
14751
15327
  try {
14752
- const st = await fs2.stat(marker);
15328
+ const st = await fs3.stat(marker);
14753
15329
  stamp = Math.max(stamp, st.mtimeMs);
14754
15330
  } catch {
14755
15331
  }
@@ -14763,7 +15339,7 @@ async function resolveMcpWorkspace(cwd, cache) {
14763
15339
  const cached2 = cache.get(root);
14764
15340
  if (cached2 && cached2.mtimeMs === stamp) return cached2.value;
14765
15341
  const { workspace, config } = await loadWorkspace(root);
14766
- const approvals = await loadApprovals(path34.join(root, ".sdd"));
15342
+ const approvals = await loadApprovals(path38.join(root, ".sdd"));
14767
15343
  const value = { root, workspace, config, approvals: approvals.byArtifact };
14768
15344
  cache.set(root, stamp, value);
14769
15345
  return value;
@@ -14855,7 +15431,7 @@ async function runMcp(ctx) {
14855
15431
  }
14856
15432
 
14857
15433
  // src/commands/packs.ts
14858
- import path35 from "path";
15434
+ import path39 from "path";
14859
15435
  async function runPacks(ctx) {
14860
15436
  const { root, config, workspace } = await requireWorkspace(ctx);
14861
15437
  const slug = flagString(ctx.flags, "check");
@@ -14913,14 +15489,14 @@ async function runPacks(ctx) {
14913
15489
  data: {
14914
15490
  slug,
14915
15491
  packs: evaluations.map((evaluation) => ({ id: evaluation.pack.id, status: evaluation.status, passed: evaluation.passed, failed: evaluation.failed, results: evaluation.results })),
14916
- analyzePath: path35.posix.join(".sdd", "changes", slug, "analyze.md")
15492
+ analyzePath: path39.posix.join(".sdd", "changes", slug, "analyze.md")
14917
15493
  },
14918
15494
  text: lines
14919
15495
  };
14920
15496
  }
14921
15497
 
14922
15498
  // src/commands/new.ts
14923
- import path36 from "path";
15499
+ import path40 from "path";
14924
15500
  async function runNew(ctx) {
14925
15501
  const slug = ctx.positionals[0];
14926
15502
  if (!slug) {
@@ -14946,14 +15522,14 @@ async function runNew(ctx) {
14946
15522
  msg("new.title", ctx.language),
14947
15523
  "",
14948
15524
  msg("new.done", ctx.language),
14949
- ...result.files.map((f) => ` + ${path36.relative(ctx.cwd, f)}`),
15525
+ ...result.files.map((f) => ` + ${path40.relative(ctx.cwd, f)}`),
14950
15526
  "",
14951
15527
  `Siguiente: /satlas.specify ${result.slug} \u2014 escribe la especificaci\xF3n 100% funcional y de negocio.`
14952
15528
  ];
14953
15529
  return {
14954
15530
  exitCode: hasErrors2 ? 1 : 0,
14955
15531
  diagnostics: result.diagnostics,
14956
- data: { slug: result.slug, files: result.files.map((f) => path36.relative(ctx.cwd, f)) },
15532
+ data: { slug: result.slug, files: result.files.map((f) => path40.relative(ctx.cwd, f)) },
14957
15533
  text: lines
14958
15534
  };
14959
15535
  }
@@ -14988,7 +15564,7 @@ async function runNext(ctx) {
14988
15564
  }
14989
15565
 
14990
15566
  // src/commands/present.ts
14991
- import path37 from "path";
15567
+ import path41 from "path";
14992
15568
  async function runPresentCommand(ctx) {
14993
15569
  const { root } = await requireWorkspace(ctx);
14994
15570
  const slug = ctx.positionals[0];
@@ -15000,7 +15576,7 @@ async function runPresentCommand(ctx) {
15000
15576
  if (result.path) {
15001
15577
  lines.push("Propuesta generada");
15002
15578
  lines.push("");
15003
- lines.push(` archivo: ${path37.relative(ctx.cwd, result.path)}`);
15579
+ lines.push(` archivo: ${path41.relative(ctx.cwd, result.path)}`);
15004
15580
  if (result.hash) lines.push(` hash de la spec: ${shortHash(result.hash)}`);
15005
15581
  lines.push("");
15006
15582
  lines.push("\xC1brela en el navegador y comp\xE1rtela con el stakeholder.");
@@ -15010,7 +15586,7 @@ async function runPresentCommand(ctx) {
15010
15586
  return {
15011
15587
  exitCode: hasErrors2 ? 1 : 0,
15012
15588
  diagnostics: result.diagnostics,
15013
- data: result.path ? { path: path37.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
15589
+ data: result.path ? { path: path41.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
15014
15590
  text: lines
15015
15591
  };
15016
15592
  }
@@ -15086,7 +15662,8 @@ function usage(usageText) {
15086
15662
 
15087
15663
  // src/commands/status.ts
15088
15664
  async function runStatus(ctx) {
15089
- const { workspace, config, approvals } = await requireWorkspace(ctx);
15665
+ const { root, workspace, config, approvals } = await requireWorkspace(ctx);
15666
+ const advisory = await upgradeAdvisory(root);
15090
15667
  const lines = [msg("status.title", ctx.language), ""];
15091
15668
  const changes = [];
15092
15669
  if (workspace.changes.length === 0) {
@@ -15118,17 +15695,44 @@ async function runStatus(ctx) {
15118
15695
  for (const spec of workspace.specs) {
15119
15696
  lines.push(` ${spec.domain} \u2014 ${spec.spec.requirements.length} requisitos`);
15120
15697
  }
15121
- const errors = workspace.diagnostics.filter((d) => d.severity === "error").length;
15698
+ const fixes = await loadLivingFixes(root);
15699
+ lines.push("");
15700
+ lines.push(`${msg("status.fixes", ctx.language)}: ${fixes.length}`);
15701
+ if (fixes.length === 0) {
15702
+ lines.push(" (se llenan al archivar un fix)");
15703
+ } else {
15704
+ for (const fix of fixes) {
15705
+ lines.push(` ${fix.date || "\u2014"} ${(fix.domain ?? "\u2014").padEnd(10)} ${fix.slug} \u2014 ${fix.result}`);
15706
+ }
15707
+ }
15708
+ const diagnostics = [...workspace.diagnostics, ...advisory.diagnostics];
15709
+ const errors = diagnostics.filter((d) => d.severity === "error").length;
15122
15710
  return {
15123
15711
  exitCode: errors > 0 ? 1 : 0,
15124
- diagnostics: workspace.diagnostics,
15125
- data: { changes, specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length })) },
15712
+ diagnostics,
15713
+ data: {
15714
+ changes,
15715
+ specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length })),
15716
+ fixes: fixes.map((fix) => ({
15717
+ slug: fix.slug,
15718
+ date: fix.date,
15719
+ result: fix.result,
15720
+ ...fix.domain !== void 0 ? { domain: fix.domain } : {},
15721
+ covers: fix.covers
15722
+ })),
15723
+ upgrade: {
15724
+ currentVersion: advisory.plan.currentVersion,
15725
+ pending: advisory.plan.pending.length,
15726
+ newer: advisory.plan.newer.length,
15727
+ unreadable: advisory.plan.unreadable.length
15728
+ }
15729
+ },
15126
15730
  text: lines
15127
15731
  };
15128
15732
  }
15129
15733
 
15130
15734
  // src/commands/trace.ts
15131
- import path38 from "path";
15735
+ import path42 from "path";
15132
15736
  async function runTrace(ctx) {
15133
15737
  const { workspace, config } = await requireWorkspace(ctx);
15134
15738
  const slug = flagString(ctx.flags, "change");
@@ -15148,7 +15752,7 @@ async function runTrace(ctx) {
15148
15752
  data.push({ slug: change.slug, nodes: result.graph.nodes.length, edges: result.graph.edges.length, summary: result.summary, findings: result.findings });
15149
15753
  lines.push(` ${change.slug}: ${result.graph.nodes.length} nodos, ${result.graph.edges.length} aristas, ${errors} errores, ${result.summary.warnings} avisos`);
15150
15754
  for (const finding of result.findings) {
15151
- const location = finding.path ? ` (${path38.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
15755
+ const location = finding.path ? ` (${path42.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
15152
15756
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}${location}`);
15153
15757
  }
15154
15758
  }
@@ -15157,12 +15761,130 @@ async function runTrace(ctx) {
15157
15761
  return { exitCode: blockingErrors > 0 ? 1 : 0, diagnostics, data: { changes: data }, text: lines };
15158
15762
  }
15159
15763
 
15764
+ // src/commands/upgrade.ts
15765
+ import path43 from "path";
15766
+ async function runUpgrade(ctx) {
15767
+ const { root } = await requireWorkspace(ctx);
15768
+ const apply = flagBool(ctx.flags, "apply");
15769
+ const rollback = flagBool(ctx.flags, "rollback");
15770
+ if (apply && rollback) {
15771
+ return {
15772
+ exitCode: 2,
15773
+ diagnostics: [
15774
+ {
15775
+ code: "ATLAS-UPGRADE-CLI-001",
15776
+ severity: "error",
15777
+ message: "Elige una sola acci\xF3n: --apply o --rollback",
15778
+ suggestion: "Vista previa: `satlas upgrade` \xB7 Aplicar: `satlas upgrade --apply` \xB7 Revertir: `satlas upgrade --rollback`"
15779
+ }
15780
+ ]
15781
+ };
15782
+ }
15783
+ if (rollback) return renderRollback(await rollbackUpgrade(root), ctx);
15784
+ if (apply) return renderApply(await applyUpgrade(root), ctx);
15785
+ return renderPreview(await planUpgrade(root), root, ctx);
15786
+ }
15787
+ function summarizable(plan) {
15788
+ return {
15789
+ currentVersion: plan.currentVersion,
15790
+ upToDate: plan.upToDate,
15791
+ pending: plan.pending.map((i) => ({ artifact: i.artifact, from: i.from, to: i.to, migration: i.migration })),
15792
+ newer: plan.newer.map((n) => ({ artifact: n.artifact, reason: n.reason })),
15793
+ unreadable: plan.unreadable.map((u) => ({ artifact: u.artifact, reason: u.reason }))
15794
+ };
15795
+ }
15796
+ function renderPreview(plan, root, ctx) {
15797
+ const lines = [msg("upgrade.title", ctx.language), ""];
15798
+ if (plan.upToDate && plan.newer.length === 0 && plan.unreadable.length === 0) {
15799
+ lines.push(`El estado del proyecto est\xE1 al d\xEDa (versi\xF3n ${plan.currentVersion}). No hay nada que actualizar.`);
15800
+ return { exitCode: 0, diagnostics: [], data: summarizable(plan), text: lines };
15801
+ }
15802
+ lines.push(`${msg("upgrade.preview", ctx.language)}: no se modifica nada.`);
15803
+ lines.push(` Versi\xF3n vigente: ${plan.currentVersion}`);
15804
+ if (plan.pending.length === 0) {
15805
+ lines.push(" Elementos por actualizar: 0");
15806
+ } else {
15807
+ lines.push(` Elementos por actualizar: ${plan.pending.length}`);
15808
+ for (const item of plan.pending) {
15809
+ lines.push(` ${item.artifact} ${item.from} \u2192 ${item.to} (${item.migration})`);
15810
+ }
15811
+ lines.push("");
15812
+ lines.push(`Aplica la actualizaci\xF3n con \`satlas upgrade --apply\`.`);
15813
+ }
15814
+ for (const item of plan.newer) {
15815
+ lines.push(` Aviso: "${item.artifact}" ${item.reason}`);
15816
+ }
15817
+ for (const item of plan.unreadable) {
15818
+ lines.push(` Ilegible: "${item.artifact}" \u2014 ${item.reason}`);
15819
+ lines.push(` \u21B3 ${path43.relative(ctx.cwd, item.path)}`);
15820
+ }
15821
+ return { exitCode: 0, diagnostics: [], data: summarizable(plan), text: lines };
15822
+ }
15823
+ function renderApply(report, ctx) {
15824
+ const lines = [msg("upgrade.title", ctx.language), ""];
15825
+ if (report.status === "up-to-date") {
15826
+ lines.push("No hab\xEDa nada que actualizar; no se escribi\xF3 ning\xFAn elemento.");
15827
+ return { exitCode: 0, diagnostics: [], data: { status: report.status, plan: summarizable(report.plan) }, text: lines };
15828
+ }
15829
+ if (report.status === "failed") {
15830
+ lines.push(`La actualizaci\xF3n fall\xF3 en "${report.failure?.artifact}": ${report.failure?.message}`);
15831
+ lines.push("El proyecto qued\xF3 exactamente como estaba.");
15832
+ return {
15833
+ exitCode: 1,
15834
+ diagnostics: [{ code: "ATLAS-UPGRADE-101", severity: "error", message: `Fall\xF3 la actualizaci\xF3n en "${report.failure?.artifact}": ${report.failure?.message}` }],
15835
+ data: { status: report.status, failure: report.failure },
15836
+ text: lines
15837
+ };
15838
+ }
15839
+ lines.push(`Actualizaci\xF3n aplicada: ${report.applied.length} elemento(s) a la versi\xF3n ${report.plan.currentVersion}.`);
15840
+ lines.push(`Respaldo recuperable: ${report.backup?.relativeDir}`);
15841
+ if (report.plan.unreadable.length > 0) {
15842
+ lines.push(`No se tocaron ${report.plan.unreadable.length} elemento(s) ilegible(s).`);
15843
+ }
15844
+ return {
15845
+ exitCode: 0,
15846
+ diagnostics: [],
15847
+ data: {
15848
+ status: report.status,
15849
+ applied: report.applied.map((i) => i.artifact),
15850
+ backup: report.backup,
15851
+ plan: summarizable(report.plan)
15852
+ },
15853
+ text: lines
15854
+ };
15855
+ }
15856
+ function renderRollback(report, ctx) {
15857
+ const lines = [msg("upgrade.title", ctx.language), ""];
15858
+ if (report.status === "no-backup") {
15859
+ lines.push("No hay respaldo disponible para revertir; no se modific\xF3 nada.");
15860
+ return {
15861
+ exitCode: 1,
15862
+ diagnostics: [{ code: "ATLAS-UPGRADE-102", severity: "warning", message: "No hay respaldo disponible para revertir la \xFAltima actualizaci\xF3n" }],
15863
+ data: { status: report.status },
15864
+ text: lines
15865
+ };
15866
+ }
15867
+ if (report.status === "failed") {
15868
+ lines.push(`La reversi\xF3n fall\xF3 en "${report.failure?.artifact}": ${report.failure?.message}`);
15869
+ lines.push("El respaldo se conserva para reintentar.");
15870
+ return {
15871
+ exitCode: 1,
15872
+ diagnostics: [{ code: "ATLAS-UPGRADE-103", severity: "error", message: `Fall\xF3 la reversi\xF3n en "${report.failure?.artifact}": ${report.failure?.message}` }],
15873
+ data: { status: report.status, failure: report.failure },
15874
+ text: lines
15875
+ };
15876
+ }
15877
+ lines.push(`Se restaur\xF3 el estado previo: ${report.restored.length} elemento(s). El respaldo qued\xF3 consumido.`);
15878
+ return { exitCode: 0, diagnostics: [], data: { status: report.status, restored: report.restored, backup: report.backup }, text: lines };
15879
+ }
15880
+
15160
15881
  // src/commands/validate.ts
15161
- import path39 from "path";
15882
+ import path44 from "path";
15162
15883
  async function runValidate(ctx) {
15163
- const { workspace, config, approvals } = await requireWorkspace(ctx);
15884
+ const { root, workspace, config, approvals } = await requireWorkspace(ctx);
15164
15885
  const slug = flagString(ctx.flags, "change");
15165
15886
  const strict = flagBool(ctx.flags, "strict");
15887
+ const advisory = slug ? void 0 : await upgradeAdvisory(root);
15166
15888
  const diagnostics = [];
15167
15889
  const lines = [msg("validate.title", ctx.language), ""];
15168
15890
  for (const spec of workspace.specs) {
@@ -15184,22 +15906,35 @@ async function runValidate(ctx) {
15184
15906
  for (const finding of evaluation.lintFindings) {
15185
15907
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${finding.line ? `:${finding.line}` : ""} \u2014 ${finding.message}`);
15186
15908
  }
15187
- lines.push(` archivo: ${path39.relative(ctx.cwd, path39.join(change.dir, "spec.md"))}`);
15909
+ lines.push(` archivo: ${path44.relative(ctx.cwd, path44.join(change.dir, "spec.md"))}`);
15188
15910
  }
15189
15911
  if (targets.length === 0) lines.push("Sin cambios activos.");
15912
+ if (advisory && advisory.plan.pending.length > 0) {
15913
+ lines.push(`Actualizaci\xF3n pendiente: ${advisory.plan.pending.length} elemento(s) (vista previa: \`satlas upgrade\`).`);
15914
+ }
15190
15915
  const errors = diagnostics.filter((d) => d.severity === "error").length;
15191
15916
  const warnings = diagnostics.filter((d) => d.severity === "warning").length;
15192
15917
  if (errors === 0 && warnings === 0) lines.push(msg("validate.ok", ctx.language));
15193
15918
  return {
15194
15919
  exitCode: errors > 0 || strict && warnings > 0 ? 1 : 0,
15195
- diagnostics,
15196
- data: { changes, errors, warnings },
15920
+ diagnostics: advisory ? [...diagnostics, ...advisory.diagnostics] : diagnostics,
15921
+ data: {
15922
+ changes,
15923
+ errors,
15924
+ warnings,
15925
+ upgrade: advisory ? {
15926
+ currentVersion: advisory.plan.currentVersion,
15927
+ pending: advisory.plan.pending.length,
15928
+ newer: advisory.plan.newer.length,
15929
+ unreadable: advisory.plan.unreadable.length
15930
+ } : void 0
15931
+ },
15197
15932
  text: lines
15198
15933
  };
15199
15934
  }
15200
15935
 
15201
15936
  // src/commands/verify.ts
15202
- import path40 from "path";
15937
+ import path45 from "path";
15203
15938
  async function runVerify(ctx) {
15204
15939
  const { root, workspace } = await requireWorkspace(ctx);
15205
15940
  const slug = ctx.positionals[0];
@@ -15242,7 +15977,7 @@ async function runVerify(ctx) {
15242
15977
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-VERIFY-001", severity: "error", message: "Falta --by <nombre> (o define meta.owner)" }] };
15243
15978
  }
15244
15979
  const profilesDir = await resolveProfilesDir();
15245
- const profile = await loadActiveProfile(path40.join(root, ".sdd"), [profilesDir ?? "", path40.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
15980
+ const profile = await loadActiveProfile(path45.join(root, ".sdd"), [profilesDir ?? "", path45.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
15246
15981
  const record = await recordEvidence({
15247
15982
  root,
15248
15983
  slug,
@@ -15264,7 +15999,7 @@ async function runVerify(ctx) {
15264
15999
  if (record.evidence.command) lines.push(` comando: ${record.evidence.command}`);
15265
16000
  lines.push(` resultado: ${record.evidence.result}`);
15266
16001
  if (record.evidence.outputHash) lines.push(` hash: ${record.evidence.outputHash}`);
15267
- lines.push(` archivo: ${path40.relative(ctx.cwd, record.path)}`);
16002
+ lines.push(` archivo: ${path45.relative(ctx.cwd, record.path)}`);
15268
16003
  if (record.stdout) {
15269
16004
  lines.push("");
15270
16005
  lines.push(" salida (\xFAltimas l\xEDneas):");
@@ -15279,7 +16014,7 @@ async function runVerify(ctx) {
15279
16014
  return {
15280
16015
  exitCode: record.exitCode === 0 ? 0 : 1,
15281
16016
  diagnostics: record.diagnostics,
15282
- data: { evidence: record.evidence, path: path40.relative(ctx.cwd, record.path), exitCode: record.exitCode },
16017
+ data: { evidence: record.evidence, path: path45.relative(ctx.cwd, record.path), exitCode: record.exitCode },
15283
16018
  text: lines
15284
16019
  };
15285
16020
  }
@@ -15334,6 +16069,7 @@ var HANDLERS2 = {
15334
16069
  trace: runTrace,
15335
16070
  waves: runWaves,
15336
16071
  doctor: runDoctorCommand,
16072
+ upgrade: runUpgrade,
15337
16073
  approve: runApprove,
15338
16074
  issue: runIssue,
15339
16075
  adapters: runAdapters,
@@ -15378,7 +16114,7 @@ async function requireWorkspace(ctx) {
15378
16114
  throw new CliError("ATLAS-WS-001", msg("cli.noWorkspace", ctx.language), 2);
15379
16115
  }
15380
16116
  const { workspace, config } = await loadWorkspace(root);
15381
- const approvals = await loadApprovals(path41.join(root, ".sdd"));
16117
+ const approvals = await loadApprovals(path46.join(root, ".sdd"));
15382
16118
  return { root, workspace, config, approvals: approvals.byArtifact };
15383
16119
  }
15384
16120
  var CliError = class extends Error {
@@ -15445,7 +16181,7 @@ function printResult(command, result, json2, language) {
15445
16181
  }
15446
16182
  const lines = [...result.text ?? []];
15447
16183
  for (const d of result.diagnostics) {
15448
- const location = d.path ? ` ${path41.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
16184
+ const location = d.path ? ` ${path46.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
15449
16185
  lines.push(`${d.severity === "error" ? "ERROR" : d.severity === "warning" ? "AVISO" : "NOTA"} ${d.code}${location} \u2014 ${d.message}`);
15450
16186
  if (d.suggestion) lines.push(` \u21B3 ${d.suggestion}`);
15451
16187
  }