specatlas 0.1.28 → 0.1.30

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
@@ -1586,7 +1586,7 @@ var require_core = __commonJS({
1586
1586
  });
1587
1587
 
1588
1588
  // src/cli.ts
1589
- import path46 from "path";
1589
+ import path48 from "path";
1590
1590
 
1591
1591
  // ../core/dist/index.js
1592
1592
  import path from "path";
@@ -9034,6 +9034,7 @@ import { parse as parseYaml8, stringify as stringifyYaml6 } from "yaml";
9034
9034
  import path19 from "path";
9035
9035
  import path20 from "path";
9036
9036
  import path21 from "path";
9037
+ import path22 from "path";
9037
9038
  var CORE_VERSION = "0.0.1";
9038
9039
  var REQ_ID_RE = /^REQ-[A-Z0-9]+(?:-[A-Z0-9]+)*-\d{3}$/;
9039
9040
  var SCENARIO_ID_RE = /^REQ-[A-Z0-9]+(?:-[A-Z0-9]+)*-\d{3}-S\d+$/;
@@ -9056,6 +9057,8 @@ var atlasConfigSchema = z.object({
9056
9057
  analyze: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("blocking"), min_severity: z.enum(["low", "medium", "high"]).default("medium") }).default({}),
9057
9058
  verify: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("blocking"), require_evidence: z.boolean().default(true) }).default({}),
9058
9059
  review: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("advisory") }).default({}),
9060
+ clarify: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("advisory") }).default({}),
9061
+ docs: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("blocking") }).default({}),
9059
9062
  mockup: z.object({ require_approval: z.boolean().default(false), compare_in_verify: z.boolean().default(false) }).default({})
9060
9063
  }).default({}),
9061
9064
  trace: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("blocking"), prefix: z.string().default("REQ") }).default({}),
@@ -9600,6 +9603,33 @@ function findScenarioHeading(lines, blockLineIndex) {
9600
9603
  }
9601
9604
  return void 0;
9602
9605
  }
9606
+ var OPEN_RE = /^\s*-\s*\[\s\]\s+(.+?)\s*$/;
9607
+ var DONE_RE = /^\s*-\s*\[[xX]\]\s+(.+?)\s*$/;
9608
+ var ANSWER_SEPARATOR = " \u2014 ";
9609
+ function splitAnswer(raw) {
9610
+ const index = raw.indexOf(ANSWER_SEPARATOR);
9611
+ if (index === -1) return { text: raw.trim() };
9612
+ return { text: raw.slice(0, index).trim(), answer: raw.slice(index + ANSWER_SEPARATOR.length).trim() };
9613
+ }
9614
+ function parseClarify(content, filePath) {
9615
+ const fm = parseFrontmatter(content, filePath);
9616
+ const lines = fm.body.replace(/\r\n?/g, "\n").split("\n");
9617
+ const open = [];
9618
+ const resolved = [];
9619
+ for (let i = 0; i < lines.length; i += 1) {
9620
+ const line = lines[i] ?? "";
9621
+ const lineNo = fm.bodyStartLine + i;
9622
+ const done = DONE_RE.exec(line);
9623
+ if (done) {
9624
+ const item = splitAnswer(done[1] ?? "");
9625
+ resolved.push({ text: item.text, line: lineNo, ...item.answer !== void 0 ? { answer: item.answer } : {} });
9626
+ continue;
9627
+ }
9628
+ const pending = OPEN_RE.exec(line);
9629
+ if (pending) open.push({ text: pending[1] ?? "", line: lineNo });
9630
+ }
9631
+ return { path: filePath, open, resolved, diagnostics: [] };
9632
+ }
9603
9633
  var HEADER_KEYS = /* @__PURE__ */ new Set(["t\xE9rmino", "termino", "term", "definici\xF3n", "definicion", "definition", "sin\xF3nimos", "sinonimos", "synonyms"]);
9604
9634
  var SEPARATOR_RE = /^:?-{2,}:?$/;
9605
9635
  function parseGlossary(md, filePath) {
@@ -9749,14 +9779,14 @@ function wordBoundary(text, term) {
9749
9779
  const re = new RegExp(`(?<![\\p{L}\\p{N}])${escaped}(?![\\p{L}\\p{N}])`, "iu");
9750
9780
  return re.test(text);
9751
9781
  }
9752
- function lintText(text, code, terms, label, path222, line) {
9782
+ function lintText(text, code, terms, label, path232, line) {
9753
9783
  const out = [];
9754
9784
  const lower = text.toLowerCase();
9755
9785
  for (const term of terms) {
9756
9786
  if (wordBoundary(lower, term)) {
9757
9787
  out.push(
9758
9788
  diag(code, "error", `${label}: "${term}"`, {
9759
- path: path222,
9789
+ path: path232,
9760
9790
  line,
9761
9791
  suggestion: "La especificaci\xF3n es funcional y de negocio: describe comportamiento, no tecnolog\xEDa ni adjetivos vagos"
9762
9792
  })
@@ -9765,7 +9795,7 @@ function lintText(text, code, terms, label, path222, line) {
9765
9795
  }
9766
9796
  return out;
9767
9797
  }
9768
- function lintRequirement(req, path222, opts = {}) {
9798
+ function lintRequirement(req, path232, opts = {}) {
9769
9799
  const out = [];
9770
9800
  const vague = opts.language === "en" ? VAGUE_EN : VAGUE_ES;
9771
9801
  const tech = opts.language === "en" ? TECH_EN : TECH_ES;
@@ -9776,32 +9806,32 @@ function lintRequirement(req, path222, opts = {}) {
9776
9806
  ...req.scenarios.flatMap((s) => [...s.when.map((w) => ({ text: w, line: s.line })), ...s.then.map((t) => ({ text: t, line: s.line }))])
9777
9807
  ];
9778
9808
  for (const part of parts) {
9779
- out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path222, part.line));
9809
+ out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path232, part.line));
9780
9810
  if (opts.businessOnly !== false) {
9781
- out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path222, part.line));
9811
+ out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path232, part.line));
9782
9812
  }
9783
9813
  }
9784
9814
  if (req.scenarios.length === 0) {
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" }));
9815
+ out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path232, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
9786
9816
  }
9787
9817
  return out;
9788
9818
  }
9789
- function lintDelta(delta, livingRequirements, path222, opts = {}) {
9819
+ function lintDelta(delta, livingRequirements, path232, opts = {}) {
9790
9820
  const out = [...delta.diagnostics];
9791
9821
  for (const req of [...delta.added, ...delta.modified]) {
9792
- out.push(...lintRequirement(req, path222, opts));
9822
+ out.push(...lintRequirement(req, path232, opts));
9793
9823
  }
9794
9824
  for (const req of delta.modified) {
9795
9825
  const living = livingRequirements.get(req.id);
9796
9826
  if (!living) {
9797
- out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path222, line: req.line }));
9827
+ out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path232, line: req.line }));
9798
9828
  continue;
9799
9829
  }
9800
9830
  for (const existing of living.scenarios) {
9801
9831
  if (!req.scenarios.some((s) => s.id === existing.id)) {
9802
9832
  out.push(
9803
9833
  diag("TRACE-007", "error", `MODIFIED ${req.id} pierde el escenario ${existing.id}: copia el bloque completo`, {
9804
- path: path222,
9834
+ path: path232,
9805
9835
  line: req.line,
9806
9836
  suggestion: "Copia el bloque completo de la spec viva y ed\xEDtalo; para quitarlo, decl\xE1ralo en REMOVED"
9807
9837
  })
@@ -9812,15 +9842,15 @@ function lintDelta(delta, livingRequirements, path222, opts = {}) {
9812
9842
  for (const req of delta.removed) {
9813
9843
  const living = livingRequirements.get(req.id);
9814
9844
  if (!living) {
9815
- out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path222, line: req.line }));
9845
+ out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path232, line: req.line }));
9816
9846
  }
9817
9847
  }
9818
9848
  for (const rename2 of delta.renamed) {
9819
9849
  if (rename2.from.id !== rename2.to.id) {
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 }));
9850
+ out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path232, line: rename2.line }));
9821
9851
  }
9822
9852
  if (!livingRequirements.has(rename2.from.id)) {
9823
- out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path222, line: rename2.line }));
9853
+ out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path232, line: rename2.line }));
9824
9854
  }
9825
9855
  }
9826
9856
  return out;
@@ -9844,7 +9874,7 @@ var MERMAID_KEYWORDS = [
9844
9874
  "architecture-beta"
9845
9875
  ];
9846
9876
  var MERMAID_BLOCKS = /^\s*(alt|loop|opt|par|rect|critical|break|subgraph)\b/;
9847
- function lintPlan(planText, path222) {
9877
+ function lintPlan(planText, path232) {
9848
9878
  const out = [];
9849
9879
  const blocks = [...planText.matchAll(/```mermaid\r?\n([\s\S]*?)```/g)];
9850
9880
  for (const [index, block] of blocks.entries()) {
@@ -9854,7 +9884,7 @@ function lintPlan(planText, path222) {
9854
9884
  if (!MERMAID_KEYWORDS.some((keyword) => first.startsWith(keyword))) {
9855
9885
  out.push(
9856
9886
  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)}"`, {
9857
- path: path222,
9887
+ path: path232,
9858
9888
  suggestion: "Corrige el tipo del diagrama o elimina el bloque"
9859
9889
  })
9860
9890
  );
@@ -9868,7 +9898,7 @@ function lintPlan(planText, path222) {
9868
9898
  if (open !== 0) {
9869
9899
  out.push(
9870
9900
  diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: faltan ${Math.abs(open)} \`end\` (bloques alt/loop/subgraph sin cerrar)`, {
9871
- path: path222,
9901
+ path: path232,
9872
9902
  suggestion: "Cierra cada bloque alt/loop/opt/par/rect/subgraph con `end`"
9873
9903
  })
9874
9904
  );
@@ -9880,7 +9910,7 @@ function lintPlan(planText, path222) {
9880
9910
  if (message.includes(";")) {
9881
9911
  out.push(
9882
9912
  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`, {
9883
- path: path222,
9913
+ path: path232,
9884
9914
  suggestion: "Sustituye `;` por `\xB7` o `,` en los mensajes de sequenceDiagram"
9885
9915
  })
9886
9916
  );
@@ -10182,6 +10212,29 @@ function requiresMockups(meta, cfg) {
10182
10212
  function mockupOverride(change) {
10183
10213
  return (change.meta?.overrides ?? []).some((override) => override.gate === "mockup");
10184
10214
  }
10215
+ function docsReady(change) {
10216
+ const paths = change.docsPaths ?? [];
10217
+ return paths.some((p) => p.endsWith("tecnica.md")) && paths.some((p) => p.endsWith("manual.md"));
10218
+ }
10219
+ function clarifyAdvisory(change, cfg) {
10220
+ const open = change.clarify?.open.length ?? 0;
10221
+ if (cfg.gates.clarify.mode !== "advisory" || open === 0) return [];
10222
+ return [
10223
+ diag("ATLAS-CLARIFY-001", "warning", `El cambio "${change.slug}" tiene ${open} pregunta(s) sin aclarar`, {
10224
+ ...change.clarifyPath !== void 0 ? { path: change.clarifyPath } : {},
10225
+ suggestion: `Aclara antes de planificar: /satlas.clarify ${change.slug} (o satlas clarify ${change.slug})`
10226
+ })
10227
+ ];
10228
+ }
10229
+ function docsAdvisory(change, cfg) {
10230
+ const lane = change.meta?.lane ?? cfg.lanes.default;
10231
+ if (lane !== "full" || cfg.gates.docs.mode !== "advisory" || docsReady(change)) return [];
10232
+ return [
10233
+ diag("ATLAS-DOCS-001", "warning", `El cambio "${change.slug}" (carril completo) no tiene su documentaci\xF3n t\xE9cnica y manual`, {
10234
+ suggestion: `Genera la documentaci\xF3n: satlas docs ${change.slug} (o /satlas.docs ${change.slug})`
10235
+ })
10236
+ ];
10237
+ }
10185
10238
  function deriveState(input) {
10186
10239
  const { change, cfg, approval, blockingFindings } = input;
10187
10240
  const lane = change.meta?.lane ?? cfg.lanes.default;
@@ -10244,6 +10297,16 @@ function deriveState(input) {
10244
10297
  };
10245
10298
  }
10246
10299
  if (!change.planPath && !change.tasks) {
10300
+ const openQuestions = change.clarify?.open.length ?? 0;
10301
+ if (openQuestions > 0 && cfg.gates.clarify.mode === "blocking") {
10302
+ blockedBy.push(`aclaraci\xF3n pendiente (${openQuestions})`);
10303
+ return {
10304
+ state: "approved",
10305
+ blockedBy,
10306
+ nextAction: next(`/satlas.clarify ${change.slug}`, `Aclarar ${openQuestions} pregunta(s) antes de planificar`, true),
10307
+ progress
10308
+ };
10309
+ }
10247
10310
  return { state: "approved", blockedBy, nextAction: next(`/satlas.plan ${change.slug}`, "Crear el plan t\xE9cnico y las tareas", true), progress };
10248
10311
  }
10249
10312
  if (tasksTotal > 0 && tasksDone < tasksTotal) {
@@ -10257,6 +10320,10 @@ function deriveState(input) {
10257
10320
  blockedBy.push("review pendiente");
10258
10321
  return { state: "verified", blockedBy, nextAction: next(`/satlas.review ${change.slug}`, "Revisi\xF3n de c\xF3digo", true), progress };
10259
10322
  }
10323
+ if (lane === "full" && cfg.gates.docs.mode === "blocking" && !docsReady(change)) {
10324
+ blockedBy.push("documentaci\xF3n pendiente");
10325
+ return { state: "reviewed", blockedBy, nextAction: next(`/satlas.docs ${change.slug}`, "Generar la documentaci\xF3n t\xE9cnica y manual del cambio", true), progress };
10326
+ }
10260
10327
  return { state: "ready", blockedBy, nextAction: next(`satlas archive ${change.slug}`, "Archivar el cambio y plegar los deltas"), progress };
10261
10328
  }
10262
10329
  function stateLabel(state) {
@@ -10270,6 +10337,7 @@ function stateLabel(state) {
10270
10337
  building: "construyendo",
10271
10338
  built: "construido",
10272
10339
  verified: "verificado",
10340
+ reviewed: "revisado",
10273
10341
  ready: "listo para archivar",
10274
10342
  archived: "archivado"
10275
10343
  };
@@ -10443,12 +10511,43 @@ function parseLivingFix(content, file) {
10443
10511
  date: typeof data["date"] === "string" ? data["date"] : "",
10444
10512
  result: typeof data["result"] === "string" ? data["result"] : "pass",
10445
10513
  covers: coversOf(data["covers"]),
10446
- content: fm.body.trimStart()
10514
+ content: fm.body.trimStart(),
10515
+ source: "living"
10447
10516
  };
10448
10517
  if (typeof data["domain"] === "string") fix.domain = data["domain"];
10449
10518
  if (typeof data["title"] === "string") fix.title = data["title"];
10450
10519
  return fix;
10451
10520
  }
10521
+ async function archivedFixes(root, known) {
10522
+ const archiveDir = path4.join(root, ".sdd", "changes", "archive");
10523
+ const fixes = [];
10524
+ for (const entry of await listDirs(archiveDir)) {
10525
+ const dir = path4.join(archiveDir, entry);
10526
+ const fixFile = path4.join(dir, "fix.md");
10527
+ const fixRaw = await readTextIfExists(fixFile);
10528
+ if (fixRaw === void 0) continue;
10529
+ const metaFile = path4.join(dir, "meta.yaml");
10530
+ const metaRaw = await readTextIfExists(metaFile);
10531
+ const meta = metaRaw !== void 0 ? parseChangeMeta(metaRaw, metaFile).meta : void 0;
10532
+ if (meta?.lane !== "fix") continue;
10533
+ const slug = entry.replace(/^\d{4}-\d{2}-/, "");
10534
+ if (known.has(slug)) continue;
10535
+ const evidence = parseVerifyFile(fixRaw, fixFile).evidence;
10536
+ const fix = {
10537
+ slug,
10538
+ file: fixFile,
10539
+ date: /^(\d{4}-\d{2})/.exec(entry)?.[1] ?? "",
10540
+ result: evidence.some((e) => e.result === "pass") ? "pass" : evidence.length > 0 ? "fail" : "pending",
10541
+ covers: parseFixCovers(fixRaw),
10542
+ content: parseFrontmatter(fixRaw, fixFile).body.trimStart(),
10543
+ source: "archive"
10544
+ };
10545
+ if (meta.domain !== void 0) fix.domain = meta.domain;
10546
+ if (meta.title !== void 0) fix.title = meta.title;
10547
+ fixes.push(fix);
10548
+ }
10549
+ return fixes;
10550
+ }
10452
10551
  async function loadLivingFixes(root) {
10453
10552
  const dir = path4.join(root, LIVING_FIXES_DIR);
10454
10553
  const entries = (await listDir(dir)).filter((entry) => entry.toLowerCase().endsWith(".md")).sort();
@@ -10459,6 +10558,7 @@ async function loadLivingFixes(root) {
10459
10558
  if (content === void 0) continue;
10460
10559
  fixes.push(parseLivingFix(content, file));
10461
10560
  }
10561
+ fixes.push(...await archivedFixes(root, new Set(fixes.map((fix) => fix.slug))));
10462
10562
  return fixes.sort((a, b) => b.date.localeCompare(a.date) || b.slug.localeCompare(a.slug));
10463
10563
  }
10464
10564
  async function writeLivingFix(root, input) {
@@ -10564,6 +10664,19 @@ async function loadChange(root, slug, relDir) {
10564
10664
  diagnostics.push(...change.fix.diagnostics);
10565
10665
  change.fixCovers = parseFixCovers(fixRaw);
10566
10666
  }
10667
+ const clarifyFile = path5.join(dir, "clarify.md");
10668
+ const clarifyRaw = await readTextIfExists(clarifyFile);
10669
+ if (clarifyRaw !== void 0) {
10670
+ change.clarify = parseClarify(clarifyRaw, clarifyFile);
10671
+ change.clarifyPath = clarifyFile;
10672
+ diagnostics.push(...change.clarify.diagnostics);
10673
+ }
10674
+ const docsPaths = [];
10675
+ for (const name of ["tecnica.md", "manual.md"]) {
10676
+ const docFile = path5.join(dir, "docs", name);
10677
+ if (await exists(docFile)) docsPaths.push(docFile);
10678
+ }
10679
+ if (docsPaths.length > 0) change.docsPaths = docsPaths;
10567
10680
  const mockupManifest = path5.join(dir, "mockups", "manifest.yaml");
10568
10681
  if (await exists(mockupManifest)) change.mockupManifestPath = mockupManifest;
10569
10682
  return change;
@@ -10777,6 +10890,44 @@ satlas verify <slug> --file fix --scenario REQ-DOMINIO-001-S1 --command "<comand
10777
10890
  o, si es manual:
10778
10891
  satlas verify <slug> --file fix --scenario REQ-DOMINIO-001-S1 --method manual --result pass --by "<nombre>" --notes "<c\xF3mo se comprob\xF3>"
10779
10892
  -->
10893
+ `,
10894
+ docTecnica: `# Documentaci\xF3n t\xE9cnica \u2014 {{TITLE}}
10895
+
10896
+ ## Resumen del cambio
10897
+
10898
+ - **Cambio**: \`{{SLUG}}\` \xB7 dominio \`{{DOMAIN}}\` \xB7 carril \`{{LANE}}\`
10899
+ - **Actualizado**: {{DATE}}
10900
+ - **Tareas**: {{TASKS}}
10901
+
10902
+ ## Requisitos y escenarios
10903
+
10904
+ {{REQUIREMENTS}}
10905
+
10906
+ ## Evidencia registrada
10907
+
10908
+ {{EVIDENCE}}
10909
+
10910
+ ## Pendiente de evidencia
10911
+
10912
+ {{PENDING}}
10913
+ `,
10914
+ docManual: `# Manual \u2014 {{TITLE}}
10915
+
10916
+ ## Qu\xE9 hace este cambio
10917
+
10918
+ {{TITLE}} \u2014 dominio \`{{DOMAIN}}\` (cambio \`{{SLUG}}\`, carril \`{{LANE}}\`).
10919
+
10920
+ ## C\xF3mo se usa
10921
+
10922
+ {{SCENARIOS}}
10923
+
10924
+ ## C\xF3mo se comprob\xF3
10925
+
10926
+ {{EVIDENCE}}
10927
+
10928
+ ## Pendiente de comprobar
10929
+
10930
+ {{PENDING}}
10780
10931
  `
10781
10932
  };
10782
10933
  var EN = {
@@ -10902,6 +11053,44 @@ Cubre: REQ-DOMAIN-001
10902
11053
  <!-- Register real evidence with:
10903
11054
  satlas verify <slug> --file fix --scenario REQ-DOMAIN-001-S1 --command "<command>" --by "<name>"
10904
11055
  -->
11056
+ `,
11057
+ docTecnica: `# Technical documentation \u2014 {{TITLE}}
11058
+
11059
+ ## Change summary
11060
+
11061
+ - **Change**: \`{{SLUG}}\` \xB7 domain \`{{DOMAIN}}\` \xB7 lane \`{{LANE}}\`
11062
+ - **Updated**: {{DATE}}
11063
+ - **Tasks**: {{TASKS}}
11064
+
11065
+ ## Requirements and scenarios
11066
+
11067
+ {{REQUIREMENTS}}
11068
+
11069
+ ## Recorded evidence
11070
+
11071
+ {{EVIDENCE}}
11072
+
11073
+ ## Pending evidence
11074
+
11075
+ {{PENDING}}
11076
+ `,
11077
+ docManual: `# Manual \u2014 {{TITLE}}
11078
+
11079
+ ## What this change does
11080
+
11081
+ {{TITLE}} \u2014 domain \`{{DOMAIN}}\` (change \`{{SLUG}}\`, lane \`{{LANE}}\`).
11082
+
11083
+ ## How to use it
11084
+
11085
+ {{SCENARIOS}}
11086
+
11087
+ ## How it was verified
11088
+
11089
+ {{EVIDENCE}}
11090
+
11091
+ ## Pending verification
11092
+
11093
+ {{PENDING}}
10905
11094
  `
10906
11095
  };
10907
11096
  function templatesFor(language) {
@@ -13401,6 +13590,84 @@ async function upgradeAdvisory(root) {
13401
13590
  }
13402
13591
  return { plan, diagnostics };
13403
13592
  }
13593
+ var DOCS_MARKER_START = "<!-- specatlas:generado:inicio -->";
13594
+ var DOCS_MARKER_END = "<!-- specatlas:generado:fin -->";
13595
+ function docData(change, language, now) {
13596
+ const es = language !== "en";
13597
+ const requirements = [...change.delta?.added ?? [], ...change.delta?.modified ?? []];
13598
+ const evidence = change.verify?.evidence ?? [];
13599
+ const passed = new Set(evidence.filter((entry) => entry.result === "pass").map((entry) => entry.scenario));
13600
+ const requirementsText = requirements.length === 0 ? es ? "_Sin requisitos en el delta._" : "_No requirements in the delta._" : requirements.map((requirement) => {
13601
+ const lines = [`### ${requirement.id} \u2014 ${requirement.title}`, ""];
13602
+ for (const scenario of requirement.scenarios) lines.push(`- \`${scenario.id}\` \u2014 ${scenario.title}`);
13603
+ return lines.join("\n");
13604
+ }).join("\n\n");
13605
+ const scenarioList = requirements.flatMap((requirement) => requirement.scenarios);
13606
+ const scenariosText = scenarioList.length === 0 ? es ? "_Sin escenarios._" : "_No scenarios._" : scenarioList.map((scenario) => `- \`${scenario.id}\` \u2014 ${scenario.title}`).join("\n");
13607
+ const evidenceText = evidence.length === 0 ? es ? "_Sin evidencia registrada._" : "_No evidence recorded._" : evidence.map((entry) => `- \`${entry.scenario}\` \u2014 ${entry.method} \xB7 ${entry.result}${entry.date ? ` \xB7 ${entry.date}` : ""}`).join("\n");
13608
+ const pending = scenarioList.filter((scenario) => !passed.has(scenario.id));
13609
+ const pendingText = pending.length === 0 ? es ? "_Nada pendiente: todos los escenarios tienen evidencia en pass._" : "_Nothing pending: every scenario has passing evidence._" : pending.map((scenario) => `- \`${scenario.id}\` \u2014 ${scenario.title}`).join("\n");
13610
+ return {
13611
+ TITLE: change.meta?.title ?? change.slug,
13612
+ SLUG: change.slug,
13613
+ DOMAIN: change.meta?.domain ?? "\u2014",
13614
+ LANE: change.meta?.lane ?? "standard",
13615
+ DATE: localDate(now),
13616
+ TASKS: change.tasks ? `${change.tasks.counts.done}/${change.tasks.counts.total}` : es ? "sin tareas" : "no tasks",
13617
+ REQUIREMENTS: requirementsText,
13618
+ SCENARIOS: scenariosText,
13619
+ EVIDENCE: evidenceText,
13620
+ PENDING: pendingText
13621
+ };
13622
+ }
13623
+ function mergeManaged(existing, block, language) {
13624
+ const managed = `${DOCS_MARKER_START}
13625
+ ${block.trimEnd()}
13626
+ ${DOCS_MARKER_END}`;
13627
+ if (existing === void 0) {
13628
+ const notes = language === "en" ? "## Notes\n\n(Write here whatever you want to keep across regenerations.)" : "## Notas\n\n(Escribe aqu\xED lo que quieras conservar entre regeneraciones.)";
13629
+ return `${managed}
13630
+
13631
+ ${notes}
13632
+ `;
13633
+ }
13634
+ const start = existing.indexOf(DOCS_MARKER_START);
13635
+ const end = existing.indexOf(DOCS_MARKER_END);
13636
+ if (start >= 0 && end > start) {
13637
+ const before = existing.slice(0, start);
13638
+ const after = existing.slice(end + DOCS_MARKER_END.length);
13639
+ return `${before}${managed}${after}`;
13640
+ }
13641
+ return `${managed}
13642
+
13643
+ ${existing}`;
13644
+ }
13645
+ async function generateDocs(opts) {
13646
+ const root = path19.resolve(opts.root);
13647
+ const { config } = await loadWorkspace(root);
13648
+ const change = await loadChange(root, opts.slug);
13649
+ if (!change.meta) {
13650
+ return {
13651
+ slug: opts.slug,
13652
+ files: [],
13653
+ diagnostics: [diag("ATLAS-DOCS-002", "error", `No existe el cambio "${opts.slug}"`, { suggestion: "Comprueba el nombre del cambio" })]
13654
+ };
13655
+ }
13656
+ const language = config.project.language;
13657
+ const tipo = opts.tipo ?? "all";
13658
+ const tipos = tipo === "all" ? ["tecnica", "manual"] : [tipo];
13659
+ const templates = templatesFor(language);
13660
+ const data = docData(change, language, opts.now ?? /* @__PURE__ */ new Date());
13661
+ const files = [];
13662
+ for (const current of tipos) {
13663
+ const file = path19.join(change.dir, "docs", `${current}.md`);
13664
+ const block = renderTemplate(current === "tecnica" ? templates.docTecnica : templates.docManual, data);
13665
+ const existing = await readTextIfExists(file);
13666
+ await writeText(file, mergeManaged(existing, block, language));
13667
+ files.push({ tipo: current, path: file, created: existing === void 0 });
13668
+ }
13669
+ return { slug: opts.slug, files, diagnostics: [] };
13670
+ }
13404
13671
  var SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
13405
13672
  var SARIF_VERSION = "2.1.0";
13406
13673
  var TOOL_NAME = "SpecAtlas";
@@ -13438,7 +13705,7 @@ function resultOf(diagnostic, root) {
13438
13705
  message: { text: diagnostic.message }
13439
13706
  };
13440
13707
  if (diagnostic.path) {
13441
- const rel = path19.relative(root, diagnostic.path);
13708
+ const rel = path20.relative(root, diagnostic.path);
13442
13709
  if (rel !== "") {
13443
13710
  const physicalLocation = {
13444
13711
  artifactLocation: { uri: toPosix(rel) },
@@ -13493,7 +13760,7 @@ async function runDoctor(root) {
13493
13760
  const approvals = await loadApprovals(workspace.sddDir);
13494
13761
  findings.push(...approvals.diagnostics);
13495
13762
  for (const change of workspace.changes) {
13496
- const deltaPath = path20.join(change.dir, "spec.md");
13763
+ const deltaPath = path21.join(change.dir, "spec.md");
13497
13764
  const deltaContent = await readTextIfExists(deltaPath);
13498
13765
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
13499
13766
  if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
@@ -13515,7 +13782,7 @@ async function runDoctor(root) {
13515
13782
  }
13516
13783
  for (const override of change.meta?.overrides ?? []) {
13517
13784
  if (!override.reason.trim() || !override.by.trim()) {
13518
- findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path20.join(change.dir, "meta.yaml") }));
13785
+ findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path21.join(change.dir, "meta.yaml") }));
13519
13786
  }
13520
13787
  }
13521
13788
  }
@@ -13547,7 +13814,7 @@ function livingRequirementsMap(specs) {
13547
13814
  return map;
13548
13815
  }
13549
13816
  async function runCiGate(opts) {
13550
- const root = path21.resolve(opts.root);
13817
+ const root = path22.resolve(opts.root);
13551
13818
  const { workspace, config } = await loadWorkspace(root);
13552
13819
  const diagnostics = [];
13553
13820
  const checks = [];
@@ -13558,7 +13825,7 @@ async function runCiGate(opts) {
13558
13825
  let changesErrors = 0;
13559
13826
  let changesWarnings = 0;
13560
13827
  for (const change of workspace.changes) {
13561
- const lintFindings = change.delta ? lintDelta(change.delta, living, path21.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
13828
+ const lintFindings = change.delta ? lintDelta(change.delta, living, path22.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
13562
13829
  const trace = checkTrace({
13563
13830
  specs: workspace.specs,
13564
13831
  change,
@@ -13667,6 +13934,8 @@ var CATALOG = [
13667
13934
  { 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]" },
13668
13935
  { name: "waves", description: "Calcula las olas paralelas de construcci\xF3n", flags: ["change", "max-parallel", "json"], usage: "satlas waves [--change <slug>] [--max-parallel N] [--json]" },
13669
13936
  { name: "doctor", description: "Diagn\xF3stico de salud del workspace", flags: ["json"], usage: "satlas doctor [--json]" },
13937
+ { name: "clarify", description: "Preguntas abiertas y aclaraciones del cambio (informe; aclarar es fase del agente)", flags: ["json"], usage: "satlas clarify <slug>" },
13938
+ { name: "docs", description: "Genera la documentaci\xF3n t\xE9cnica y manual del cambio (carril completo) desde la evidencia", flags: ["tipo", "json"], usage: "satlas docs <slug> [--tipo tecnica|manual|all]" },
13670
13939
  { 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]" },
13671
13940
  { 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' },
13672
13941
  { 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>" },
@@ -13718,6 +13987,13 @@ var ES2 = {
13718
13987
  "new.done": "Cambio creado. Siguiente: escribe la spec funcional.",
13719
13988
  "upgrade.title": "Actualizar el estado del proyecto",
13720
13989
  "upgrade.preview": "Vista previa",
13990
+ "clarify.title": "Aclaraciones del cambio",
13991
+ "clarify.mode": "modo",
13992
+ "clarify.open": "preguntas abiertas",
13993
+ "clarify.resolved": "aclaradas",
13994
+ "docs.title": "Documentaci\xF3n del cambio",
13995
+ "docs.created": "creado",
13996
+ "docs.updated": "actualizado",
13721
13997
  "summary.findings": "hallazgos",
13722
13998
  "label.errors": "errores",
13723
13999
  "label.warnings": "avisos",
@@ -13753,6 +14029,13 @@ var EN2 = {
13753
14029
  "new.done": "Change created. Next: write the business spec.",
13754
14030
  "upgrade.title": "Upgrade project state",
13755
14031
  "upgrade.preview": "Preview",
14032
+ "clarify.title": "Change clarifications",
14033
+ "clarify.mode": "mode",
14034
+ "clarify.open": "open questions",
14035
+ "clarify.resolved": "clarified",
14036
+ "docs.title": "Change documentation",
14037
+ "docs.created": "created",
14038
+ "docs.updated": "updated",
13756
14039
  "summary.findings": "findings",
13757
14040
  "label.errors": "errors",
13758
14041
  "label.warnings": "warnings",
@@ -13780,26 +14063,26 @@ function cliVersion() {
13780
14063
  }
13781
14064
 
13782
14065
  // src/commands/adapters.ts
13783
- import path25 from "path";
14066
+ import path26 from "path";
13784
14067
 
13785
14068
  // ../adapters/dist/index.js
13786
- import path22 from "path";
13787
- import { stringify as stringifyYaml7 } from "yaml";
13788
14069
  import path23 from "path";
14070
+ import { stringify as stringifyYaml7 } from "yaml";
14071
+ import path24 from "path";
13789
14072
  async function loadWorkflow(workflowDir) {
13790
- const phasesDir = path22.join(workflowDir, "phases");
13791
- const snippetsDir = path22.join(workflowDir, "snippets");
14073
+ const phasesDir = path23.join(workflowDir, "phases");
14074
+ const snippetsDir = path23.join(workflowDir, "snippets");
13792
14075
  const phases = [];
13793
14076
  const snippets = /* @__PURE__ */ new Map();
13794
14077
  const hashParts = [];
13795
14078
  for (const entry of (await listDir(phasesDir)).sort()) {
13796
14079
  if (!entry.endsWith(".md")) continue;
13797
- const filePath = path22.join(phasesDir, entry);
14080
+ const filePath = path23.join(phasesDir, entry);
13798
14081
  const raw = await readText(filePath);
13799
14082
  hashParts.push(raw);
13800
14083
  const fm = parseFrontmatter(raw, filePath);
13801
14084
  const data = fm.data;
13802
- const id = typeof data["id"] === "string" ? data["id"] : path22.basename(entry, ".md");
14085
+ const id = typeof data["id"] === "string" ? data["id"] : path23.basename(entry, ".md");
13803
14086
  phases.push({
13804
14087
  id,
13805
14088
  title: typeof data["title"] === "string" ? data["title"] : id,
@@ -13813,9 +14096,9 @@ async function loadWorkflow(workflowDir) {
13813
14096
  }
13814
14097
  for (const entry of (await listDir(snippetsDir)).sort()) {
13815
14098
  if (!entry.endsWith(".md")) continue;
13816
- const raw = await readTextIfExists(path22.join(snippetsDir, entry)) ?? "";
14099
+ const raw = await readTextIfExists(path23.join(snippetsDir, entry)) ?? "";
13817
14100
  hashParts.push(raw);
13818
- snippets.set(path22.basename(entry, ".md"), raw.trim());
14101
+ snippets.set(path23.basename(entry, ".md"), raw.trim());
13819
14102
  }
13820
14103
  return { phases: phases.sort((a, b) => a.id.localeCompare(b.id)), snippets, sourceHash: sha256(hashParts.join("\n---\n")) };
13821
14104
  }
@@ -13997,11 +14280,11 @@ Reglas: la spec es funcional y de negocio (sin tecnolog\xEDa); la trazabilidad e
13997
14280
  function tomlString(value) {
13998
14281
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
13999
14282
  }
14000
- var MANIFEST_REL = path23.join(".sdd", ".generated", "manifest.json");
14283
+ var MANIFEST_REL = path24.join(".sdd", ".generated", "manifest.json");
14001
14284
  var BEGIN = "<!-- BEGIN specatlas -->";
14002
14285
  var END = "<!-- END specatlas -->";
14003
14286
  async function compileTargets(opts) {
14004
- const root = path23.resolve(opts.root);
14287
+ const root = path24.resolve(opts.root);
14005
14288
  const language = opts.language ?? "es";
14006
14289
  const diagnostics = [];
14007
14290
  const sources = await loadWorkflow(opts.workflowDir);
@@ -14009,7 +14292,7 @@ async function compileTargets(opts) {
14009
14292
  diagnostics.push({
14010
14293
  code: "ATLAS-ADAPTERS-002",
14011
14294
  severity: "error",
14012
- message: `No hay fases en ${path23.join(opts.workflowDir, "phases")}`,
14295
+ message: `No hay fases en ${path24.join(opts.workflowDir, "phases")}`,
14013
14296
  suggestion: "Revisa la carpeta workflow/phases del proyecto"
14014
14297
  });
14015
14298
  }
@@ -14021,7 +14304,7 @@ async function compileTargets(opts) {
14021
14304
  }
14022
14305
  }
14023
14306
  const compiled = [...deduped.values()];
14024
- const manifestPath = path23.join(root, MANIFEST_REL);
14307
+ const manifestPath = path24.join(root, MANIFEST_REL);
14025
14308
  const previous = await readManifest(manifestPath);
14026
14309
  const previousHashes = /* @__PURE__ */ new Map();
14027
14310
  for (const files2 of Object.values(previous?.targets ?? {})) {
@@ -14032,7 +14315,7 @@ async function compileTargets(opts) {
14032
14315
  const stale = [];
14033
14316
  const missing = [];
14034
14317
  for (const file of compiled) {
14035
- const abs = path23.join(root, file.path);
14318
+ const abs = path24.join(root, file.path);
14036
14319
  const finalContent = file.path === "AGENTS.md" ? mergeAgentsBlock(await readTextIfExists(abs) ?? "", file.content) : file.content;
14037
14320
  const hash = artifactHash(finalContent);
14038
14321
  const existing = await readTextIfExists(abs);
@@ -14048,7 +14331,7 @@ async function compileTargets(opts) {
14048
14331
  stale.push(file.path);
14049
14332
  }
14050
14333
  if (!opts.check && status !== "unchanged") {
14051
- await ensureDir(path23.dirname(abs));
14334
+ await ensureDir(path24.dirname(abs));
14052
14335
  await writeText(abs, finalContent);
14053
14336
  written.push(file.path);
14054
14337
  }
@@ -14102,7 +14385,7 @@ async function readManifest(manifestPath) {
14102
14385
  }
14103
14386
  }
14104
14387
  async function checkAdapters(opts) {
14105
- const manifestPath = path23.join(path23.resolve(opts.root), MANIFEST_REL);
14388
+ const manifestPath = path24.join(path24.resolve(opts.root), MANIFEST_REL);
14106
14389
  const manifestRaw = await readTextIfExists(manifestPath);
14107
14390
  if (manifestRaw === void 0) return { ok: false, manifest: false, stale: [], missing: [], orphaned: [] };
14108
14391
  const manifest = await readManifest(manifestPath);
@@ -14119,7 +14402,7 @@ async function checkAdapters(opts) {
14119
14402
  }
14120
14403
 
14121
14404
  // src/paths.ts
14122
- import path24 from "path";
14405
+ import path25 from "path";
14123
14406
  import { fileURLToPath as fileURLToPath2 } from "url";
14124
14407
  async function resolveProfilesDir() {
14125
14408
  const env = process.env["SPECATLAS_PROFILES_DIR"];
@@ -14129,16 +14412,16 @@ async function resolveProfilesDir() {
14129
14412
  async function resolveWorkflowDir() {
14130
14413
  const env = process.env["SPECATLAS_WORKFLOW_DIR"];
14131
14414
  if (env && await exists(env)) return env;
14132
- return walkUpFor(path24.join("workflow", "phases"));
14415
+ return walkUpFor(path25.join("workflow", "phases"));
14133
14416
  }
14134
14417
  async function walkUpFor(relative) {
14135
- let dir = path24.dirname(fileURLToPath2(import.meta.url));
14418
+ let dir = path25.dirname(fileURLToPath2(import.meta.url));
14136
14419
  for (let i = 0; i < 8; i += 1) {
14137
- const candidate = path24.join(dir, relative);
14420
+ const candidate = path25.join(dir, relative);
14138
14421
  if (await exists(candidate)) {
14139
- return relative.includes(path24.sep) ? path24.dirname(candidate) : candidate;
14422
+ return relative.includes(path25.sep) ? path25.dirname(candidate) : candidate;
14140
14423
  }
14141
- const parent = path24.dirname(dir);
14424
+ const parent = path25.dirname(dir);
14142
14425
  if (parent === dir) break;
14143
14426
  dir = parent;
14144
14427
  }
@@ -14216,7 +14499,7 @@ async function runAdapters(ctx) {
14216
14499
  for (const [target, count2] of byTarget) lines.push(` ${target}: ${count2} archivo(s)`);
14217
14500
  if (report.written.length > 0) {
14218
14501
  lines.push("");
14219
- for (const p of report.written) lines.push(` + ${path25.relative(ctx.cwd, path25.join(root, p))}`);
14502
+ for (const p of report.written) lines.push(` + ${path26.relative(ctx.cwd, path26.join(root, p))}`);
14220
14503
  } else {
14221
14504
  lines.push("");
14222
14505
  lines.push("Sin cambios: los adaptadores ya estaban al d\xEDa.");
@@ -14227,7 +14510,7 @@ async function runAdapters(ctx) {
14227
14510
  }
14228
14511
 
14229
14512
  // src/commands/adopt.ts
14230
- import path26 from "path";
14513
+ import path27 from "path";
14231
14514
  async function runAdopt(ctx) {
14232
14515
  const { root } = await requireWorkspace(ctx);
14233
14516
  const domainsFlag = flagString(ctx.flags, "domains") ?? flagString(ctx.flags, "domain");
@@ -14247,11 +14530,11 @@ async function runAdopt(ctx) {
14247
14530
  }
14248
14531
  if (result.createdSpecs.length > 0) {
14249
14532
  lines.push("");
14250
- for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${path26.relative(ctx.cwd, spec)}`);
14533
+ for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${path27.relative(ctx.cwd, spec)}`);
14251
14534
  }
14252
14535
  if (result.reportPath && !result.dryRun) {
14253
14536
  lines.push("");
14254
- lines.push(` Informe: ${path26.relative(ctx.cwd, result.reportPath)}`);
14537
+ lines.push(` Informe: ${path27.relative(ctx.cwd, result.reportPath)}`);
14255
14538
  }
14256
14539
  lines.push("");
14257
14540
  lines.push("Siguiente: fase /satlas-adopt por dominio (requisitos AS-IS) y luego:");
@@ -14264,8 +14547,8 @@ async function runAdopt(ctx) {
14264
14547
  diagnostics: result.diagnostics,
14265
14548
  data: {
14266
14549
  domains: result.domains.map((d) => ({ name: d.name, files: d.files, existingSpec: d.existingSpec })),
14267
- createdSpecs: result.createdSpecs.map((s) => path26.relative(ctx.cwd, s)),
14268
- reportPath: result.reportPath ? path26.relative(ctx.cwd, result.reportPath) : void 0,
14550
+ createdSpecs: result.createdSpecs.map((s) => path27.relative(ctx.cwd, s)),
14551
+ reportPath: result.reportPath ? path27.relative(ctx.cwd, result.reportPath) : void 0,
14269
14552
  stack: result.stack,
14270
14553
  dryRun: result.dryRun
14271
14554
  },
@@ -14274,7 +14557,7 @@ async function runAdopt(ctx) {
14274
14557
  }
14275
14558
 
14276
14559
  // src/commands/analyze.ts
14277
- import path27 from "path";
14560
+ import path28 from "path";
14278
14561
  async function runAnalyzeCommand(ctx) {
14279
14562
  const { root } = await requireWorkspace(ctx);
14280
14563
  const slug = ctx.positionals[0];
@@ -14285,23 +14568,23 @@ async function runAnalyzeCommand(ctx) {
14285
14568
  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`];
14286
14569
  if (result.waves) lines.push(` olas: ${result.waves.blocks} bloque(s), ${result.waves.waves} ola(s), ${result.waves.tasks} tarea(s)`);
14287
14570
  if (result.mockups) lines.push(` mockups: ${result.mockups.screens} pantalla(s)${result.mockups.stale ? " (desactualizados)" : ""}`);
14288
- if (result.path) lines.push("", ` informe: ${path27.relative(ctx.cwd, result.path)}`);
14571
+ if (result.path) lines.push("", ` informe: ${path28.relative(ctx.cwd, result.path)}`);
14289
14572
  for (const finding of result.findings) {
14290
14573
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}`);
14291
14574
  }
14292
14575
  return {
14293
14576
  exitCode: result.status === "blocked" ? 1 : 0,
14294
14577
  diagnostics: result.findings,
14295
- data: result.path ? { ...result, path: path27.relative(ctx.cwd, result.path) } : result,
14578
+ data: result.path ? { ...result, path: path28.relative(ctx.cwd, result.path) } : result,
14296
14579
  text: lines
14297
14580
  };
14298
14581
  }
14299
14582
 
14300
14583
  // src/commands/approve.ts
14301
- import path29 from "path";
14584
+ import path30 from "path";
14302
14585
 
14303
14586
  // src/commands/issue.ts
14304
- import path28 from "path";
14587
+ import path29 from "path";
14305
14588
  async function runIssue(ctx) {
14306
14589
  const action = ctx.positionals[0] ?? "status";
14307
14590
  const slug = ctx.positionals[1];
@@ -14329,7 +14612,7 @@ async function runIssue(ctx) {
14329
14612
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-ISSUE-000", severity: "error", message: "Falta el slug: satlas issue status <slug>" }] };
14330
14613
  }
14331
14614
  const change = await loadChange(root, slug);
14332
- const { config } = await loadConfig(path28.join(root, ".sdd"));
14615
+ const { config } = await loadConfig(path29.join(root, ".sdd"));
14333
14616
  const tracker = change.meta?.tracker;
14334
14617
  const lines = [`Issue de ${slug}`, ""];
14335
14618
  if (!tracker || tracker.provider !== "github") {
@@ -14400,7 +14683,7 @@ async function runApprove(ctx) {
14400
14683
  };
14401
14684
  }
14402
14685
  const { root, workspace, config } = await requireWorkspace(ctx);
14403
- const artifact = target.includes("/") || target.includes("\\") ? target : path29.join("changes", target, "spec.md");
14686
+ const artifact = target.includes("/") || target.includes("\\") ? target : path30.join("changes", target, "spec.md");
14404
14687
  const channelFlag = flagString(ctx.flags, "channel");
14405
14688
  const channel = channelFlag === "presentation" || channelFlag === "editor" || channelFlag === "pr" || channelFlag === "tracker" ? channelFlag : "cli";
14406
14689
  const change = workspace.changes.find(
@@ -14441,13 +14724,13 @@ async function runApprove(ctx) {
14441
14724
  return {
14442
14725
  exitCode: hasErrors2 ? 1 : 0,
14443
14726
  diagnostics: result.diagnostics,
14444
- data: result.approval ? { ...result.approval, file: path29.relative(ctx.cwd, result.file) } : void 0,
14727
+ data: result.approval ? { ...result.approval, file: path30.relative(ctx.cwd, result.file) } : void 0,
14445
14728
  text: lines
14446
14729
  };
14447
14730
  }
14448
14731
 
14449
14732
  // src/commands/archive.ts
14450
- import path30 from "path";
14733
+ import path31 from "path";
14451
14734
  async function runArchive(ctx) {
14452
14735
  const slug = ctx.positionals[0];
14453
14736
  if (!slug) {
@@ -14484,7 +14767,7 @@ async function runArchive(ctx) {
14484
14767
  } else {
14485
14768
  lines.push("El delta no contiene operaciones (ADDED/MODIFIED/REMOVED/RENAMED).");
14486
14769
  }
14487
- if (result.archivedTo) lines.push(` archivado en: ${path30.relative(ctx.cwd, result.archivedTo)}`);
14770
+ if (result.archivedTo) lines.push(` archivado en: ${path31.relative(ctx.cwd, result.archivedTo)}`);
14488
14771
  const hasErrors2 = result.diagnostics.some((d) => d.severity === "error");
14489
14772
  return {
14490
14773
  exitCode: hasErrors2 ? 1 : 0,
@@ -14495,7 +14778,7 @@ async function runArchive(ctx) {
14495
14778
  }
14496
14779
 
14497
14780
  // src/commands/ci.ts
14498
- import path31 from "path";
14781
+ import path32 from "path";
14499
14782
  async function runCi(ctx) {
14500
14783
  const { root, config } = await requireWorkspace(ctx);
14501
14784
  const strict = flagBool(ctx.flags, "strict");
@@ -14521,10 +14804,10 @@ async function runCi(ctx) {
14521
14804
  const lines = ["CI de SpecAtlas", ""];
14522
14805
  let sarifWritten;
14523
14806
  if (sarifPath) {
14524
- const target = path31.resolve(ctx.cwd, sarifPath);
14807
+ const target = path32.resolve(ctx.cwd, sarifPath);
14525
14808
  try {
14526
14809
  await writeText(target, toSarifText({ diagnostics: gate.diagnostics, root, version: cliVersion(), failed: gate.failed }));
14527
- sarifWritten = path31.relative(ctx.cwd, target);
14810
+ sarifWritten = path32.relative(ctx.cwd, target);
14528
14811
  } catch (err) {
14529
14812
  diagnostics.push({
14530
14813
  code: "ATLAS-CI-SARIF-001",
@@ -14558,13 +14841,83 @@ async function runCi(ctx) {
14558
14841
  };
14559
14842
  }
14560
14843
 
14844
+ // src/commands/clarify.ts
14845
+ async function runClarify(ctx) {
14846
+ const slug = ctx.positionals[0];
14847
+ if (!slug) {
14848
+ return { exitCode: 2, diagnostics: [{ code: "ATLAS-CLARIFY-000", severity: "error", message: "Falta el slug: satlas clarify <slug>" }] };
14849
+ }
14850
+ const { workspace, config } = await requireWorkspace(ctx);
14851
+ const change = workspace.changes.find((c) => c.slug === slug);
14852
+ if (!change) {
14853
+ return { exitCode: 2, diagnostics: [{ code: "ATLAS-CLARIFY-000", severity: "error", message: `No existe el cambio "${slug}"` }] };
14854
+ }
14855
+ const open = change.clarify?.open ?? [];
14856
+ const resolved = change.clarify?.resolved ?? [];
14857
+ const mode = config.gates.clarify.mode;
14858
+ const lines = [msg("clarify.title", ctx.language), ""];
14859
+ lines.push(` ${msg("clarify.mode", ctx.language)}: ${mode}`);
14860
+ lines.push(` ${msg("clarify.open", ctx.language)}: ${open.length}`);
14861
+ for (const item of open) lines.push(` [ ] ${item.text}`);
14862
+ lines.push(` ${msg("clarify.resolved", ctx.language)}: ${resolved.length}`);
14863
+ for (const item of resolved) lines.push(` [x] ${item.text}${item.answer ? ` \u2014 ${item.answer}` : ""}`);
14864
+ if (open.length > 0) {
14865
+ lines.push("");
14866
+ lines.push(`Aclara con la fase del agente: /satlas.clarify ${slug}`);
14867
+ } else if (resolved.length === 0) {
14868
+ lines.push("");
14869
+ lines.push("El cambio no tiene preguntas abiertas ni aclaraciones registradas.");
14870
+ }
14871
+ return {
14872
+ exitCode: 0,
14873
+ diagnostics: [],
14874
+ data: {
14875
+ slug,
14876
+ mode,
14877
+ open: open.map((item) => ({ text: item.text, line: item.line })),
14878
+ resolved: resolved.map((item) => ({ text: item.text, line: item.line, answer: item.answer })),
14879
+ ...open.length > 0 ? { action: `/satlas.clarify ${slug}` } : {}
14880
+ },
14881
+ text: lines
14882
+ };
14883
+ }
14884
+
14885
+ // src/commands/docs.ts
14886
+ import path33 from "path";
14887
+ async function runDocs(ctx) {
14888
+ const slug = ctx.positionals[0];
14889
+ if (!slug) {
14890
+ return { exitCode: 2, diagnostics: [{ code: "ATLAS-DOCS-000", severity: "error", message: "Falta el slug: satlas docs <slug> [--tipo tecnica|manual|all]" }] };
14891
+ }
14892
+ const { root } = await requireWorkspace(ctx);
14893
+ const tipoFlag = flagString(ctx.flags, "tipo");
14894
+ const tipo = tipoFlag === "tecnica" || tipoFlag === "manual" || tipoFlag === "all" ? tipoFlag : "all";
14895
+ const result = await generateDocs({ root, slug, tipo });
14896
+ const errors = result.diagnostics.filter((d) => d.severity === "error");
14897
+ const lines = [msg("docs.title", ctx.language), ""];
14898
+ for (const file of result.files) {
14899
+ lines.push(` ${file.created ? msg("docs.created", ctx.language) : msg("docs.updated", ctx.language)}: ${path33.relative(ctx.cwd, file.path)}`);
14900
+ }
14901
+ for (const error of errors) lines.push(` ERROR ${error.code} \u2014 ${error.message}`);
14902
+ return {
14903
+ exitCode: errors.length > 0 ? 2 : 0,
14904
+ diagnostics: result.diagnostics,
14905
+ data: {
14906
+ slug: result.slug,
14907
+ tipo,
14908
+ files: result.files.map((file) => ({ tipo: file.tipo, path: path33.relative(ctx.cwd, file.path), created: file.created }))
14909
+ },
14910
+ text: lines
14911
+ };
14912
+ }
14913
+
14561
14914
  // src/commands/profile.ts
14562
- import path32 from "path";
14915
+ import path34 from "path";
14563
14916
  async function runProfile(ctx) {
14564
14917
  const action = ctx.positionals[0] ?? "detect";
14565
14918
  const { root, workspace } = await requireWorkspace(ctx);
14566
14919
  const officialDir = await resolveProfilesDir();
14567
- const customDir = path32.join(workspace.sddDir, "profiles", "custom");
14920
+ const customDir = path34.join(workspace.sddDir, "profiles", "custom");
14568
14921
  const profiles = [...officialDir ? await loadProfilesFromDir(officialDir) : [], ...await loadProfilesFromDir(customDir)];
14569
14922
  if (action === "detect") {
14570
14923
  const result = await detectProfiles(root, profiles);
@@ -14593,7 +14946,7 @@ async function runProfile(ctx) {
14593
14946
  if (!name) {
14594
14947
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-001", severity: "error", message: "Falta el nombre del perfil: satlas profile create <nombre>" }] };
14595
14948
  }
14596
- const file = path32.join(customDir, `${name}.yaml`);
14949
+ const file = path34.join(customDir, `${name}.yaml`);
14597
14950
  if (await exists(file)) {
14598
14951
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-002", severity: "error", message: `El perfil ya existe: ${file}` }] };
14599
14952
  }
@@ -14602,7 +14955,7 @@ async function runProfile(ctx) {
14602
14955
  exitCode: 0,
14603
14956
  diagnostics: [],
14604
14957
  data: { file },
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."]
14958
+ text: [`Perfil creado: ${path34.relative(ctx.cwd, file)}`, "", "Ed\xEDtalo con los comandos reales de tu stack (build/lint/test) y tus anti-patrones."]
14606
14959
  };
14607
14960
  }
14608
14961
  return {
@@ -14611,7 +14964,7 @@ async function runProfile(ctx) {
14611
14964
  };
14612
14965
  }
14613
14966
  async function isInside(dir, name) {
14614
- return exists(path32.join(dir, `${name}.yaml`));
14967
+ return exists(path34.join(dir, `${name}.yaml`));
14615
14968
  }
14616
14969
  function profileTemplate(name) {
14617
14970
  return `name: ${name}
@@ -14639,7 +14992,7 @@ assumptions: []
14639
14992
  }
14640
14993
 
14641
14994
  // src/commands/doctor.ts
14642
- import path33 from "path";
14995
+ import path35 from "path";
14643
14996
  async function runDoctorCommand(ctx) {
14644
14997
  const { root, config } = await requireWorkspace(ctx);
14645
14998
  const report = await runDoctor(root);
@@ -14664,7 +15017,7 @@ async function runDoctorCommand(ctx) {
14664
15017
  lines.push("Sin hallazgos. El workspace est\xE1 sano.");
14665
15018
  } else {
14666
15019
  for (const finding of report.findings) {
14667
- const location = finding.path ? ` ${path33.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
15020
+ const location = finding.path ? ` ${path35.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
14668
15021
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${location} \u2014 ${finding.message}`);
14669
15022
  if (finding.suggestion) lines.push(` \u21B3 ${finding.suggestion}`);
14670
15023
  }
@@ -14738,7 +15091,7 @@ async function runHelp(ctx, commandName) {
14738
15091
  }
14739
15092
 
14740
15093
  // src/commands/init.ts
14741
- import path34 from "path";
15094
+ import path36 from "path";
14742
15095
  async function runInit(ctx) {
14743
15096
  const languageFlag = flagString(ctx.flags, "language");
14744
15097
  const language = languageFlag === "en" ? "en" : languageFlag === "es" ? "es" : void 0;
@@ -14785,11 +15138,11 @@ async function runInit(ctx) {
14785
15138
  const lines = [msg("init.title", ctx.language), ""];
14786
15139
  if (result.created.length > 0) {
14787
15140
  lines.push(msg("init.done", ctx.language));
14788
- for (const file of result.created) lines.push(` + ${path34.relative(ctx.cwd, file)}`);
15141
+ for (const file of result.created) lines.push(` + ${path36.relative(ctx.cwd, file)}`);
14789
15142
  if (compiled.length > 0) {
14790
15143
  lines.push("");
14791
15144
  lines.push("Adaptadores compilados:");
14792
- for (const file of compiled) lines.push(` + ${path34.relative(ctx.cwd, path34.join(ctx.cwd, file))}`);
15145
+ for (const file of compiled) lines.push(` + ${path36.relative(ctx.cwd, path36.join(ctx.cwd, file))}`);
14793
15146
  }
14794
15147
  lines.push("");
14795
15148
  lines.push("Siguiente: crea un cambio con `satlas new <slug>` y escribe la spec funcional.");
@@ -14799,8 +15152,8 @@ async function runInit(ctx) {
14799
15152
  exitCode: hasErrors2 ? 1 : 0,
14800
15153
  diagnostics: result.diagnostics,
14801
15154
  data: {
14802
- sddDir: path34.relative(ctx.cwd, result.sddDir),
14803
- created: result.created.map((f) => path34.relative(ctx.cwd, f)),
15155
+ sddDir: path36.relative(ctx.cwd, result.sddDir),
15156
+ created: result.created.map((f) => path36.relative(ctx.cwd, f)),
14804
15157
  detected: result.detected?.matches.map((m) => ({ name: m.name, score: m.score })),
14805
15158
  best: result.detected?.best?.name
14806
15159
  },
@@ -14841,7 +15194,7 @@ async function runMetrics(ctx) {
14841
15194
  }
14842
15195
 
14843
15196
  // src/commands/mockup.ts
14844
- import path35 from "path";
15197
+ import path37 from "path";
14845
15198
  async function runMockup(ctx) {
14846
15199
  const { root, workspace, config } = await requireWorkspace(ctx);
14847
15200
  const slug = ctx.positionals[0];
@@ -14890,17 +15243,17 @@ async function runMockup(ctx) {
14890
15243
  ` pantallas: ${plan.screens.length}`,
14891
15244
  ...plan.screens.map((s) => ` - ${s.id}: ${s.title} (${s.illustrates.length} escenario(s))`),
14892
15245
  "",
14893
- ` plan: ${path35.relative(ctx.cwd, planFile)}`,
14894
- ` manifiesto: ${path35.relative(ctx.cwd, manifestFile)}`,
15246
+ ` plan: ${path37.relative(ctx.cwd, planFile)}`,
15247
+ ` manifiesto: ${path37.relative(ctx.cwd, manifestFile)}`,
14895
15248
  "",
14896
15249
  "Siguiente: genera el HTML de cada pantalla (fase /satlas-mockup) y valida con `satlas mockup " + slug + " --check`."
14897
15250
  ];
14898
- return { exitCode: 0, diagnostics: [], data: { plan, planFile: path35.relative(ctx.cwd, planFile), manifestFile: path35.relative(ctx.cwd, manifestFile) }, text: lines };
15251
+ return { exitCode: 0, diagnostics: [], data: { plan, planFile: path37.relative(ctx.cwd, planFile), manifestFile: path37.relative(ctx.cwd, manifestFile) }, text: lines };
14899
15252
  }
14900
15253
 
14901
15254
  // src/mcp/server.ts
14902
15255
  import { promises as fs3 } from "fs";
14903
- import path38 from "path";
15256
+ import path40 from "path";
14904
15257
  import { createInterface } from "readline/promises";
14905
15258
 
14906
15259
  // src/mcp/protocol.ts
@@ -14979,6 +15332,7 @@ async function runAtlasFixes(_args, host) {
14979
15332
  slug: fix.slug,
14980
15333
  date: fix.date,
14981
15334
  result: fix.result,
15335
+ source: fix.source,
14982
15336
  domain: fix.domain,
14983
15337
  title: fix.title,
14984
15338
  covers: fix.covers,
@@ -14988,12 +15342,12 @@ async function runAtlasFixes(_args, host) {
14988
15342
  }
14989
15343
 
14990
15344
  // src/mcp/tools/glossary.ts
14991
- import path36 from "path";
15345
+ import path38 from "path";
14992
15346
  async function runAtlasGlossary(_args, host) {
14993
15347
  const resolved = await host.getWorkspace();
14994
15348
  if (!resolved) return noWorkspaceResult();
14995
15349
  const { root, config } = resolved;
14996
- const glossaryPath = path36.resolve(root, config.spec.glossary);
15350
+ const glossaryPath = path38.resolve(root, config.spec.glossary);
14997
15351
  const raw = await readTextIfExists(glossaryPath);
14998
15352
  if (raw === void 0) {
14999
15353
  return jsonResult({
@@ -15039,7 +15393,7 @@ async function runAtlasImpact(args, host) {
15039
15393
  }
15040
15394
 
15041
15395
  // src/evaluate.ts
15042
- import path37 from "path";
15396
+ import path39 from "path";
15043
15397
  function livingRequirementsMap2(specs) {
15044
15398
  const map = /* @__PURE__ */ new Map();
15045
15399
  for (const spec of specs) {
@@ -15048,7 +15402,7 @@ function livingRequirementsMap2(specs) {
15048
15402
  return map;
15049
15403
  }
15050
15404
  async function evaluateChange(workspace, config, change, approvals) {
15051
- const deltaPath = path37.join(change.dir, "spec.md");
15405
+ const deltaPath = path39.join(change.dir, "spec.md");
15052
15406
  const deltaContent = await readTextIfExists(deltaPath);
15053
15407
  const approval = verifyApproval(change, approvals, config, deltaContent ?? void 0);
15054
15408
  const living = livingRequirementsMap2(workspace.specs);
@@ -15067,7 +15421,8 @@ async function evaluateChange(workspace, config, change, approvals) {
15067
15421
  blockingFindings: change.delta ? blocking : 0,
15068
15422
  ...mockupsAreReady !== void 0 ? { mockupsReady: mockupsAreReady } : {}
15069
15423
  });
15070
- return { change, approval, lintFindings, trace, blocking, state };
15424
+ const phaseAdvisories = [...clarifyAdvisory(change, config), ...docsAdvisory(change, config)];
15425
+ return { change, approval, lintFindings: [...lintFindings, ...phaseAdvisories], trace, blocking, state };
15071
15426
  }
15072
15427
 
15073
15428
  // src/mcp/tools/next.ts
@@ -15320,8 +15675,8 @@ var WorkspaceCache = class {
15320
15675
  }
15321
15676
  };
15322
15677
  async function workspaceStamp(root) {
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")];
15678
+ const sddDir = path40.join(root, ".sdd");
15679
+ const markers = [sddDir, path40.join(sddDir, "config.yaml"), path40.join(sddDir, "changes"), path40.join(sddDir, "specs"), path40.join(sddDir, "approvals.yaml")];
15325
15680
  let stamp = 0;
15326
15681
  for (const marker of markers) {
15327
15682
  try {
@@ -15339,7 +15694,7 @@ async function resolveMcpWorkspace(cwd, cache) {
15339
15694
  const cached2 = cache.get(root);
15340
15695
  if (cached2 && cached2.mtimeMs === stamp) return cached2.value;
15341
15696
  const { workspace, config } = await loadWorkspace(root);
15342
- const approvals = await loadApprovals(path38.join(root, ".sdd"));
15697
+ const approvals = await loadApprovals(path40.join(root, ".sdd"));
15343
15698
  const value = { root, workspace, config, approvals: approvals.byArtifact };
15344
15699
  cache.set(root, stamp, value);
15345
15700
  return value;
@@ -15431,7 +15786,7 @@ async function runMcp(ctx) {
15431
15786
  }
15432
15787
 
15433
15788
  // src/commands/packs.ts
15434
- import path39 from "path";
15789
+ import path41 from "path";
15435
15790
  async function runPacks(ctx) {
15436
15791
  const { root, config, workspace } = await requireWorkspace(ctx);
15437
15792
  const slug = flagString(ctx.flags, "check");
@@ -15489,14 +15844,14 @@ async function runPacks(ctx) {
15489
15844
  data: {
15490
15845
  slug,
15491
15846
  packs: evaluations.map((evaluation) => ({ id: evaluation.pack.id, status: evaluation.status, passed: evaluation.passed, failed: evaluation.failed, results: evaluation.results })),
15492
- analyzePath: path39.posix.join(".sdd", "changes", slug, "analyze.md")
15847
+ analyzePath: path41.posix.join(".sdd", "changes", slug, "analyze.md")
15493
15848
  },
15494
15849
  text: lines
15495
15850
  };
15496
15851
  }
15497
15852
 
15498
15853
  // src/commands/new.ts
15499
- import path40 from "path";
15854
+ import path42 from "path";
15500
15855
  async function runNew(ctx) {
15501
15856
  const slug = ctx.positionals[0];
15502
15857
  if (!slug) {
@@ -15522,14 +15877,14 @@ async function runNew(ctx) {
15522
15877
  msg("new.title", ctx.language),
15523
15878
  "",
15524
15879
  msg("new.done", ctx.language),
15525
- ...result.files.map((f) => ` + ${path40.relative(ctx.cwd, f)}`),
15880
+ ...result.files.map((f) => ` + ${path42.relative(ctx.cwd, f)}`),
15526
15881
  "",
15527
15882
  `Siguiente: /satlas.specify ${result.slug} \u2014 escribe la especificaci\xF3n 100% funcional y de negocio.`
15528
15883
  ];
15529
15884
  return {
15530
15885
  exitCode: hasErrors2 ? 1 : 0,
15531
15886
  diagnostics: result.diagnostics,
15532
- data: { slug: result.slug, files: result.files.map((f) => path40.relative(ctx.cwd, f)) },
15887
+ data: { slug: result.slug, files: result.files.map((f) => path42.relative(ctx.cwd, f)) },
15533
15888
  text: lines
15534
15889
  };
15535
15890
  }
@@ -15564,7 +15919,7 @@ async function runNext(ctx) {
15564
15919
  }
15565
15920
 
15566
15921
  // src/commands/present.ts
15567
- import path41 from "path";
15922
+ import path43 from "path";
15568
15923
  async function runPresentCommand(ctx) {
15569
15924
  const { root } = await requireWorkspace(ctx);
15570
15925
  const slug = ctx.positionals[0];
@@ -15576,7 +15931,7 @@ async function runPresentCommand(ctx) {
15576
15931
  if (result.path) {
15577
15932
  lines.push("Propuesta generada");
15578
15933
  lines.push("");
15579
- lines.push(` archivo: ${path41.relative(ctx.cwd, result.path)}`);
15934
+ lines.push(` archivo: ${path43.relative(ctx.cwd, result.path)}`);
15580
15935
  if (result.hash) lines.push(` hash de la spec: ${shortHash(result.hash)}`);
15581
15936
  lines.push("");
15582
15937
  lines.push("\xC1brela en el navegador y comp\xE1rtela con el stakeholder.");
@@ -15586,7 +15941,7 @@ async function runPresentCommand(ctx) {
15586
15941
  return {
15587
15942
  exitCode: hasErrors2 ? 1 : 0,
15588
15943
  diagnostics: result.diagnostics,
15589
- data: result.path ? { path: path41.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
15944
+ data: result.path ? { path: path43.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
15590
15945
  text: lines
15591
15946
  };
15592
15947
  }
@@ -15702,7 +16057,8 @@ async function runStatus(ctx) {
15702
16057
  lines.push(" (se llenan al archivar un fix)");
15703
16058
  } else {
15704
16059
  for (const fix of fixes) {
15705
- lines.push(` ${fix.date || "\u2014"} ${(fix.domain ?? "\u2014").padEnd(10)} ${fix.slug} \u2014 ${fix.result}`);
16060
+ const origin = fix.source === "archive" ? " (hist\xF3rico)" : "";
16061
+ lines.push(` ${fix.date || "\u2014"} ${(fix.domain ?? "\u2014").padEnd(10)} ${fix.slug} \u2014 ${fix.result}${origin}`);
15706
16062
  }
15707
16063
  }
15708
16064
  const diagnostics = [...workspace.diagnostics, ...advisory.diagnostics];
@@ -15717,6 +16073,7 @@ async function runStatus(ctx) {
15717
16073
  slug: fix.slug,
15718
16074
  date: fix.date,
15719
16075
  result: fix.result,
16076
+ source: fix.source,
15720
16077
  ...fix.domain !== void 0 ? { domain: fix.domain } : {},
15721
16078
  covers: fix.covers
15722
16079
  })),
@@ -15732,7 +16089,7 @@ async function runStatus(ctx) {
15732
16089
  }
15733
16090
 
15734
16091
  // src/commands/trace.ts
15735
- import path42 from "path";
16092
+ import path44 from "path";
15736
16093
  async function runTrace(ctx) {
15737
16094
  const { workspace, config } = await requireWorkspace(ctx);
15738
16095
  const slug = flagString(ctx.flags, "change");
@@ -15752,7 +16109,7 @@ async function runTrace(ctx) {
15752
16109
  data.push({ slug: change.slug, nodes: result.graph.nodes.length, edges: result.graph.edges.length, summary: result.summary, findings: result.findings });
15753
16110
  lines.push(` ${change.slug}: ${result.graph.nodes.length} nodos, ${result.graph.edges.length} aristas, ${errors} errores, ${result.summary.warnings} avisos`);
15754
16111
  for (const finding of result.findings) {
15755
- const location = finding.path ? ` (${path42.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
16112
+ const location = finding.path ? ` (${path44.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
15756
16113
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}${location}`);
15757
16114
  }
15758
16115
  }
@@ -15762,7 +16119,7 @@ async function runTrace(ctx) {
15762
16119
  }
15763
16120
 
15764
16121
  // src/commands/upgrade.ts
15765
- import path43 from "path";
16122
+ import path45 from "path";
15766
16123
  async function runUpgrade(ctx) {
15767
16124
  const { root } = await requireWorkspace(ctx);
15768
16125
  const apply = flagBool(ctx.flags, "apply");
@@ -15816,7 +16173,7 @@ function renderPreview(plan, root, ctx) {
15816
16173
  }
15817
16174
  for (const item of plan.unreadable) {
15818
16175
  lines.push(` Ilegible: "${item.artifact}" \u2014 ${item.reason}`);
15819
- lines.push(` \u21B3 ${path43.relative(ctx.cwd, item.path)}`);
16176
+ lines.push(` \u21B3 ${path45.relative(ctx.cwd, item.path)}`);
15820
16177
  }
15821
16178
  return { exitCode: 0, diagnostics: [], data: summarizable(plan), text: lines };
15822
16179
  }
@@ -15879,7 +16236,7 @@ function renderRollback(report, ctx) {
15879
16236
  }
15880
16237
 
15881
16238
  // src/commands/validate.ts
15882
- import path44 from "path";
16239
+ import path46 from "path";
15883
16240
  async function runValidate(ctx) {
15884
16241
  const { root, workspace, config, approvals } = await requireWorkspace(ctx);
15885
16242
  const slug = flagString(ctx.flags, "change");
@@ -15906,7 +16263,7 @@ async function runValidate(ctx) {
15906
16263
  for (const finding of evaluation.lintFindings) {
15907
16264
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${finding.line ? `:${finding.line}` : ""} \u2014 ${finding.message}`);
15908
16265
  }
15909
- lines.push(` archivo: ${path44.relative(ctx.cwd, path44.join(change.dir, "spec.md"))}`);
16266
+ lines.push(` archivo: ${path46.relative(ctx.cwd, path46.join(change.dir, "spec.md"))}`);
15910
16267
  }
15911
16268
  if (targets.length === 0) lines.push("Sin cambios activos.");
15912
16269
  if (advisory && advisory.plan.pending.length > 0) {
@@ -15934,7 +16291,7 @@ async function runValidate(ctx) {
15934
16291
  }
15935
16292
 
15936
16293
  // src/commands/verify.ts
15937
- import path45 from "path";
16294
+ import path47 from "path";
15938
16295
  async function runVerify(ctx) {
15939
16296
  const { root, workspace } = await requireWorkspace(ctx);
15940
16297
  const slug = ctx.positionals[0];
@@ -15977,7 +16334,7 @@ async function runVerify(ctx) {
15977
16334
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-VERIFY-001", severity: "error", message: "Falta --by <nombre> (o define meta.owner)" }] };
15978
16335
  }
15979
16336
  const profilesDir = await resolveProfilesDir();
15980
- const profile = await loadActiveProfile(path45.join(root, ".sdd"), [profilesDir ?? "", path45.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
16337
+ const profile = await loadActiveProfile(path47.join(root, ".sdd"), [profilesDir ?? "", path47.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
15981
16338
  const record = await recordEvidence({
15982
16339
  root,
15983
16340
  slug,
@@ -15999,7 +16356,7 @@ async function runVerify(ctx) {
15999
16356
  if (record.evidence.command) lines.push(` comando: ${record.evidence.command}`);
16000
16357
  lines.push(` resultado: ${record.evidence.result}`);
16001
16358
  if (record.evidence.outputHash) lines.push(` hash: ${record.evidence.outputHash}`);
16002
- lines.push(` archivo: ${path45.relative(ctx.cwd, record.path)}`);
16359
+ lines.push(` archivo: ${path47.relative(ctx.cwd, record.path)}`);
16003
16360
  if (record.stdout) {
16004
16361
  lines.push("");
16005
16362
  lines.push(" salida (\xFAltimas l\xEDneas):");
@@ -16014,7 +16371,7 @@ async function runVerify(ctx) {
16014
16371
  return {
16015
16372
  exitCode: record.exitCode === 0 ? 0 : 1,
16016
16373
  diagnostics: record.diagnostics,
16017
- data: { evidence: record.evidence, path: path45.relative(ctx.cwd, record.path), exitCode: record.exitCode },
16374
+ data: { evidence: record.evidence, path: path47.relative(ctx.cwd, record.path), exitCode: record.exitCode },
16018
16375
  text: lines
16019
16376
  };
16020
16377
  }
@@ -16069,6 +16426,8 @@ var HANDLERS2 = {
16069
16426
  trace: runTrace,
16070
16427
  waves: runWaves,
16071
16428
  doctor: runDoctorCommand,
16429
+ clarify: runClarify,
16430
+ docs: runDocs,
16072
16431
  upgrade: runUpgrade,
16073
16432
  approve: runApprove,
16074
16433
  issue: runIssue,
@@ -16114,7 +16473,7 @@ async function requireWorkspace(ctx) {
16114
16473
  throw new CliError("ATLAS-WS-001", msg("cli.noWorkspace", ctx.language), 2);
16115
16474
  }
16116
16475
  const { workspace, config } = await loadWorkspace(root);
16117
- const approvals = await loadApprovals(path46.join(root, ".sdd"));
16476
+ const approvals = await loadApprovals(path48.join(root, ".sdd"));
16118
16477
  return { root, workspace, config, approvals: approvals.byArtifact };
16119
16478
  }
16120
16479
  var CliError = class extends Error {
@@ -16181,7 +16540,7 @@ function printResult(command, result, json2, language) {
16181
16540
  }
16182
16541
  const lines = [...result.text ?? []];
16183
16542
  for (const d of result.diagnostics) {
16184
- const location = d.path ? ` ${path46.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
16543
+ const location = d.path ? ` ${path48.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
16185
16544
  lines.push(`${d.severity === "error" ? "ERROR" : d.severity === "warning" ? "AVISO" : "NOTA"} ${d.code}${location} \u2014 ${d.message}`);
16186
16545
  if (d.suggestion) lines.push(` \u21B3 ${d.suggestion}`);
16187
16546
  }