@specatlas/core 0.1.24 → 0.1.25

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/index.js CHANGED
@@ -794,14 +794,14 @@ function wordBoundary(text, term) {
794
794
  const re = new RegExp(`(?<![\\p{L}\\p{N}])${escaped}(?![\\p{L}\\p{N}])`, "iu");
795
795
  return re.test(text);
796
796
  }
797
- function lintText(text, code, terms, label, path21, line) {
797
+ function lintText(text, code, terms, label, path22, line) {
798
798
  const out = [];
799
799
  const lower = text.toLowerCase();
800
800
  for (const term of terms) {
801
801
  if (wordBoundary(lower, term)) {
802
802
  out.push(
803
803
  diag(code, "error", `${label}: "${term}"`, {
804
- path: path21,
804
+ path: path22,
805
805
  line,
806
806
  suggestion: "La especificaci\xF3n es funcional y de negocio: describe comportamiento, no tecnolog\xEDa ni adjetivos vagos"
807
807
  })
@@ -810,7 +810,7 @@ function lintText(text, code, terms, label, path21, line) {
810
810
  }
811
811
  return out;
812
812
  }
813
- function lintRequirement(req, path21, opts = {}) {
813
+ function lintRequirement(req, path22, opts = {}) {
814
814
  const out = [];
815
815
  const vague = opts.language === "en" ? VAGUE_EN : VAGUE_ES;
816
816
  const tech = opts.language === "en" ? TECH_EN : TECH_ES;
@@ -821,32 +821,32 @@ function lintRequirement(req, path21, opts = {}) {
821
821
  ...req.scenarios.flatMap((s) => [...s.when.map((w) => ({ text: w, line: s.line })), ...s.then.map((t) => ({ text: t, line: s.line }))])
822
822
  ];
823
823
  for (const part of parts) {
824
- out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path21, part.line));
824
+ out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path22, part.line));
825
825
  if (opts.businessOnly !== false) {
826
- out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path21, part.line));
826
+ out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path22, part.line));
827
827
  }
828
828
  }
829
829
  if (req.scenarios.length === 0) {
830
- out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path21, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
830
+ out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path22, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
831
831
  }
832
832
  return out;
833
833
  }
834
- function lintDelta(delta, livingRequirements, path21, opts = {}) {
834
+ function lintDelta(delta, livingRequirements, path22, opts = {}) {
835
835
  const out = [...delta.diagnostics];
836
836
  for (const req of [...delta.added, ...delta.modified]) {
837
- out.push(...lintRequirement(req, path21, opts));
837
+ out.push(...lintRequirement(req, path22, opts));
838
838
  }
839
839
  for (const req of delta.modified) {
840
840
  const living = livingRequirements.get(req.id);
841
841
  if (!living) {
842
- out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path21, line: req.line }));
842
+ out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path22, line: req.line }));
843
843
  continue;
844
844
  }
845
845
  for (const existing of living.scenarios) {
846
846
  if (!req.scenarios.some((s) => s.id === existing.id)) {
847
847
  out.push(
848
848
  diag("TRACE-007", "error", `MODIFIED ${req.id} pierde el escenario ${existing.id}: copia el bloque completo`, {
849
- path: path21,
849
+ path: path22,
850
850
  line: req.line,
851
851
  suggestion: "Copia el bloque completo de la spec viva y ed\xEDtalo; para quitarlo, decl\xE1ralo en REMOVED"
852
852
  })
@@ -857,15 +857,15 @@ function lintDelta(delta, livingRequirements, path21, opts = {}) {
857
857
  for (const req of delta.removed) {
858
858
  const living = livingRequirements.get(req.id);
859
859
  if (!living) {
860
- out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path21, line: req.line }));
860
+ out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path22, line: req.line }));
861
861
  }
862
862
  }
863
863
  for (const rename2 of delta.renamed) {
864
864
  if (rename2.from.id !== rename2.to.id) {
865
- out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path21, line: rename2.line }));
865
+ out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path22, line: rename2.line }));
866
866
  }
867
867
  if (!livingRequirements.has(rename2.from.id)) {
868
- out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path21, line: rename2.line }));
868
+ out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path22, line: rename2.line }));
869
869
  }
870
870
  }
871
871
  return out;
@@ -895,7 +895,7 @@ var MERMAID_KEYWORDS = [
895
895
  "architecture-beta"
896
896
  ];
897
897
  var MERMAID_BLOCKS = /^\s*(alt|loop|opt|par|rect|critical|break|subgraph)\b/;
898
- function lintPlan(planText, path21) {
898
+ function lintPlan(planText, path22) {
899
899
  const out = [];
900
900
  const blocks = [...planText.matchAll(/```mermaid\r?\n([\s\S]*?)```/g)];
901
901
  for (const [index, block] of blocks.entries()) {
@@ -905,7 +905,7 @@ function lintPlan(planText, path21) {
905
905
  if (!MERMAID_KEYWORDS.some((keyword) => first.startsWith(keyword))) {
906
906
  out.push(
907
907
  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)}"`, {
908
- path: path21,
908
+ path: path22,
909
909
  suggestion: "Corrige el tipo del diagrama o elimina el bloque"
910
910
  })
911
911
  );
@@ -919,7 +919,7 @@ function lintPlan(planText, path21) {
919
919
  if (open !== 0) {
920
920
  out.push(
921
921
  diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: faltan ${Math.abs(open)} \`end\` (bloques alt/loop/subgraph sin cerrar)`, {
922
- path: path21,
922
+ path: path22,
923
923
  suggestion: "Cierra cada bloque alt/loop/opt/par/rect/subgraph con `end`"
924
924
  })
925
925
  );
@@ -931,7 +931,7 @@ function lintPlan(planText, path21) {
931
931
  if (message.includes(";")) {
932
932
  out.push(
933
933
  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`, {
934
- path: path21,
934
+ path: path22,
935
935
  suggestion: "Sustituye `;` por `\xB7` o `,` en los mensajes de sequenceDiagram"
936
936
  })
937
937
  );
@@ -1088,6 +1088,16 @@ function checkTrace(input) {
1088
1088
  findings.push(diag("TRACE-006", "warning", `Evidencia hu\xE9rfana: ${ev.scenario} no existe`, { path: change.verify?.path, line: ev.line }));
1089
1089
  }
1090
1090
  }
1091
+ for (const cover of change.fixCovers ?? []) {
1092
+ if (!livingReqs.has(cover)) {
1093
+ findings.push(
1094
+ diag("TRACE-011", "warning", `El fix ${change.slug} declara ${cover}, que no existe en las specs vivas`, {
1095
+ path: change.fix?.path,
1096
+ suggestion: "Corrige el identificador o qu\xEDtalo: la declaraci\xF3n es opcional"
1097
+ })
1098
+ );
1099
+ }
1100
+ }
1091
1101
  const summary = countBySeverity(findings);
1092
1102
  return { graph: buildTraceGraph(input), findings, summary };
1093
1103
  }
@@ -1463,35 +1473,117 @@ function detectionToYaml(result, generatedAt) {
1463
1473
  }
1464
1474
 
1465
1475
  // src/workspace.ts
1476
+ import path5 from "path";
1477
+
1478
+ // src/fixes.ts
1466
1479
  import path4 from "path";
1480
+ import { stringify as stringifyYaml3 } from "yaml";
1481
+ var LIVING_FIXES_DIR = path4.join(".sdd", "fixes");
1482
+ var COMMENT_RE = /<!--[\s\S]*?-->/g;
1483
+ function parseFixCovers(content) {
1484
+ const clean = content.replace(COMMENT_RE, "");
1485
+ const covers = [];
1486
+ for (const line of clean.replace(/\r\n?/g, "\n").split("\n")) {
1487
+ const match = /^\s*-?\s*Cubre:\s*(.+?)\s*$/.exec(line);
1488
+ if (!match) continue;
1489
+ for (const raw of (match[1] ?? "").split(",")) {
1490
+ const id = raw.trim().toUpperCase();
1491
+ if (/^REQ-[A-Z0-9-]+$/.test(id) && !covers.includes(id)) covers.push(id);
1492
+ }
1493
+ }
1494
+ return covers;
1495
+ }
1496
+ function coversOf(value) {
1497
+ if (Array.isArray(value)) {
1498
+ return value.map((v) => String(v).trim().toUpperCase()).filter((v) => /^REQ-[A-Z0-9-]+$/.test(v));
1499
+ }
1500
+ if (typeof value === "string") {
1501
+ return value.split(",").map((v) => v.trim().toUpperCase()).filter((v) => /^REQ-[A-Z0-9-]+$/.test(v));
1502
+ }
1503
+ return [];
1504
+ }
1505
+ function parseLivingFix(content, file) {
1506
+ const fm = parseFrontmatter(content, file);
1507
+ const data = fm.data;
1508
+ const base = path4.basename(file).replace(/\.md$/i, "");
1509
+ const slug = typeof data["slug"] === "string" && data["slug"].trim() !== "" ? data["slug"].trim() : base.replace(/^\d{4}-\d{2}-/, "");
1510
+ const fix = {
1511
+ slug,
1512
+ file,
1513
+ date: typeof data["date"] === "string" ? data["date"] : "",
1514
+ result: typeof data["result"] === "string" ? data["result"] : "pass",
1515
+ covers: coversOf(data["covers"]),
1516
+ content: fm.body.trimStart()
1517
+ };
1518
+ if (typeof data["domain"] === "string") fix.domain = data["domain"];
1519
+ if (typeof data["title"] === "string") fix.title = data["title"];
1520
+ return fix;
1521
+ }
1522
+ async function loadLivingFixes(root) {
1523
+ const dir = path4.join(root, LIVING_FIXES_DIR);
1524
+ const entries = (await listDir(dir)).filter((entry) => entry.toLowerCase().endsWith(".md")).sort();
1525
+ const fixes = [];
1526
+ for (const entry of entries) {
1527
+ const file = path4.join(dir, entry);
1528
+ const content = await readTextIfExists(file);
1529
+ if (content === void 0) continue;
1530
+ fixes.push(parseLivingFix(content, file));
1531
+ }
1532
+ return fixes.sort((a, b) => b.date.localeCompare(a.date) || b.slug.localeCompare(a.slug));
1533
+ }
1534
+ async function writeLivingFix(root, input) {
1535
+ const dir = path4.join(root, LIVING_FIXES_DIR);
1536
+ const month = input.date.slice(0, 7);
1537
+ const file = path4.join(dir, `${month}-${input.slug}.md`);
1538
+ const relativePath = toPosix(path4.relative(root, file));
1539
+ if (await exists(file)) return { file, relativePath, created: false };
1540
+ const data = {
1541
+ slug: input.slug,
1542
+ date: input.date,
1543
+ result: input.result ?? "pass"
1544
+ };
1545
+ if (input.domain) data["domain"] = input.domain;
1546
+ if (input.title) data["title"] = input.title;
1547
+ if (input.covers && input.covers.length > 0) data["covers"] = input.covers;
1548
+ const body = input.content.replace(/^[\r\n]+/, "").trimEnd();
1549
+ const text = `---
1550
+ ${stringifyYaml3(data, { lineWidth: 120 })}---
1551
+
1552
+ ${body}
1553
+ `;
1554
+ await writeText(file, text);
1555
+ return { file, relativePath, created: true };
1556
+ }
1557
+
1558
+ // src/workspace.ts
1467
1559
  var SDD_DIR = ".sdd";
1468
1560
  async function findWorkspaceRoot(start) {
1469
- let current = path4.resolve(start);
1561
+ let current = path5.resolve(start);
1470
1562
  for (let i = 0; i < 40; i += 1) {
1471
- if (await isDirectory(path4.join(current, SDD_DIR))) return current;
1472
- const parent = path4.dirname(current);
1563
+ if (await isDirectory(path5.join(current, SDD_DIR))) return current;
1564
+ const parent = path5.dirname(current);
1473
1565
  if (parent === current) return void 0;
1474
1566
  current = parent;
1475
1567
  }
1476
1568
  return void 0;
1477
1569
  }
1478
1570
  async function loadApprovals(sddDir) {
1479
- const file = path4.join(sddDir, "approvals.yaml");
1571
+ const file = path5.join(sddDir, "approvals.yaml");
1480
1572
  const raw = await readTextIfExists(file);
1481
1573
  if (raw === void 0) return { byArtifact: /* @__PURE__ */ new Map(), diagnostics: [] };
1482
1574
  const parsed = parseApprovals(raw, file);
1483
1575
  const map = /* @__PURE__ */ new Map();
1484
1576
  for (const a of parsed.approvals?.approvals ?? []) {
1485
- map.set(a.artifact.split(path4.sep).join("/"), { hash: a.artifactHash, by: a.approvedBy, at: a.approvedAt });
1577
+ map.set(a.artifact.split(path5.sep).join("/"), { hash: a.artifactHash, by: a.approvedBy, at: a.approvedAt });
1486
1578
  }
1487
1579
  return { byArtifact: map, diagnostics: parsed.diagnostics };
1488
1580
  }
1489
1581
  async function loadSpecs(sddDir) {
1490
- const specsDir = path4.join(sddDir, "specs");
1582
+ const specsDir = path5.join(sddDir, "specs");
1491
1583
  const domains = await listDirs(specsDir);
1492
1584
  const out = [];
1493
1585
  for (const domain of domains) {
1494
- const file = path4.join(specsDir, domain, "spec.md");
1586
+ const file = path5.join(specsDir, domain, "spec.md");
1495
1587
  if (!await exists(file)) continue;
1496
1588
  const content = await readText(file);
1497
1589
  out.push({ domain, path: file, spec: parseSpecFile(content, file) });
@@ -1499,10 +1591,10 @@ async function loadSpecs(sddDir) {
1499
1591
  return out;
1500
1592
  }
1501
1593
  async function loadChange(root, slug, relDir) {
1502
- const dir = path4.join(root, SDD_DIR, "changes", relDir ?? slug);
1594
+ const dir = path5.join(root, SDD_DIR, "changes", relDir ?? slug);
1503
1595
  const diagnostics = [];
1504
1596
  const change = { slug, dir, diagnostics };
1505
- const metaFile = path4.join(dir, "meta.yaml");
1597
+ const metaFile = path5.join(dir, "meta.yaml");
1506
1598
  const metaRaw = await readTextIfExists(metaFile);
1507
1599
  if (metaRaw === void 0) {
1508
1600
  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" }));
@@ -1511,7 +1603,7 @@ async function loadChange(root, slug, relDir) {
1511
1603
  diagnostics.push(...parsed.diagnostics);
1512
1604
  if (parsed.meta) change.meta = parsed.meta;
1513
1605
  }
1514
- const deltaFile = path4.join(dir, "spec.md");
1606
+ const deltaFile = path5.join(dir, "spec.md");
1515
1607
  const deltaRaw = await readTextIfExists(deltaFile);
1516
1608
  if (deltaRaw !== void 0) {
1517
1609
  change.delta = parseDelta(deltaRaw, deltaFile);
@@ -1519,41 +1611,42 @@ async function loadChange(root, slug, relDir) {
1519
1611
  } else {
1520
1612
  diagnostics.push(diag("ATLAS-FILES-002", "warning", `El cambio "${slug}" no tiene spec.md (delta)`, { path: deltaFile }));
1521
1613
  }
1522
- const planFile = path4.join(dir, "plan.md");
1614
+ const planFile = path5.join(dir, "plan.md");
1523
1615
  if (await exists(planFile)) change.planPath = planFile;
1524
- const reviewFile = path4.join(dir, "review.md");
1616
+ const reviewFile = path5.join(dir, "review.md");
1525
1617
  if (await exists(reviewFile)) change.reviewPath = reviewFile;
1526
- const presentationFile = path4.join(dir, "presentation", "index.html");
1618
+ const presentationFile = path5.join(dir, "presentation", "index.html");
1527
1619
  if (await exists(presentationFile)) change.presentationPath = presentationFile;
1528
- const tasksFile = path4.join(dir, "tasks.md");
1620
+ const tasksFile = path5.join(dir, "tasks.md");
1529
1621
  const tasksRaw = await readTextIfExists(tasksFile);
1530
1622
  if (tasksRaw !== void 0) {
1531
1623
  change.tasks = parseTasksFile(tasksRaw, tasksFile);
1532
1624
  diagnostics.push(...change.tasks.diagnostics);
1533
1625
  }
1534
- const verifyFile = path4.join(dir, "verify.md");
1626
+ const verifyFile = path5.join(dir, "verify.md");
1535
1627
  const verifyRaw = await readTextIfExists(verifyFile);
1536
1628
  if (verifyRaw !== void 0) {
1537
1629
  change.verify = parseVerifyFile(verifyRaw, verifyFile);
1538
1630
  diagnostics.push(...change.verify.diagnostics);
1539
1631
  }
1540
- const fixFile = path4.join(dir, "fix.md");
1632
+ const fixFile = path5.join(dir, "fix.md");
1541
1633
  const fixRaw = await readTextIfExists(fixFile);
1542
1634
  if (fixRaw !== void 0) {
1543
1635
  change.fix = parseVerifyFile(fixRaw, fixFile);
1544
1636
  diagnostics.push(...change.fix.diagnostics);
1637
+ change.fixCovers = parseFixCovers(fixRaw);
1545
1638
  }
1546
- const mockupManifest = path4.join(dir, "mockups", "manifest.yaml");
1639
+ const mockupManifest = path5.join(dir, "mockups", "manifest.yaml");
1547
1640
  if (await exists(mockupManifest)) change.mockupManifestPath = mockupManifest;
1548
1641
  return change;
1549
1642
  }
1550
1643
  async function listChangeSlugs(root, opts = {}) {
1551
- const changesDir = path4.join(root, SDD_DIR, "changes");
1644
+ const changesDir = path5.join(root, SDD_DIR, "changes");
1552
1645
  const dirs = await listDirs(changesDir);
1553
1646
  return dirs.filter((d) => opts.includeArchived ? true : d !== "archive");
1554
1647
  }
1555
1648
  async function loadWorkspace(root) {
1556
- const sddDir = path4.join(root, SDD_DIR);
1649
+ const sddDir = path5.join(root, SDD_DIR);
1557
1650
  const { config, diagnostics: configDiags } = await loadConfig(sddDir);
1558
1651
  const diagnostics = [...configDiags];
1559
1652
  const specs = await loadSpecs(sddDir);
@@ -1566,20 +1659,20 @@ async function loadWorkspace(root) {
1566
1659
  diagnostics.push(...change.diagnostics);
1567
1660
  }
1568
1661
  const archived = [];
1569
- const archivedDir = path4.join(sddDir, "changes", "archive");
1662
+ const archivedDir = path5.join(sddDir, "changes", "archive");
1570
1663
  if (await isDirectory(archivedDir)) {
1571
1664
  for (const entry of await listDirs(archivedDir)) {
1572
- archived.push(await loadChange(root, entry.replace(/^\d{4}-\d{2}-/, ""), path4.join("archive", entry)));
1665
+ archived.push(await loadChange(root, entry.replace(/^\d{4}-\d{2}-/, ""), path5.join("archive", entry)));
1573
1666
  }
1574
1667
  }
1575
1668
  return { workspace: { root, sddDir, specs, changes, archived, diagnostics }, config };
1576
1669
  }
1577
1670
  async function ensureSddDirs(sddDir) {
1578
1671
  const created = [];
1579
- const dirs = ["specs", "changes", "runs", "metrics", path4.join("profiles", "custom")];
1672
+ const dirs = ["specs", "changes", "runs", "metrics", "fixes", path5.join("profiles", "custom")];
1580
1673
  const { ensureDir: ensureDir2 } = await import("./fsx-VF2P7ALA.js");
1581
1674
  for (const d of dirs) {
1582
- const full = path4.join(sddDir, d);
1675
+ const full = path5.join(sddDir, d);
1583
1676
  if (!await exists(full)) {
1584
1677
  await ensureDir2(full);
1585
1678
  created.push(full);
@@ -1589,7 +1682,7 @@ async function ensureSddDirs(sddDir) {
1589
1682
  }
1590
1683
 
1591
1684
  // src/templates.ts
1592
- import { stringify as stringifyYaml3 } from "yaml";
1685
+ import { stringify as stringifyYaml4 } from "yaml";
1593
1686
  var ES = {
1594
1687
  constitution: `# Constituci\xF3n del proyecto
1595
1688
 
@@ -1750,6 +1843,10 @@ notes: 12/12 casos
1750
1843
 
1751
1844
  ## Evidencia
1752
1845
 
1846
+ <!-- Opcional: declara los requisitos que este fix afecta (aparecen en su trazabilidad).
1847
+ Cubre: REQ-DOMINIO-001
1848
+ -->
1849
+
1753
1850
  <!-- Registra la evidencia real con:
1754
1851
  satlas verify <slug> --file fix --scenario REQ-DOMINIO-001-S1 --command "<comando>" --by "<nombre>"
1755
1852
  o, si es manual:
@@ -1873,6 +1970,10 @@ by: your-name
1873
1970
  ## Rollback
1874
1971
  ## Evidence
1875
1972
 
1973
+ <!-- Optional: declare the requirements this fix affects (they show up in its traceability).
1974
+ Cubre: REQ-DOMAIN-001
1975
+ -->
1976
+
1876
1977
  <!-- Register real evidence with:
1877
1978
  satlas verify <slug> --file fix --scenario REQ-DOMAIN-001-S1 --command "<command>" --by "<name>"
1878
1979
  -->
@@ -1897,7 +1998,7 @@ function changeMetaYaml(meta) {
1897
1998
  if (meta.owner) doc["owner"] = meta.owner;
1898
1999
  if (meta.mockups) doc["mockups"] = meta.mockups;
1899
2000
  return `# Estado del cambio. La fase se DERIVA de los artefactos; aqu\xED solo hechos.
1900
- ` + stringifyYaml3(doc, { lineWidth: 120 });
2001
+ ` + stringifyYaml4(doc, { lineWidth: 120 });
1901
2002
  }
1902
2003
  function indexMarkdown(input) {
1903
2004
  const es = input.language !== "en";
@@ -1911,6 +2012,15 @@ function indexMarkdown(input) {
1911
2012
  if (input.specs.length === 0) lines.push(es ? "_Sin specs todav\xEDa._" : "_No specs yet._");
1912
2013
  for (const spec of input.specs) lines.push(`- \`${spec.domain}\` \u2014 ${spec.requirements} ${es ? "requisitos" : "requirements"}`);
1913
2014
  lines.push("");
2015
+ lines.push(es ? "## Fixes vivos" : "## Living fixes");
2016
+ lines.push("");
2017
+ const fixes = input.fixes ?? [];
2018
+ if (fixes.length === 0) lines.push(es ? "_Sin fixes archivados todav\xEDa._" : "_No archived fixes yet._");
2019
+ for (const fix of fixes) {
2020
+ const parts = [fix.date, fix.domain ? `\`${fix.domain}\`` : "", `\`${fix.slug}\``, fix.result].filter((part) => part !== "");
2021
+ lines.push(`- ${parts.join(" \xB7 ")}`);
2022
+ }
2023
+ lines.push("");
1914
2024
  lines.push(es ? "## Cambios activos" : "## Active changes");
1915
2025
  lines.push("");
1916
2026
  if (input.changes.length === 0) lines.push(es ? "_Sin cambios activos._" : "_No active changes._");
@@ -1926,7 +2036,7 @@ ${input.archived} change(s) in \`changes/archive/\`.`);
1926
2036
  }
1927
2037
 
1928
2038
  // src/github.ts
1929
- import path5 from "path";
2039
+ import path6 from "path";
1930
2040
  import { parse as parseYaml5 } from "yaml";
1931
2041
 
1932
2042
  // src/exec.ts
@@ -2071,8 +2181,8 @@ function issueBody(input) {
2071
2181
  lines.push(`- Siguiente: \`${input.next}\` \u2014 ${input.nextDescription}`);
2072
2182
  lines.push("");
2073
2183
  lines.push("### Artefactos");
2074
- lines.push(`- Spec: \`${path5.posix.join(".sdd", "changes", change.slug, "spec.md")}\``);
2075
- if (change.planPath) lines.push(`- Plan: \`${path5.posix.join(".sdd", "changes", change.slug, "plan.md")}\``);
2184
+ lines.push(`- Spec: \`${path6.posix.join(".sdd", "changes", change.slug, "spec.md")}\``);
2185
+ if (change.planPath) lines.push(`- Plan: \`${path6.posix.join(".sdd", "changes", change.slug, "plan.md")}\``);
2076
2186
  if (change.tasks) lines.push(`- Tareas: ${change.tasks.counts.done}/${change.tasks.counts.total}`);
2077
2187
  if (change.verify) lines.push(`- Evidencia registrada: ${change.verify.evidence.length}`);
2078
2188
  lines.push("");
@@ -2140,10 +2250,10 @@ async function commentIssue(root, number, body, runner = defaultRunner) {
2140
2250
  return result.ok ? [] : [diag("ATLAS-GH-006", "error", `No se pudo comentar el issue #${number}: ${(result.stderr || result.stdout).trim().slice(0, 300)}`)];
2141
2251
  }
2142
2252
  async function patchChangeMeta(root, slug, patch) {
2143
- const file = path5.join(path5.resolve(root), ".sdd", "changes", slug, "meta.yaml");
2253
+ const file = path6.join(path6.resolve(root), ".sdd", "changes", slug, "meta.yaml");
2144
2254
  const raw = await readTextIfExists(file);
2145
2255
  if (raw === void 0) {
2146
- return { path: file, diagnostics: [diag("ATLAS-GH-007", "error", `No existe ${path5.relative(root, file)}`)] };
2256
+ return { path: file, diagnostics: [diag("ATLAS-GH-007", "error", `No existe ${path6.relative(root, file)}`)] };
2147
2257
  }
2148
2258
  let data;
2149
2259
  try {
@@ -2197,7 +2307,7 @@ async function approveFromGithub(opts) {
2197
2307
  const { signApproval: signApproval2 } = await import("./approvals-2PO6223I.js");
2198
2308
  const signed = await signApproval2({
2199
2309
  root: opts.root,
2200
- artifact: path5.posix.join("changes", opts.slug, "spec.md"),
2310
+ artifact: path6.posix.join("changes", opts.slug, "spec.md"),
2201
2311
  by,
2202
2312
  channel: "tracker",
2203
2313
  note: `Aprobado v\xEDa GitHub issue #${number} (etiqueta "${label}")`
@@ -2257,7 +2367,7 @@ async function syncGithubIssue(root, slug, opts = {}) {
2257
2367
  }
2258
2368
 
2259
2369
  // src/evidence.ts
2260
- import path6 from "path";
2370
+ import path7 from "path";
2261
2371
  var SCENARIO_HEAD_RE2 = /^###\s+(REQ-[A-Z0-9-]+-S\d+)\b/;
2262
2372
  function locateBlocks(content) {
2263
2373
  const normalized = content.replace(/\r\n?/g, "\n");
@@ -2295,10 +2405,10 @@ function buildEvidenceYaml(evidence) {
2295
2405
  return lines.join("\n");
2296
2406
  }
2297
2407
  async function recordEvidence(opts) {
2298
- const root = path6.resolve(opts.root);
2408
+ const root = path7.resolve(opts.root);
2299
2409
  const slug = opts.slug;
2300
2410
  const fileKind = opts.file ?? "verify";
2301
- const file = path6.join(root, ".sdd", "changes", slug, fileKind === "fix" ? "fix.md" : "verify.md");
2411
+ const file = path7.join(root, ".sdd", "changes", slug, fileKind === "fix" ? "fix.md" : "verify.md");
2302
2412
  const diagnostics = [];
2303
2413
  const scenario = opts.scenario.toUpperCase();
2304
2414
  if (!SCENARIO_ID_RE.test(scenario)) {
@@ -2395,7 +2505,7 @@ function evidenceSummary(content) {
2395
2505
  }
2396
2506
 
2397
2507
  // src/runs.ts
2398
- import path7 from "path";
2508
+ import path8 from "path";
2399
2509
  import { randomBytes } from "crypto";
2400
2510
  var RUN_EVENT_TYPES = [
2401
2511
  "run_started",
@@ -2421,7 +2531,7 @@ function generateRunId(now = /* @__PURE__ */ new Date()) {
2421
2531
  return `${iso.slice(0, 8)}-${iso.slice(8, 14)}-${randomBytes(3).toString("hex")}`;
2422
2532
  }
2423
2533
  function runsDir(root) {
2424
- return path7.join(path7.resolve(root), ".sdd", "runs");
2534
+ return path8.join(path8.resolve(root), ".sdd", "runs");
2425
2535
  }
2426
2536
  async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Date()) {
2427
2537
  const runId = generateRunId(now);
@@ -2434,25 +2544,25 @@ async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Da
2434
2544
  updatedAt: localStamp(now)
2435
2545
  };
2436
2546
  if (inputs) state.inputs = inputs;
2437
- const dir = path7.join(runsDir(root), runId);
2547
+ const dir = path8.join(runsDir(root), runId);
2438
2548
  await ensureDir(dir);
2439
- await writeText(path7.join(dir, "state.json"), `${JSON.stringify(state, null, 2)}
2549
+ await writeText(path8.join(dir, "state.json"), `${JSON.stringify(state, null, 2)}
2440
2550
  `);
2441
- await writeText(path7.join(dir, "events.jsonl"), "");
2551
+ await writeText(path8.join(dir, "events.jsonl"), "");
2442
2552
  await appendRunEvent(root, runId, "run_started", { slug, phase });
2443
2553
  return state;
2444
2554
  }
2445
2555
  async function appendRunEvent(root, runId, type, data, now = /* @__PURE__ */ new Date()) {
2446
2556
  const event = { eventId: randomBytes(4).toString("hex"), at: localStamp(now), type };
2447
2557
  if (data) event.data = data;
2448
- const file = path7.join(runsDir(root), runId, "events.jsonl");
2558
+ const file = path8.join(runsDir(root), runId, "events.jsonl");
2449
2559
  const previous = await readTextIfExists(file) ?? "";
2450
2560
  await writeText(file, `${previous}${JSON.stringify(event)}
2451
2561
  `);
2452
2562
  return event;
2453
2563
  }
2454
2564
  async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Date()) {
2455
- const file = path7.join(runsDir(root), runId, "state.json");
2565
+ const file = path8.join(runsDir(root), runId, "state.json");
2456
2566
  const raw = await readTextIfExists(file);
2457
2567
  if (raw === void 0) return;
2458
2568
  const state = JSON.parse(raw);
@@ -2462,13 +2572,13 @@ async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Da
2462
2572
  `);
2463
2573
  }
2464
2574
  async function readRun(root, runId) {
2465
- const dir = path7.join(runsDir(root), runId);
2466
- const stateRaw = await readTextIfExists(path7.join(dir, "state.json"));
2575
+ const dir = path8.join(runsDir(root), runId);
2576
+ const stateRaw = await readTextIfExists(path8.join(dir, "state.json"));
2467
2577
  if (stateRaw === void 0) {
2468
2578
  return { diagnostics: [diag("ATLAS-RUN-001", "error", `No existe el run ${runId}`, { path: dir })] };
2469
2579
  }
2470
2580
  const state = JSON.parse(stateRaw);
2471
- const eventsRaw = await readTextIfExists(path7.join(dir, "events.jsonl")) ?? "";
2581
+ const eventsRaw = await readTextIfExists(path8.join(dir, "events.jsonl")) ?? "";
2472
2582
  const events = [];
2473
2583
  for (const line of eventsRaw.split("\n")) {
2474
2584
  if (line.trim() === "") continue;
@@ -2485,7 +2595,7 @@ async function listRuns(root, filter = {}) {
2485
2595
  const ids = await listDirs(dir);
2486
2596
  const out = [];
2487
2597
  for (const id of ids) {
2488
- const raw = await readTextIfExists(path7.join(dir, id, "state.json"));
2598
+ const raw = await readTextIfExists(path8.join(dir, id, "state.json"));
2489
2599
  if (raw === void 0) continue;
2490
2600
  try {
2491
2601
  const state = JSON.parse(raw);
@@ -2499,11 +2609,11 @@ async function listRuns(root, filter = {}) {
2499
2609
  }
2500
2610
 
2501
2611
  // src/analyze.ts
2502
- import path10 from "path";
2612
+ import path11 from "path";
2503
2613
 
2504
2614
  // src/mockups.ts
2505
- import path8 from "path";
2506
- import { parse as parseYaml6, stringify as stringifyYaml4 } from "yaml";
2615
+ import path9 from "path";
2616
+ import { parse as parseYaml6, stringify as stringifyYaml5 } from "yaml";
2507
2617
  import { z as z4 } from "zod";
2508
2618
  var mockupManifestSchema = z4.object({
2509
2619
  schema_version: z4.number().int().positive().default(1),
@@ -2528,20 +2638,20 @@ var mockupManifestSchema = z4.object({
2528
2638
  });
2529
2639
  var TOKEN_CANDIDATES = ["design/tokens.json", "DESIGN.md", "design/DESIGN.md", ".sdd/design/tokens.json"];
2530
2640
  function mockupsDir(root, slug) {
2531
- return path8.join(path8.resolve(root), ".sdd", "changes", slug, "mockups");
2641
+ return path9.join(path9.resolve(root), ".sdd", "changes", slug, "mockups");
2532
2642
  }
2533
2643
  async function tokensFile(root) {
2534
2644
  for (const candidate of TOKEN_CANDIDATES) {
2535
- const abs = path8.join(root, candidate);
2645
+ const abs = path9.join(root, candidate);
2536
2646
  if (await exists(abs)) return candidate;
2537
2647
  }
2538
2648
  return void 0;
2539
2649
  }
2540
2650
  async function computeInputsHash(root, change) {
2541
- const deltaPath = path8.join(change.dir, "spec.md");
2651
+ const deltaPath = path9.join(change.dir, "spec.md");
2542
2652
  const delta = await readTextIfExists(deltaPath) ?? "";
2543
2653
  const tokens = await tokensFile(root);
2544
- const tokensContent = tokens ? await readTextIfExists(path8.join(root, tokens)) ?? "" : "";
2654
+ const tokensContent = tokens ? await readTextIfExists(path9.join(root, tokens)) ?? "" : "";
2545
2655
  return artifactHash(`${delta}
2546
2656
  ---tokens---
2547
2657
  ${tokensContent}`);
@@ -2566,7 +2676,7 @@ function planMockups(change, platform = "web") {
2566
2676
  return { platform, screens };
2567
2677
  }
2568
2678
  async function readMockupManifest(root, slug) {
2569
- const file = path8.join(mockupsDir(root, slug), "manifest.yaml");
2679
+ const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
2570
2680
  const raw = await readTextIfExists(file);
2571
2681
  if (raw === void 0) return { diagnostics: [] };
2572
2682
  let data;
@@ -2658,7 +2768,7 @@ async function checkMockups(root, slug, change) {
2658
2768
  const { manifest, path: manifestPath, diagnostics } = await readMockupManifest(root, slug);
2659
2769
  const findings = [...diagnostics];
2660
2770
  if (!manifest || !manifestPath) {
2661
- return { findings: [...findings, diag("LINT-MKP-004", "error", "No existe mockups/manifest.yaml", { path: path8.join(dir, "manifest.yaml") })], stale: false };
2771
+ return { findings: [...findings, diag("LINT-MKP-004", "error", "No existe mockups/manifest.yaml", { path: path9.join(dir, "manifest.yaml") })], stale: false };
2662
2772
  }
2663
2773
  const known = /* @__PURE__ */ new Set();
2664
2774
  for (const req of [...change.delta?.added ?? [], ...change.delta?.modified ?? []]) {
@@ -2666,12 +2776,12 @@ async function checkMockups(root, slug, change) {
2666
2776
  }
2667
2777
  findings.push(...lintMockupManifest(manifest, known, manifestPath));
2668
2778
  for (const screen of manifest.screens) {
2669
- const html = await readTextIfExists(path8.join(dir, screen.file));
2779
+ const html = await readTextIfExists(path9.join(dir, screen.file));
2670
2780
  if (html === void 0) {
2671
- findings.push(diag("ATLAS-MKP-004", "error", `Falta el archivo del mockup: ${screen.file}`, { path: path8.join(dir, screen.file) }));
2781
+ findings.push(diag("ATLAS-MKP-004", "error", `Falta el archivo del mockup: ${screen.file}`, { path: path9.join(dir, screen.file) }));
2672
2782
  continue;
2673
2783
  }
2674
- findings.push(...lintMockupHtml(html, path8.join(dir, screen.file)));
2784
+ findings.push(...lintMockupHtml(html, path9.join(dir, screen.file)));
2675
2785
  }
2676
2786
  const inputsHash = await computeInputsHash(root, change);
2677
2787
  const stale = manifest.inputsHash !== void 0 && manifest.inputsHash !== inputsHash;
@@ -2686,7 +2796,7 @@ async function checkMockups(root, slug, change) {
2686
2796
  return { findings, stale, manifest, manifestPath };
2687
2797
  }
2688
2798
  async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date()) {
2689
- const file = path8.join(mockupsDir(root, slug), "plan.yaml");
2799
+ const file = path9.join(mockupsDir(root, slug), "plan.yaml");
2690
2800
  if (await exists(file)) return file;
2691
2801
  const doc = {
2692
2802
  schema_version: 1,
@@ -2694,11 +2804,11 @@ async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date(
2694
2804
  platform: plan.platform,
2695
2805
  screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints }))
2696
2806
  };
2697
- await writeText(file, stringifyYaml4(doc, { lineWidth: 120 }));
2807
+ await writeText(file, stringifyYaml5(doc, { lineWidth: 120 }));
2698
2808
  return file;
2699
2809
  }
2700
2810
  async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PURE__ */ new Date()) {
2701
- const file = path8.join(mockupsDir(root, slug), "manifest.yaml");
2811
+ const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
2702
2812
  if (await exists(file)) return file;
2703
2813
  const doc = {
2704
2814
  schema_version: 1,
@@ -2710,16 +2820,16 @@ async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PUR
2710
2820
  screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints })),
2711
2821
  screenshots: []
2712
2822
  };
2713
- await writeText(file, stringifyYaml4(doc, { lineWidth: 120 }));
2823
+ await writeText(file, stringifyYaml5(doc, { lineWidth: 120 }));
2714
2824
  return file;
2715
2825
  }
2716
2826
  async function updateMockupScreenshots(root, slug, screenshots) {
2717
- const file = path8.join(mockupsDir(root, slug), "manifest.yaml");
2827
+ const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
2718
2828
  const raw = await readTextIfExists(file);
2719
2829
  if (raw === void 0) return void 0;
2720
2830
  const data = parseYaml6(raw) ?? {};
2721
2831
  data["screenshots"] = screenshots;
2722
- await writeText(file, stringifyYaml4(data, { lineWidth: 120 }));
2832
+ await writeText(file, stringifyYaml5(data, { lineWidth: 120 }));
2723
2833
  return file;
2724
2834
  }
2725
2835
  async function captureMockups(root, slug, manifest, now = /* @__PURE__ */ new Date()) {
@@ -2744,9 +2854,9 @@ async function captureMockups(root, slug, manifest, now = /* @__PURE__ */ new Da
2744
2854
  const page2 = await browser.newPage();
2745
2855
  for (const bp of screen.breakpoints && screen.breakpoints.length > 0 ? screen.breakpoints : [1440]) {
2746
2856
  await page2.setViewportSize({ width: bp, height: 900 });
2747
- await page2.goto(`file://${path8.join(dir, screen.file).split(path8.sep).join("/")}`);
2748
- const rel = path8.posix.join("screens", `${screen.id}-${bp}.png`);
2749
- await page2.screenshot({ path: path8.join(dir, rel) });
2857
+ await page2.goto(`file://${path9.join(dir, screen.file).split(path9.sep).join("/")}`);
2858
+ const rel = path9.posix.join("screens", `${screen.id}-${bp}.png`);
2859
+ await page2.screenshot({ path: path9.join(dir, rel) });
2750
2860
  screenshots.push(rel);
2751
2861
  }
2752
2862
  await page2.close();
@@ -2762,7 +2872,7 @@ async function mockupsReady(root, slug, change) {
2762
2872
  return Boolean(check.manifest && check.manifest.screens.length > 0 && !check.stale);
2763
2873
  }
2764
2874
  async function setMockupRequirement(root, slug, value) {
2765
- const file = path8.join(root, ".sdd", "changes", slug, "meta.yaml");
2875
+ const file = path9.join(root, ".sdd", "changes", slug, "meta.yaml");
2766
2876
  const raw = await readTextIfExists(file) ?? `schema_version: 1
2767
2877
  slug: ${slug}
2768
2878
  lane: standard
@@ -2781,7 +2891,7 @@ lane: standard
2781
2891
  }
2782
2892
 
2783
2893
  // src/packs.ts
2784
- import path9 from "path";
2894
+ import path10 from "path";
2785
2895
  import { parse as parseYaml7 } from "yaml";
2786
2896
  import { z as z5 } from "zod";
2787
2897
  var checkSchema = z5.object({
@@ -2997,12 +3107,12 @@ ${texts.scenarios}`);
2997
3107
  }
2998
3108
  }
2999
3109
  async function loadProjectPacks(sddDir) {
3000
- const dir = path9.join(sddDir, "packs");
3110
+ const dir = path10.join(sddDir, "packs");
3001
3111
  const diagnostics = [];
3002
3112
  const packs = [];
3003
3113
  for (const entry of await listDir(dir)) {
3004
3114
  if (!/\.ya?ml$/i.test(entry)) continue;
3005
- const file = path9.join(dir, entry);
3115
+ const file = path10.join(dir, entry);
3006
3116
  const raw = await readTextIfExists(file);
3007
3117
  if (raw === void 0) continue;
3008
3118
  let data;
@@ -3091,7 +3201,7 @@ function packFindings(evaluations, change) {
3091
3201
  // src/analyze.ts
3092
3202
  var UI_DOMAINS = /* @__PURE__ */ new Set(["frontend", "mobile", "fullstack"]);
3093
3203
  async function runAnalyze(opts) {
3094
- const root = path10.resolve(opts.root);
3204
+ const root = path11.resolve(opts.root);
3095
3205
  const { workspace, config } = await loadWorkspace(root);
3096
3206
  const change = workspace.changes.find((c) => c.slug === opts.slug);
3097
3207
  if (!change) {
@@ -3109,7 +3219,7 @@ async function runAnalyze(opts) {
3109
3219
  for (const req of spec.spec.requirements) living.set(req.id, req);
3110
3220
  }
3111
3221
  if (change.delta) {
3112
- findings.push(...lintDelta(change.delta, living, path10.join(change.dir, "spec.md"), { language: config.spec.language }));
3222
+ findings.push(...lintDelta(change.delta, living, path11.join(change.dir, "spec.md"), { language: config.spec.language }));
3113
3223
  }
3114
3224
  const trace = checkTrace({ specs: workspace.specs, change, requireEvidence: false });
3115
3225
  findings.push(...trace.findings);
@@ -3158,7 +3268,7 @@ async function runAnalyze(opts) {
3158
3268
  if (mockups) result.mockups = mockups;
3159
3269
  if (packs) result.packs = packs;
3160
3270
  if (opts.write !== false) {
3161
- const file = path10.join(change.dir, "analyze.md");
3271
+ const file = path11.join(change.dir, "analyze.md");
3162
3272
  await writeText(file, renderAnalyze(change, result, localStamp(opts.now)));
3163
3273
  result.path = file;
3164
3274
  }
@@ -3201,9 +3311,9 @@ function renderAnalyze(change, result, generatedAt) {
3201
3311
  }
3202
3312
 
3203
3313
  // src/metrics.ts
3204
- import path11 from "path";
3314
+ import path12 from "path";
3205
3315
  async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
3206
- const resolved = path11.resolve(root);
3316
+ const resolved = path12.resolve(root);
3207
3317
  const { workspace, config } = await loadWorkspace(resolved);
3208
3318
  const approvals = await loadApprovals(workspace.sddDir);
3209
3319
  const living = /* @__PURE__ */ new Map();
@@ -3218,7 +3328,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
3218
3328
  const deltaContent = await readDelta(change);
3219
3329
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent);
3220
3330
  const findings = [];
3221
- if (change.delta) findings.push(...lintDelta(change.delta, living, path11.join(change.dir, "spec.md"), { language: config.spec.language }));
3331
+ if (change.delta) findings.push(...lintDelta(change.delta, living, path12.join(change.dir, "spec.md"), { language: config.spec.language }));
3222
3332
  const trace = checkTrace({
3223
3333
  specs: workspace.specs,
3224
3334
  change,
@@ -3283,7 +3393,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
3283
3393
  attention.push({ slug: change.slug, kind: "missing-evidence", detail: `evidencia ${change.scenariosPassed}/${change.scenariosTotal}` });
3284
3394
  }
3285
3395
  }
3286
- const archiveDir = path11.join(workspace.sddDir, "changes", "archive");
3396
+ const archiveDir = path12.join(workspace.sddDir, "changes", "archive");
3287
3397
  const archivedEntries = await listDirs(archiveDir);
3288
3398
  const throughputByMonth = {};
3289
3399
  for (const entry of archivedEntries) {
@@ -3321,15 +3431,15 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
3321
3431
  }
3322
3432
  async function readDelta(change) {
3323
3433
  const { readTextIfExists: readTextIfExists2 } = await import("./fsx-VF2P7ALA.js");
3324
- return readTextIfExists2(path11.join(change.dir, "spec.md"));
3434
+ return readTextIfExists2(path12.join(change.dir, "spec.md"));
3325
3435
  }
3326
3436
 
3327
3437
  // src/present.ts
3328
- import path12 from "path";
3438
+ import path13 from "path";
3329
3439
  import { escapeHtml, inlineMarkdown, renderMarkdown as renderRich } from "@specatlas/render";
3330
3440
  import { defaultTokens, renderDocument, renderStyles } from "@specatlas/render";
3331
3441
  async function generatePresentation(opts) {
3332
- const root = path12.resolve(opts.root);
3442
+ const root = path13.resolve(opts.root);
3333
3443
  const { workspace, config } = await loadWorkspace(root);
3334
3444
  const language = config.project.language;
3335
3445
  const change = workspace.changes.find((c) => c.slug === opts.slug);
@@ -3339,17 +3449,17 @@ async function generatePresentation(opts) {
3339
3449
  if (!change.delta) {
3340
3450
  return { slug: opts.slug, diagnostics: [diag("ATLAS-PRESENT-001", "error", `El cambio "${opts.slug}" no tiene spec.md (delta)`)] };
3341
3451
  }
3342
- const deltaPath = path12.join(change.dir, "spec.md");
3452
+ const deltaPath = path13.join(change.dir, "spec.md");
3343
3453
  const deltaContent = await readTextIfExists(deltaPath) ?? "";
3344
3454
  const hash = artifactHash(deltaContent);
3345
3455
  const now = opts.now ?? /* @__PURE__ */ new Date();
3346
- const presentationDir = path12.join(change.dir, "presentation");
3347
- const mockupsCopyDir = path12.join(presentationDir, "mockups");
3456
+ const presentationDir = path13.join(change.dir, "presentation");
3457
+ const mockupsCopyDir = path13.join(presentationDir, "mockups");
3348
3458
  await ensureDir(presentationDir);
3349
3459
  const mockupInfo = await copyMockups(root, change, mockupsCopyDir);
3350
- const proposalRaw = await readTextIfExists(path12.join(change.dir, "proposal.md"));
3460
+ const proposalRaw = await readTextIfExists(path13.join(change.dir, "proposal.md"));
3351
3461
  const proposalBody = proposalRaw ? parseFrontmatter(proposalRaw).body : "";
3352
- const approvals = await loadApprovals(path12.join(root, ".sdd"));
3462
+ const approvals = await loadApprovals(path13.join(root, ".sdd"));
3353
3463
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent);
3354
3464
  const labels = labelsFor(language);
3355
3465
  const html = page({
@@ -3370,7 +3480,7 @@ async function generatePresentation(opts) {
3370
3480
  approveHint: `satlas approve ${change.slug} --by "<nombre>" --channel presentation`,
3371
3481
  ...approval.status === "valid" && approval.approvedBy ? { approval: { by: approval.approvedBy, at: approval.approvedAt ?? "" } } : {}
3372
3482
  });
3373
- const outFile = path12.join(presentationDir, "index.html");
3483
+ const outFile = path13.join(presentationDir, "index.html");
3374
3484
  await writeText(outFile, html);
3375
3485
  return { slug: change.slug, path: outFile, hash, diagnostics: mockupInfo.diagnostics };
3376
3486
  }
@@ -3382,12 +3492,12 @@ async function copyMockups(root, change, destDir) {
3382
3492
  await ensureDir(destDir);
3383
3493
  const screens = [];
3384
3494
  for (const screen of manifest.screens) {
3385
- const src = path12.join(dir, screen.file);
3495
+ const src = path13.join(dir, screen.file);
3386
3496
  if (!await exists(src)) {
3387
3497
  diagnostics.push(diag("ATLAS-PRESENT-002", "warning", `El mockup ${screen.file} no existe y no se incluir\xE1`, { path: src }));
3388
3498
  continue;
3389
3499
  }
3390
- await copyFile(src, path12.join(destDir, screen.file));
3500
+ await copyFile(src, path13.join(destDir, screen.file));
3391
3501
  screens.push({
3392
3502
  id: screen.id,
3393
3503
  title: screen.title ?? screen.id,
@@ -3397,13 +3507,13 @@ async function copyMockups(root, change, destDir) {
3397
3507
  });
3398
3508
  }
3399
3509
  const screenshots = [];
3400
- const shotsDir = path12.join(dir, "screens");
3510
+ const shotsDir = path13.join(dir, "screens");
3401
3511
  if (await exists(shotsDir)) {
3402
- await ensureDir(path12.join(destDir, "screens"));
3512
+ await ensureDir(path13.join(destDir, "screens"));
3403
3513
  const { listDir: listDir4 } = await import("./fsx-VF2P7ALA.js");
3404
3514
  for (const file of await listDir4(shotsDir)) {
3405
3515
  if (!/\.(png|jpe?g|webp)$/i.test(file)) continue;
3406
- await copyFile(path12.join(shotsDir, file), path12.join(destDir, "screens", file));
3516
+ await copyFile(path13.join(shotsDir, file), path13.join(destDir, "screens", file));
3407
3517
  screenshots.push(`mockups/screens/${file}`);
3408
3518
  }
3409
3519
  }
@@ -3578,7 +3688,7 @@ function renderMarkdown(markdown) {
3578
3688
  }
3579
3689
 
3580
3690
  // src/adopt.ts
3581
- import path13 from "path";
3691
+ import path14 from "path";
3582
3692
  var COMMON_ROOTS = ["src", "app", "lib", "modules", "services", "packages", "api", "features", "domain", "internal", "components"];
3583
3693
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage", ".git", ".sdd", ".opencode", ".claude", "test", "tests", "__tests__", "e2e", "docs", "scripts", "assets", "public", "migrations", "vendor", "bin", "obj"]);
3584
3694
  var SOURCE_EXT = /* @__PURE__ */ new Set([
@@ -3622,11 +3732,11 @@ var SOURCE_EXT = /* @__PURE__ */ new Set([
3622
3732
  ".groovy"
3623
3733
  ]);
3624
3734
  async function adoptWorkspace(opts) {
3625
- const root = path13.resolve(opts.root);
3735
+ const root = path14.resolve(opts.root);
3626
3736
  const dryRun = opts.dryRun ?? false;
3627
3737
  const diagnostics = [];
3628
- const sddDir = path13.join(root, ".sdd");
3629
- if (!await exists(path13.join(sddDir, "config.yaml"))) {
3738
+ const sddDir = path14.join(root, ".sdd");
3739
+ if (!await exists(path14.join(sddDir, "config.yaml"))) {
3630
3740
  return {
3631
3741
  root,
3632
3742
  domains: [],
@@ -3637,8 +3747,8 @@ async function adoptWorkspace(opts) {
3637
3747
  }
3638
3748
  const { config } = await loadConfig(sddDir);
3639
3749
  const files = await walkFiles(root, { skipDirs: [...SKIP_DIRS] });
3640
- const inventoried = files.map((f) => toPosix(path13.relative(root, f.path)));
3641
- const relative = inventoried.filter((f) => SOURCE_EXT.has(path13.extname(f).toLowerCase()));
3750
+ const inventoried = files.map((f) => toPosix(path14.relative(root, f.path)));
3751
+ const relative = inventoried.filter((f) => SOURCE_EXT.has(path14.extname(f).toLowerCase()));
3642
3752
  if (relative.length === 0) {
3643
3753
  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>" }));
3644
3754
  }
@@ -3666,13 +3776,13 @@ async function adoptWorkspace(opts) {
3666
3776
  const createdSpecs = [];
3667
3777
  for (const name of domainNames.sort()) {
3668
3778
  const domainFiles = (counts.get(name) ?? []).sort();
3669
- const existingSpec = await exists(path13.join(sddDir, "specs", name, "spec.md"));
3779
+ const existingSpec = await exists(path14.join(sddDir, "specs", name, "spec.md"));
3670
3780
  domains.push({ name, files: domainFiles.length, samples: domainFiles.slice(0, 8), existingSpec });
3671
3781
  if (existingSpec) {
3672
- diagnostics.push(diag("ATLAS-ADOPT-003", "info", `La spec de "${name}" ya existe; se conserva`, { path: path13.join(sddDir, "specs", name, "spec.md") }));
3782
+ diagnostics.push(diag("ATLAS-ADOPT-003", "info", `La spec de "${name}" ya existe; se conserva`, { path: path14.join(sddDir, "specs", name, "spec.md") }));
3673
3783
  continue;
3674
3784
  }
3675
- const specPath = path13.join(sddDir, "specs", name, "spec.md");
3785
+ const specPath = path14.join(sddDir, "specs", name, "spec.md");
3676
3786
  if (!dryRun) {
3677
3787
  await writeText(specPath, baselineSpec(name, domainFiles));
3678
3788
  createdSpecs.push(specPath);
@@ -3680,7 +3790,7 @@ async function adoptWorkspace(opts) {
3680
3790
  createdSpecs.push(specPath);
3681
3791
  }
3682
3792
  }
3683
- const reportPath = path13.join(sddDir, "adopt-report.md");
3793
+ const reportPath = path14.join(sddDir, "adopt-report.md");
3684
3794
  if (!dryRun) {
3685
3795
  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() }));
3686
3796
  }
@@ -3700,12 +3810,12 @@ async function loadProjectProfiles(root, dirs) {
3700
3810
  if (!await exists(dir)) continue;
3701
3811
  out.push(...await loadProfilesFromDir(dir));
3702
3812
  }
3703
- const custom = path13.join(root, ".sdd", "profiles", "custom");
3813
+ const custom = path14.join(root, ".sdd", "profiles", "custom");
3704
3814
  if (await exists(custom)) out.push(...await loadProfilesFromDir(custom));
3705
3815
  return out;
3706
3816
  }
3707
3817
  async function fallbackDomainName(root, projectName) {
3708
- const pkg = await readTextIfExists(path13.join(root, "package.json"));
3818
+ const pkg = await readTextIfExists(path14.join(root, "package.json"));
3709
3819
  if (pkg) {
3710
3820
  try {
3711
3821
  const data = JSON.parse(pkg);
@@ -3793,13 +3903,13 @@ function adoptReport(input) {
3793
3903
  }
3794
3904
 
3795
3905
  // src/init.ts
3796
- import path14 from "path";
3906
+ import path15 from "path";
3797
3907
  async function initWorkspace(opts) {
3798
- const root = path14.resolve(opts.root);
3799
- const sddDir = path14.join(root, ".sdd");
3908
+ const root = path15.resolve(opts.root);
3909
+ const sddDir = path15.join(root, ".sdd");
3800
3910
  const diagnostics = [];
3801
3911
  const created = [];
3802
- if (await exists(path14.join(sddDir, "config.yaml"))) {
3912
+ if (await exists(path15.join(sddDir, "config.yaml"))) {
3803
3913
  return {
3804
3914
  sddDir,
3805
3915
  created,
@@ -3809,29 +3919,29 @@ async function initWorkspace(opts) {
3809
3919
  created.push(...await ensureSddDirs(sddDir));
3810
3920
  const language = opts.language ?? "es";
3811
3921
  const cfg = defaultConfig({ name: opts.name, language });
3812
- cfg.project.name = opts.name ?? path14.basename(root);
3922
+ cfg.project.name = opts.name ?? path15.basename(root);
3813
3923
  await writeConfig(sddDir, cfg);
3814
- created.push(path14.join(sddDir, "config.yaml"));
3924
+ created.push(path15.join(sddDir, "config.yaml"));
3815
3925
  const templates = templatesFor(language);
3816
- await writeText(path14.join(sddDir, "constitution.md"), templates.constitution);
3817
- created.push(path14.join(sddDir, "constitution.md"));
3818
- await writeText(path14.join(sddDir, "glossary.md"), templates.glossary);
3819
- created.push(path14.join(sddDir, "glossary.md"));
3926
+ await writeText(path15.join(sddDir, "constitution.md"), templates.constitution);
3927
+ created.push(path15.join(sddDir, "constitution.md"));
3928
+ await writeText(path15.join(sddDir, "glossary.md"), templates.glossary);
3929
+ created.push(path15.join(sddDir, "glossary.md"));
3820
3930
  const detected = await detectIfPossible(root, opts.profilesDir);
3821
3931
  if (detected) {
3822
3932
  const yaml = detectionToYaml(detected, localStamp(opts.now));
3823
- await writeText(path14.join(sddDir, "profiles", "detected.yaml"), yaml);
3824
- created.push(path14.join(sddDir, "profiles", "detected.yaml"));
3933
+ await writeText(path15.join(sddDir, "profiles", "detected.yaml"), yaml);
3934
+ created.push(path15.join(sddDir, "profiles", "detected.yaml"));
3825
3935
  if (detected.best) {
3826
3936
  diagnostics.push(diag("ATLAS-INIT-002", "info", `Stack detectado: ${detected.best.displayName} (${detected.best.score} puntos)`, { suggestion: "Revisa .sdd/profiles/detected.yaml" }));
3827
3937
  } else {
3828
3938
  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/" }));
3829
3939
  }
3830
3940
  }
3831
- await writeText(path14.join(sddDir, "INDEX.md"), initialIndex(language, cfg.project.name));
3832
- created.push(path14.join(sddDir, "INDEX.md"));
3941
+ await writeText(path15.join(sddDir, "INDEX.md"), initialIndex(language, cfg.project.name));
3942
+ created.push(path15.join(sddDir, "INDEX.md"));
3833
3943
  if (opts.local) {
3834
- const gitignore = path14.join(root, ".gitignore");
3944
+ const gitignore = path15.join(root, ".gitignore");
3835
3945
  const current = await readTextIfExists(gitignore) ?? "";
3836
3946
  if (!/(^|\n)\.sdd\/?(\n|$)/.test(current)) {
3837
3947
  const next = current.endsWith("\n") || current === "" ? `${current}.sdd/
@@ -3866,13 +3976,13 @@ ${es ? "## Archivados\n\n0 cambio(s) en `changes/archive/`." : "## Archived\n\n0
3866
3976
  }
3867
3977
 
3868
3978
  // src/new.ts
3869
- import path15 from "path";
3979
+ import path16 from "path";
3870
3980
  var SLUG_RE = /^[a-z0-9][a-z0-9-]{1,49}$/;
3871
3981
  async function createChange(opts) {
3872
3982
  const diagnostics = [];
3873
3983
  const slug = opts.slug.trim().toLowerCase();
3874
3984
  const language = opts.language ?? opts.cfg?.project.language ?? "es";
3875
- const dir = path15.join(path15.resolve(opts.root), ".sdd", "changes", slug);
3985
+ const dir = path16.join(path16.resolve(opts.root), ".sdd", "changes", slug);
3876
3986
  if (!SLUG_RE.test(slug)) {
3877
3987
  return {
3878
3988
  slug,
@@ -3903,20 +4013,20 @@ async function createChange(opts) {
3903
4013
  await ensureDir(dir);
3904
4014
  const templates = templatesFor(language);
3905
4015
  const files = [];
3906
- const metaFile = path15.join(dir, "meta.yaml");
4016
+ const metaFile = path16.join(dir, "meta.yaml");
3907
4017
  await writeText(metaFile, changeMetaYaml(meta));
3908
4018
  files.push(metaFile);
3909
4019
  if (lane === "fix") {
3910
- const fixFile = path15.join(dir, "fix.md");
4020
+ const fixFile = path16.join(dir, "fix.md");
3911
4021
  await writeText(fixFile, renderTemplate(templates.fix, { TITLE: title }));
3912
4022
  files.push(fixFile);
3913
4023
  return { slug, dir, files, diagnostics };
3914
4024
  }
3915
- const proposalFile = path15.join(dir, "proposal.md");
4025
+ const proposalFile = path16.join(dir, "proposal.md");
3916
4026
  await writeText(proposalFile, renderTemplate(templates.proposal, { TITLE: title }));
3917
4027
  files.push(proposalFile);
3918
4028
  const domainUpper = domain.replace(/[^a-z0-9]/gi, "").toUpperCase() || "GEN";
3919
- const deltaFile = path15.join(dir, "spec.md");
4029
+ const deltaFile = path16.join(dir, "spec.md");
3920
4030
  await writeText(deltaFile, templates.specDelta({ title, domainUpper, domain }));
3921
4031
  files.push(deltaFile);
3922
4032
  return { slug, dir, files, diagnostics };
@@ -3925,7 +4035,7 @@ async function createChange(opts) {
3925
4035
  // src/archive.ts
3926
4036
  import { promises as fs } from "fs";
3927
4037
  import { cp, rename, rm } from "fs/promises";
3928
- import path16 from "path";
4038
+ import path17 from "path";
3929
4039
  var REQ_HEADER_RE = /^###\s+(?:Requisito|Requirement):\s+(REQ-[A-Z0-9-]+)\s*(?:—|-|–)\s*(.*)$/;
3930
4040
  function foldDelta(livingBody, delta, language = "es") {
3931
4041
  const diagnostics = [];
@@ -4034,7 +4144,7 @@ async function restoreFile(file, previous) {
4034
4144
  }
4035
4145
  }
4036
4146
  async function archiveChange(opts) {
4037
- const root = path16.resolve(opts.root);
4147
+ const root = path17.resolve(opts.root);
4038
4148
  const diagnostics = [];
4039
4149
  const { config } = await loadWorkspace(root);
4040
4150
  const language = opts.language ?? config.project.language;
@@ -4050,16 +4160,43 @@ async function archiveChange(opts) {
4050
4160
  return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
4051
4161
  }
4052
4162
  const nowFix = opts.now ?? /* @__PURE__ */ new Date();
4053
- const targetFix = path16.join(root, ".sdd", "changes", "archive", `${localMonth(nowFix)}-${opts.slug}`);
4163
+ const targetFix = path17.join(root, ".sdd", "changes", "archive", `${localMonth(nowFix)}-${opts.slug}`);
4054
4164
  if (await exists(targetFix)) {
4055
4165
  diagnostics.push(diag("ATLAS-ARCH-002", "error", `Ya existe un cambio archivado en ${targetFix}`, { path: targetFix }));
4056
4166
  return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
4057
4167
  }
4168
+ let livingFix;
4169
+ let livingCreated = false;
4170
+ if (!opts.dryRun) {
4171
+ const fixRaw = await readTextIfExists(path17.join(change.dir, "fix.md")) ?? "";
4172
+ try {
4173
+ const written = await writeLivingFix(root, {
4174
+ slug: opts.slug,
4175
+ date: localDate(nowFix),
4176
+ result: "pass",
4177
+ ...change.meta.domain !== void 0 ? { domain: change.meta.domain } : {},
4178
+ ...change.meta.title !== void 0 ? { title: change.meta.title } : {},
4179
+ covers: change.fixCovers ?? [],
4180
+ content: fixRaw
4181
+ });
4182
+ livingFix = written.relativePath;
4183
+ livingCreated = written.created;
4184
+ } catch (error) {
4185
+ diagnostics.push(
4186
+ diag("ATLAS-ARCH-006", "error", `No se pudo conservar el fix vivo: ${error.message}`, {
4187
+ path: path17.join(root, ".sdd", "fixes"),
4188
+ suggestion: "Revisa los permisos de .sdd/fixes y vuelve a intentar; el fix sigue sin archivar"
4189
+ })
4190
+ );
4191
+ return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: false };
4192
+ }
4193
+ }
4058
4194
  if (!opts.dryRun) {
4059
- await ensureDir(path16.dirname(targetFix));
4195
+ await ensureDir(path17.dirname(targetFix));
4060
4196
  try {
4061
4197
  await moveDirectory(change.dir, targetFix);
4062
4198
  } catch (error) {
4199
+ if (livingCreated && livingFix) await removeFile(path17.join(root, livingFix));
4063
4200
  diagnostics.push(
4064
4201
  diag("ATLAS-ARCH-004", "error", `No se pudo mover el cambio al hist\xF3rico: ${error.message}`, {
4065
4202
  path: change.dir,
@@ -4068,9 +4205,16 @@ async function archiveChange(opts) {
4068
4205
  );
4069
4206
  return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: false };
4070
4207
  }
4071
- await regenerateIndex(root, config);
4208
+ await regenerateIndex(root, config, nowFix);
4072
4209
  }
4073
- return { slug: opts.slug, archivedTo: targetFix, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
4210
+ return {
4211
+ slug: opts.slug,
4212
+ archivedTo: targetFix,
4213
+ ...livingFix !== void 0 ? { livingFix } : {},
4214
+ fold: emptyFold,
4215
+ diagnostics,
4216
+ dryRun: opts.dryRun ?? false
4217
+ };
4074
4218
  }
4075
4219
  if (!change.delta) {
4076
4220
  diagnostics.push(diag("ATLAS-ARCH-003", "error", `El cambio "${opts.slug}" no tiene spec.md (delta): no hay nada que plegar`, { path: change.dir }));
@@ -4078,10 +4222,10 @@ async function archiveChange(opts) {
4078
4222
  }
4079
4223
  const domain = change.meta.domain;
4080
4224
  if (!domain) {
4081
- diagnostics.push(diag("ATLAS-ARCH-001", "error", `El cambio "${opts.slug}" no declara domain en meta.yaml`, { path: path16.join(change.dir, "meta.yaml") }));
4225
+ diagnostics.push(diag("ATLAS-ARCH-001", "error", `El cambio "${opts.slug}" no declara domain en meta.yaml`, { path: path17.join(change.dir, "meta.yaml") }));
4082
4226
  return { slug: opts.slug, fold: { content: "", applied: { added: [], modified: [], removed: [], renamed: [] }, diagnostics }, diagnostics, dryRun: opts.dryRun ?? false };
4083
4227
  }
4084
- const specFile = path16.join(root, ".sdd", "specs", domain, "spec.md");
4228
+ const specFile = path17.join(root, ".sdd", "specs", domain, "spec.md");
4085
4229
  const existing = await readTextIfExists(specFile);
4086
4230
  const base = existing ?? `---
4087
4231
  domain: ${domain}
@@ -4111,8 +4255,8 @@ ${Object.entries(nextFm).map(([k, v]) => `${k}: ${String(v)}`).join("\n")}
4111
4255
 
4112
4256
  ${body}`;
4113
4257
  const month = localMonth(now);
4114
- const archiveDir = path16.join(root, ".sdd", "changes", "archive");
4115
- const target = path16.join(archiveDir, `${month}-${opts.slug}`);
4258
+ const archiveDir = path17.join(root, ".sdd", "changes", "archive");
4259
+ const target = path17.join(archiveDir, `${month}-${opts.slug}`);
4116
4260
  if (await exists(target)) {
4117
4261
  diagnostics.push(diag("ATLAS-ARCH-002", "error", `Ya existe un cambio archivado en ${target}`, { path: target }));
4118
4262
  return { slug: opts.slug, domain, fold, diagnostics, dryRun: opts.dryRun ?? false };
@@ -4138,23 +4282,31 @@ ${body}`;
4138
4282
  }
4139
4283
  async function regenerateIndex(root, cfg, now = /* @__PURE__ */ new Date()) {
4140
4284
  const { workspace, config } = cfg ? { workspace: (await loadWorkspace(root)).workspace, config: cfg } : await loadWorkspace(root);
4141
- const archiveDir = path16.join(root, ".sdd", "changes", "archive");
4285
+ const archiveDir = path17.join(root, ".sdd", "changes", "archive");
4142
4286
  const archived = await exists(archiveDir) ? (await fs.readdir(archiveDir)).filter((e) => !e.startsWith(".")).length : 0;
4287
+ const fixes = await loadLivingFixes(root);
4143
4288
  const markdown = indexMarkdown({
4144
4289
  projectName: config.project.name,
4145
4290
  language: config.project.language,
4146
4291
  specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length })),
4147
4292
  changes: workspace.changes.map((c) => ({ slug: c.slug, lane: c.meta?.lane ?? config.lanes.default })),
4293
+ fixes: fixes.map((fix) => ({ slug: fix.slug, date: fix.date, result: fix.result, ...fix.domain !== void 0 ? { domain: fix.domain } : {} })),
4148
4294
  archived
4149
4295
  });
4150
4296
  void now;
4151
- await writeText(path16.join(root, ".sdd", "INDEX.md"), markdown);
4297
+ await writeText(path17.join(root, ".sdd", "INDEX.md"), markdown);
4298
+ }
4299
+ async function removeFile(file) {
4300
+ try {
4301
+ await rm(file, { force: true });
4302
+ } catch {
4303
+ }
4152
4304
  }
4153
4305
 
4154
4306
  // src/migrations.ts
4155
4307
  import { promises as fs2 } from "fs";
4156
- import path17 from "path";
4157
- import { parse as parseYaml8, stringify as stringifyYaml5 } from "yaml";
4308
+ import path18 from "path";
4309
+ import { parse as parseYaml8, stringify as stringifyYaml6 } from "yaml";
4158
4310
  var SCHEMA_VERSION = 1;
4159
4311
  var BACKUP_DIRNAME = ".backup";
4160
4312
  var BACKUP_POINTER = ".latest";
@@ -4166,28 +4318,28 @@ var SCHEMA_MIGRATIONS = [
4166
4318
  to: SCHEMA_VERSION
4167
4319
  }
4168
4320
  ];
4169
- var FIXED_ARTIFACTS = ["config.yaml", "approvals.yaml", path17.join("profiles", "detected.yaml")];
4170
- var CHANGE_ARTIFACTS = ["meta.yaml", path17.join("mockups", "manifest.yaml")];
4321
+ var FIXED_ARTIFACTS = ["config.yaml", "approvals.yaml", path18.join("profiles", "detected.yaml")];
4322
+ var CHANGE_ARTIFACTS = ["meta.yaml", path18.join("mockups", "manifest.yaml")];
4171
4323
  async function pushChangeArtifacts(out, sddDir, relDir) {
4172
4324
  for (const rel of CHANGE_ARTIFACTS) {
4173
- const abs = path17.join(sddDir, relDir, rel);
4174
- if (await exists(abs)) out.push({ artifact: toPosix(path17.join(relDir, rel)), path: abs });
4325
+ const abs = path18.join(sddDir, relDir, rel);
4326
+ if (await exists(abs)) out.push({ artifact: toPosix(path18.join(relDir, rel)), path: abs });
4175
4327
  }
4176
4328
  }
4177
4329
  async function collectVersionedArtifacts(root) {
4178
- const sddDir = path17.join(root, ".sdd");
4330
+ const sddDir = path18.join(root, ".sdd");
4179
4331
  const out = [];
4180
4332
  for (const rel of FIXED_ARTIFACTS) {
4181
- const abs = path17.join(sddDir, rel);
4333
+ const abs = path18.join(sddDir, rel);
4182
4334
  if (await exists(abs)) out.push({ artifact: toPosix(rel), path: abs });
4183
4335
  }
4184
- const changesDir = path17.join(sddDir, "changes");
4336
+ const changesDir = path18.join(sddDir, "changes");
4185
4337
  for (const slug of await listDirs(changesDir)) {
4186
4338
  if (slug === "archive") continue;
4187
- await pushChangeArtifacts(out, sddDir, path17.join("changes", slug));
4339
+ await pushChangeArtifacts(out, sddDir, path18.join("changes", slug));
4188
4340
  }
4189
- for (const entry of await listDirs(path17.join(changesDir, "archive"))) {
4190
- await pushChangeArtifacts(out, sddDir, path17.join("changes", "archive", entry));
4341
+ for (const entry of await listDirs(path18.join(changesDir, "archive"))) {
4342
+ await pushChangeArtifacts(out, sddDir, path18.join("changes", "archive", entry));
4191
4343
  }
4192
4344
  return out;
4193
4345
  }
@@ -4272,7 +4424,7 @@ async function removeDir(dir) {
4272
4424
  } catch {
4273
4425
  }
4274
4426
  }
4275
- async function removeFile(file) {
4427
+ async function removeFile2(file) {
4276
4428
  try {
4277
4429
  await fs2.rm(file, { force: true });
4278
4430
  } catch {
@@ -4284,22 +4436,22 @@ function backupName(now) {
4284
4436
  async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
4285
4437
  const plan = await planUpgrade(root);
4286
4438
  if (plan.pending.length === 0) return { status: "up-to-date", plan, applied: [] };
4287
- const sddDir = path17.join(root, ".sdd");
4288
- const backupRoot = path17.join(sddDir, BACKUP_DIRNAME);
4439
+ const sddDir = path18.join(root, ".sdd");
4440
+ const backupRoot = path18.join(sddDir, BACKUP_DIRNAME);
4289
4441
  const name = backupName(now);
4290
- const backupDir = path17.join(backupRoot, name);
4291
- const pointerFile = path17.join(backupRoot, BACKUP_POINTER);
4442
+ const backupDir = path18.join(backupRoot, name);
4443
+ const pointerFile = path18.join(backupRoot, BACKUP_POINTER);
4292
4444
  const previous = [];
4293
4445
  try {
4294
4446
  for (const item of plan.pending) {
4295
4447
  const content = await readTextIfExists(item.path);
4296
4448
  if (content === void 0) throw new Error("el elemento desapareci\xF3 mientras se respaldaba");
4297
4449
  previous.push({ item, contents: content });
4298
- await writeText(path17.join(backupDir, "files", ...item.artifact.split("/")), content);
4450
+ await writeText(path18.join(backupDir, "files", ...item.artifact.split("/")), content);
4299
4451
  }
4300
4452
  await writeText(
4301
- path17.join(backupDir, "backup.yaml"),
4302
- stringifyYaml5(
4453
+ path18.join(backupDir, "backup.yaml"),
4454
+ stringifyYaml6(
4303
4455
  {
4304
4456
  schema_version: SCHEMA_VERSION,
4305
4457
  created_at: localStamp(now),
@@ -4313,7 +4465,7 @@ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
4313
4465
  `);
4314
4466
  } catch (err) {
4315
4467
  await removeDir(backupDir);
4316
- await removeFile(pointerFile);
4468
+ await removeFile2(pointerFile);
4317
4469
  return { status: "failed", plan, applied: [], failure: { artifact: "(respaldo)", message: err.message } };
4318
4470
  }
4319
4471
  const written = [];
@@ -4333,13 +4485,13 @@ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
4333
4485
  }
4334
4486
  }
4335
4487
  }
4336
- await removeFile(pointerFile);
4488
+ await removeFile2(pointerFile);
4337
4489
  await removeDir(backupDir);
4338
4490
  return { status: "failed", plan, applied: [], failure: { artifact: item.artifact, message: err.message } };
4339
4491
  }
4340
4492
  }
4341
4493
  for (const dir of await listDirs(backupRoot)) {
4342
- if (dir !== name) await removeDir(path17.join(backupRoot, dir));
4494
+ if (dir !== name) await removeDir(path18.join(backupRoot, dir));
4343
4495
  }
4344
4496
  return {
4345
4497
  status: "applied",
@@ -4348,20 +4500,20 @@ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
4348
4500
  backup: {
4349
4501
  name,
4350
4502
  dir: backupDir,
4351
- relativeDir: toPosix(path17.relative(root, backupDir)),
4503
+ relativeDir: toPosix(path18.relative(root, backupDir)),
4352
4504
  createdAt: localStamp(now),
4353
4505
  files: plan.pending.map((i) => i.artifact)
4354
4506
  }
4355
4507
  };
4356
4508
  }
4357
4509
  async function rollbackUpgrade(root) {
4358
- const sddDir = path17.join(root, ".sdd");
4359
- const backupRoot = path17.join(sddDir, BACKUP_DIRNAME);
4360
- const pointerFile = path17.join(backupRoot, BACKUP_POINTER);
4510
+ const sddDir = path18.join(root, ".sdd");
4511
+ const backupRoot = path18.join(sddDir, BACKUP_DIRNAME);
4512
+ const pointerFile = path18.join(backupRoot, BACKUP_POINTER);
4361
4513
  const name = (await readTextIfExists(pointerFile))?.trim();
4362
4514
  if (!name) return { status: "no-backup", restored: [] };
4363
- const backupDir = path17.join(backupRoot, name);
4364
- const manifestRaw = await readTextIfExists(path17.join(backupDir, "backup.yaml"));
4515
+ const backupDir = path18.join(backupRoot, name);
4516
+ const manifestRaw = await readTextIfExists(path18.join(backupDir, "backup.yaml"));
4365
4517
  if (manifestRaw === void 0) return { status: "no-backup", restored: [] };
4366
4518
  let files = [];
4367
4519
  try {
@@ -4372,8 +4524,8 @@ async function rollbackUpgrade(root) {
4372
4524
  }
4373
4525
  const restored = [];
4374
4526
  for (const file of files) {
4375
- const from = path17.join(backupDir, "files", ...file.artifact.split("/"));
4376
- const to = path17.join(sddDir, ...file.artifact.split("/"));
4527
+ const from = path18.join(backupDir, "files", ...file.artifact.split("/"));
4528
+ const to = path18.join(sddDir, ...file.artifact.split("/"));
4377
4529
  const content = await readTextIfExists(from);
4378
4530
  if (content === void 0) continue;
4379
4531
  try {
@@ -4384,7 +4536,7 @@ async function rollbackUpgrade(root) {
4384
4536
  }
4385
4537
  }
4386
4538
  await removeDir(backupDir);
4387
- await removeFile(pointerFile);
4539
+ await removeFile2(pointerFile);
4388
4540
  return { status: "restored", restored, backup: name };
4389
4541
  }
4390
4542
  async function upgradeAdvisory(root) {
@@ -4417,7 +4569,7 @@ async function upgradeAdvisory(root) {
4417
4569
  }
4418
4570
 
4419
4571
  // src/sarif.ts
4420
- import path18 from "path";
4572
+ import path19 from "path";
4421
4573
  var SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
4422
4574
  var SARIF_VERSION = "2.1.0";
4423
4575
  var TOOL_NAME = "SpecAtlas";
@@ -4455,7 +4607,7 @@ function resultOf(diagnostic, root) {
4455
4607
  message: { text: diagnostic.message }
4456
4608
  };
4457
4609
  if (diagnostic.path) {
4458
- const rel = path18.relative(root, diagnostic.path);
4610
+ const rel = path19.relative(root, diagnostic.path);
4459
4611
  if (rel !== "") {
4460
4612
  const physicalLocation = {
4461
4613
  artifactLocation: { uri: toPosix(rel) },
@@ -4505,7 +4657,7 @@ function toSarifText(opts) {
4505
4657
  }
4506
4658
 
4507
4659
  // src/doctor.ts
4508
- import path19 from "path";
4660
+ import path20 from "path";
4509
4661
  async function runDoctor(root) {
4510
4662
  const findings = [];
4511
4663
  const { workspace, config } = await loadWorkspace(root);
@@ -4513,7 +4665,7 @@ async function runDoctor(root) {
4513
4665
  const approvals = await loadApprovals(workspace.sddDir);
4514
4666
  findings.push(...approvals.diagnostics);
4515
4667
  for (const change of workspace.changes) {
4516
- const deltaPath = path19.join(change.dir, "spec.md");
4668
+ const deltaPath = path20.join(change.dir, "spec.md");
4517
4669
  const deltaContent = await readTextIfExists(deltaPath);
4518
4670
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
4519
4671
  if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
@@ -4535,7 +4687,7 @@ async function runDoctor(root) {
4535
4687
  }
4536
4688
  for (const override of change.meta?.overrides ?? []) {
4537
4689
  if (!override.reason.trim() || !override.by.trim()) {
4538
- findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path19.join(change.dir, "meta.yaml") }));
4690
+ findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path20.join(change.dir, "meta.yaml") }));
4539
4691
  }
4540
4692
  }
4541
4693
  }
@@ -4558,7 +4710,7 @@ async function specHashOf(filePath) {
4558
4710
  }
4559
4711
 
4560
4712
  // src/gate.ts
4561
- import path20 from "path";
4713
+ import path21 from "path";
4562
4714
  var UI_DOMAINS2 = /* @__PURE__ */ new Set(["frontend", "mobile", "fullstack"]);
4563
4715
  function count(name, diagnostics) {
4564
4716
  return {
@@ -4575,7 +4727,7 @@ function livingRequirementsMap(specs) {
4575
4727
  return map;
4576
4728
  }
4577
4729
  async function runCiGate(opts) {
4578
- const root = path20.resolve(opts.root);
4730
+ const root = path21.resolve(opts.root);
4579
4731
  const { workspace, config } = await loadWorkspace(root);
4580
4732
  const diagnostics = [];
4581
4733
  const checks = [];
@@ -4586,7 +4738,7 @@ async function runCiGate(opts) {
4586
4738
  let changesErrors = 0;
4587
4739
  let changesWarnings = 0;
4588
4740
  for (const change of workspace.changes) {
4589
- const lintFindings = change.delta ? lintDelta(change.delta, living, path20.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
4741
+ const lintFindings = change.delta ? lintDelta(change.delta, living, path21.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
4590
4742
  const trace = checkTrace({
4591
4743
  specs: workspace.specs,
4592
4744
  change,
@@ -4636,6 +4788,7 @@ export {
4636
4788
  BUILTIN_PACKS,
4637
4789
  CONFIG_FILE,
4638
4790
  CORE_VERSION,
4791
+ LIVING_FIXES_DIR,
4639
4792
  REQ_HEAD_RE,
4640
4793
  REQ_ID_RE,
4641
4794
  RULE_ID_RE,
@@ -4735,6 +4888,7 @@ export {
4735
4888
  loadChange,
4736
4889
  loadConfig,
4737
4890
  loadDetectedBest,
4891
+ loadLivingFixes,
4738
4892
  loadProfileFile,
4739
4893
  loadProfilesFromDir,
4740
4894
  loadProjectPacks,
@@ -4755,10 +4909,12 @@ export {
4755
4909
  parseChangeMeta,
4756
4910
  parseConfig,
4757
4911
  parseDelta,
4912
+ parseFixCovers,
4758
4913
  parseFrontmatter,
4759
4914
  parseGitHubRemote,
4760
4915
  parseGlossary,
4761
4916
  parseIssueUrl,
4917
+ parseLivingFix,
4762
4918
  parseRequirementBlocks,
4763
4919
  parseSpecFile,
4764
4920
  parseTasksFile,
@@ -4810,6 +4966,7 @@ export {
4810
4966
  verifyApproval,
4811
4967
  walkFiles,
4812
4968
  writeConfig,
4969
+ writeLivingFix,
4813
4970
  writeMockupManifest,
4814
4971
  writeMockupPlan,
4815
4972
  writeText