specatlas 0.1.27 → 0.1.29
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 +517 -275
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -1586,7 +1586,7 @@ var require_core = __commonJS({
|
|
|
1586
1586
|
});
|
|
1587
1587
|
|
|
1588
1588
|
// src/cli.ts
|
|
1589
|
-
import
|
|
1589
|
+
import path46 from "path";
|
|
1590
1590
|
|
|
1591
1591
|
// ../core/dist/index.js
|
|
1592
1592
|
import path from "path";
|
|
@@ -1599,23 +1599,25 @@ import path2 from "path";
|
|
|
1599
1599
|
import path3 from "path";
|
|
1600
1600
|
import { parse as parseYaml4 } from "yaml";
|
|
1601
1601
|
import { z as z3 } from "zod";
|
|
1602
|
+
import path5 from "path";
|
|
1602
1603
|
import path4 from "path";
|
|
1603
1604
|
import { stringify as stringifyYaml3 } from "yaml";
|
|
1604
|
-
import
|
|
1605
|
+
import { stringify as stringifyYaml4 } from "yaml";
|
|
1606
|
+
import path6 from "path";
|
|
1605
1607
|
import { parse as parseYaml5 } from "yaml";
|
|
1606
1608
|
import { execFile } from "child_process";
|
|
1607
|
-
import path6 from "path";
|
|
1608
1609
|
import path7 from "path";
|
|
1609
|
-
import { randomBytes } from "crypto";
|
|
1610
|
-
import path10 from "path";
|
|
1611
1610
|
import path8 from "path";
|
|
1612
|
-
import {
|
|
1613
|
-
import
|
|
1611
|
+
import { randomBytes } from "crypto";
|
|
1612
|
+
import path11 from "path";
|
|
1614
1613
|
import path9 from "path";
|
|
1614
|
+
import { parse as parseYaml6, stringify as stringifyYaml5 } from "yaml";
|
|
1615
|
+
import { z as z4 } from "zod";
|
|
1616
|
+
import path10 from "path";
|
|
1615
1617
|
import { parse as parseYaml7 } from "yaml";
|
|
1616
1618
|
import { z as z5 } from "zod";
|
|
1617
|
-
import path11 from "path";
|
|
1618
1619
|
import path12 from "path";
|
|
1620
|
+
import path13 from "path";
|
|
1619
1621
|
|
|
1620
1622
|
// ../../node_modules/.pnpm/highlight.js@11.12.0/node_modules/highlight.js/es/core.js
|
|
1621
1623
|
var import_core = __toESM(require_core(), 1);
|
|
@@ -9020,18 +9022,18 @@ function renderMarkdown(markdown2, opts = {}) {
|
|
|
9020
9022
|
}
|
|
9021
9023
|
|
|
9022
9024
|
// ../core/dist/index.js
|
|
9023
|
-
import path13 from "path";
|
|
9024
9025
|
import path14 from "path";
|
|
9025
9026
|
import path15 from "path";
|
|
9027
|
+
import path16 from "path";
|
|
9026
9028
|
import { promises as fs } from "fs";
|
|
9027
9029
|
import { cp, rename, rm } from "fs/promises";
|
|
9028
|
-
import path16 from "path";
|
|
9029
|
-
import { promises as fs2 } from "fs";
|
|
9030
9030
|
import path17 from "path";
|
|
9031
|
-
import {
|
|
9031
|
+
import { promises as fs2 } from "fs";
|
|
9032
9032
|
import path18 from "path";
|
|
9033
|
+
import { parse as parseYaml8, stringify as stringifyYaml6 } from "yaml";
|
|
9033
9034
|
import path19 from "path";
|
|
9034
9035
|
import path20 from "path";
|
|
9036
|
+
import path21 from "path";
|
|
9035
9037
|
var CORE_VERSION = "0.0.1";
|
|
9036
9038
|
var REQ_ID_RE = /^REQ-[A-Z0-9]+(?:-[A-Z0-9]+)*-\d{3}$/;
|
|
9037
9039
|
var SCENARIO_ID_RE = /^REQ-[A-Z0-9]+(?:-[A-Z0-9]+)*-\d{3}-S\d+$/;
|
|
@@ -9747,14 +9749,14 @@ function wordBoundary(text, term) {
|
|
|
9747
9749
|
const re = new RegExp(`(?<![\\p{L}\\p{N}])${escaped}(?![\\p{L}\\p{N}])`, "iu");
|
|
9748
9750
|
return re.test(text);
|
|
9749
9751
|
}
|
|
9750
|
-
function lintText(text, code, terms, label,
|
|
9752
|
+
function lintText(text, code, terms, label, path222, line) {
|
|
9751
9753
|
const out = [];
|
|
9752
9754
|
const lower = text.toLowerCase();
|
|
9753
9755
|
for (const term of terms) {
|
|
9754
9756
|
if (wordBoundary(lower, term)) {
|
|
9755
9757
|
out.push(
|
|
9756
9758
|
diag(code, "error", `${label}: "${term}"`, {
|
|
9757
|
-
path:
|
|
9759
|
+
path: path222,
|
|
9758
9760
|
line,
|
|
9759
9761
|
suggestion: "La especificaci\xF3n es funcional y de negocio: describe comportamiento, no tecnolog\xEDa ni adjetivos vagos"
|
|
9760
9762
|
})
|
|
@@ -9763,7 +9765,7 @@ function lintText(text, code, terms, label, path212, line) {
|
|
|
9763
9765
|
}
|
|
9764
9766
|
return out;
|
|
9765
9767
|
}
|
|
9766
|
-
function lintRequirement(req,
|
|
9768
|
+
function lintRequirement(req, path222, opts = {}) {
|
|
9767
9769
|
const out = [];
|
|
9768
9770
|
const vague = opts.language === "en" ? VAGUE_EN : VAGUE_ES;
|
|
9769
9771
|
const tech = opts.language === "en" ? TECH_EN : TECH_ES;
|
|
@@ -9774,32 +9776,32 @@ function lintRequirement(req, path212, opts = {}) {
|
|
|
9774
9776
|
...req.scenarios.flatMap((s) => [...s.when.map((w) => ({ text: w, line: s.line })), ...s.then.map((t) => ({ text: t, line: s.line }))])
|
|
9775
9777
|
];
|
|
9776
9778
|
for (const part of parts) {
|
|
9777
|
-
out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n",
|
|
9779
|
+
out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path222, part.line));
|
|
9778
9780
|
if (opts.businessOnly !== false) {
|
|
9779
|
-
out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio",
|
|
9781
|
+
out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path222, part.line));
|
|
9780
9782
|
}
|
|
9781
9783
|
}
|
|
9782
9784
|
if (req.scenarios.length === 0) {
|
|
9783
|
-
out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path:
|
|
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" }));
|
|
9784
9786
|
}
|
|
9785
9787
|
return out;
|
|
9786
9788
|
}
|
|
9787
|
-
function lintDelta(delta, livingRequirements,
|
|
9789
|
+
function lintDelta(delta, livingRequirements, path222, opts = {}) {
|
|
9788
9790
|
const out = [...delta.diagnostics];
|
|
9789
9791
|
for (const req of [...delta.added, ...delta.modified]) {
|
|
9790
|
-
out.push(...lintRequirement(req,
|
|
9792
|
+
out.push(...lintRequirement(req, path222, opts));
|
|
9791
9793
|
}
|
|
9792
9794
|
for (const req of delta.modified) {
|
|
9793
9795
|
const living = livingRequirements.get(req.id);
|
|
9794
9796
|
if (!living) {
|
|
9795
|
-
out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path:
|
|
9797
|
+
out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path222, line: req.line }));
|
|
9796
9798
|
continue;
|
|
9797
9799
|
}
|
|
9798
9800
|
for (const existing of living.scenarios) {
|
|
9799
9801
|
if (!req.scenarios.some((s) => s.id === existing.id)) {
|
|
9800
9802
|
out.push(
|
|
9801
9803
|
diag("TRACE-007", "error", `MODIFIED ${req.id} pierde el escenario ${existing.id}: copia el bloque completo`, {
|
|
9802
|
-
path:
|
|
9804
|
+
path: path222,
|
|
9803
9805
|
line: req.line,
|
|
9804
9806
|
suggestion: "Copia el bloque completo de la spec viva y ed\xEDtalo; para quitarlo, decl\xE1ralo en REMOVED"
|
|
9805
9807
|
})
|
|
@@ -9810,15 +9812,15 @@ function lintDelta(delta, livingRequirements, path212, opts = {}) {
|
|
|
9810
9812
|
for (const req of delta.removed) {
|
|
9811
9813
|
const living = livingRequirements.get(req.id);
|
|
9812
9814
|
if (!living) {
|
|
9813
|
-
out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path:
|
|
9815
|
+
out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path222, line: req.line }));
|
|
9814
9816
|
}
|
|
9815
9817
|
}
|
|
9816
9818
|
for (const rename2 of delta.renamed) {
|
|
9817
9819
|
if (rename2.from.id !== rename2.to.id) {
|
|
9818
|
-
out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path:
|
|
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 }));
|
|
9819
9821
|
}
|
|
9820
9822
|
if (!livingRequirements.has(rename2.from.id)) {
|
|
9821
|
-
out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path:
|
|
9823
|
+
out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path222, line: rename2.line }));
|
|
9822
9824
|
}
|
|
9823
9825
|
}
|
|
9824
9826
|
return out;
|
|
@@ -9842,7 +9844,7 @@ var MERMAID_KEYWORDS = [
|
|
|
9842
9844
|
"architecture-beta"
|
|
9843
9845
|
];
|
|
9844
9846
|
var MERMAID_BLOCKS = /^\s*(alt|loop|opt|par|rect|critical|break|subgraph)\b/;
|
|
9845
|
-
function lintPlan(planText,
|
|
9847
|
+
function lintPlan(planText, path222) {
|
|
9846
9848
|
const out = [];
|
|
9847
9849
|
const blocks = [...planText.matchAll(/```mermaid\r?\n([\s\S]*?)```/g)];
|
|
9848
9850
|
for (const [index, block] of blocks.entries()) {
|
|
@@ -9852,7 +9854,7 @@ function lintPlan(planText, path212) {
|
|
|
9852
9854
|
if (!MERMAID_KEYWORDS.some((keyword) => first.startsWith(keyword))) {
|
|
9853
9855
|
out.push(
|
|
9854
9856
|
diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: la primera l\xEDnea debe declarar el tipo (${MERMAID_KEYWORDS.slice(0, 5).join(", ")}\u2026) y empieza por "${first.slice(0, 30)}"`, {
|
|
9855
|
-
path:
|
|
9857
|
+
path: path222,
|
|
9856
9858
|
suggestion: "Corrige el tipo del diagrama o elimina el bloque"
|
|
9857
9859
|
})
|
|
9858
9860
|
);
|
|
@@ -9866,7 +9868,7 @@ function lintPlan(planText, path212) {
|
|
|
9866
9868
|
if (open !== 0) {
|
|
9867
9869
|
out.push(
|
|
9868
9870
|
diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: faltan ${Math.abs(open)} \`end\` (bloques alt/loop/subgraph sin cerrar)`, {
|
|
9869
|
-
path:
|
|
9871
|
+
path: path222,
|
|
9870
9872
|
suggestion: "Cierra cada bloque alt/loop/opt/par/rect/subgraph con `end`"
|
|
9871
9873
|
})
|
|
9872
9874
|
);
|
|
@@ -9878,7 +9880,7 @@ function lintPlan(planText, path212) {
|
|
|
9878
9880
|
if (message.includes(";")) {
|
|
9879
9881
|
out.push(
|
|
9880
9882
|
diag("LINT-PLN-003", "error", `Diagrama mermaid ${index + 1}: el mensaje "${message.trim().slice(0, 40)}\u2026" usa \`;\` y mermaid lo interpreta como fin de sentencia`, {
|
|
9881
|
-
path:
|
|
9883
|
+
path: path222,
|
|
9882
9884
|
suggestion: "Sustituye `;` por `\xB7` o `,` en los mensajes de sequenceDiagram"
|
|
9883
9885
|
})
|
|
9884
9886
|
);
|
|
@@ -10033,6 +10035,16 @@ function checkTrace(input) {
|
|
|
10033
10035
|
findings.push(diag("TRACE-006", "warning", `Evidencia hu\xE9rfana: ${ev.scenario} no existe`, { path: change.verify?.path, line: ev.line }));
|
|
10034
10036
|
}
|
|
10035
10037
|
}
|
|
10038
|
+
for (const cover of change.fixCovers ?? []) {
|
|
10039
|
+
if (!livingReqs.has(cover)) {
|
|
10040
|
+
findings.push(
|
|
10041
|
+
diag("TRACE-011", "warning", `El fix ${change.slug} declara ${cover}, que no existe en las specs vivas`, {
|
|
10042
|
+
path: change.fix?.path,
|
|
10043
|
+
suggestion: "Corrige el identificador o qu\xEDtalo: la declaraci\xF3n es opcional"
|
|
10044
|
+
})
|
|
10045
|
+
);
|
|
10046
|
+
}
|
|
10047
|
+
}
|
|
10036
10048
|
const summary = countBySeverity(findings);
|
|
10037
10049
|
return { graph: buildTraceGraph(input), findings, summary };
|
|
10038
10050
|
}
|
|
@@ -10396,34 +10408,142 @@ function detectionToYaml(result, generatedAt) {
|
|
|
10396
10408
|
}
|
|
10397
10409
|
return lines.join("\n") + "\n";
|
|
10398
10410
|
}
|
|
10411
|
+
var LIVING_FIXES_DIR = path4.join(".sdd", "fixes");
|
|
10412
|
+
var COMMENT_RE = /<!--[\s\S]*?-->/g;
|
|
10413
|
+
function parseFixCovers(content) {
|
|
10414
|
+
const clean = content.replace(COMMENT_RE, "");
|
|
10415
|
+
const covers = [];
|
|
10416
|
+
for (const line of clean.replace(/\r\n?/g, "\n").split("\n")) {
|
|
10417
|
+
const match = /^\s*-?\s*Cubre:\s*(.+?)\s*$/.exec(line);
|
|
10418
|
+
if (!match) continue;
|
|
10419
|
+
for (const raw of (match[1] ?? "").split(",")) {
|
|
10420
|
+
const id = raw.trim().toUpperCase();
|
|
10421
|
+
if (/^REQ-[A-Z0-9-]+$/.test(id) && !covers.includes(id)) covers.push(id);
|
|
10422
|
+
}
|
|
10423
|
+
}
|
|
10424
|
+
return covers;
|
|
10425
|
+
}
|
|
10426
|
+
function coversOf(value) {
|
|
10427
|
+
if (Array.isArray(value)) {
|
|
10428
|
+
return value.map((v) => String(v).trim().toUpperCase()).filter((v) => /^REQ-[A-Z0-9-]+$/.test(v));
|
|
10429
|
+
}
|
|
10430
|
+
if (typeof value === "string") {
|
|
10431
|
+
return value.split(",").map((v) => v.trim().toUpperCase()).filter((v) => /^REQ-[A-Z0-9-]+$/.test(v));
|
|
10432
|
+
}
|
|
10433
|
+
return [];
|
|
10434
|
+
}
|
|
10435
|
+
function parseLivingFix(content, file) {
|
|
10436
|
+
const fm = parseFrontmatter(content, file);
|
|
10437
|
+
const data = fm.data;
|
|
10438
|
+
const base = path4.basename(file).replace(/\.md$/i, "");
|
|
10439
|
+
const slug = typeof data["slug"] === "string" && data["slug"].trim() !== "" ? data["slug"].trim() : base.replace(/^\d{4}-\d{2}-/, "");
|
|
10440
|
+
const fix = {
|
|
10441
|
+
slug,
|
|
10442
|
+
file,
|
|
10443
|
+
date: typeof data["date"] === "string" ? data["date"] : "",
|
|
10444
|
+
result: typeof data["result"] === "string" ? data["result"] : "pass",
|
|
10445
|
+
covers: coversOf(data["covers"]),
|
|
10446
|
+
content: fm.body.trimStart(),
|
|
10447
|
+
source: "living"
|
|
10448
|
+
};
|
|
10449
|
+
if (typeof data["domain"] === "string") fix.domain = data["domain"];
|
|
10450
|
+
if (typeof data["title"] === "string") fix.title = data["title"];
|
|
10451
|
+
return fix;
|
|
10452
|
+
}
|
|
10453
|
+
async function archivedFixes(root, known) {
|
|
10454
|
+
const archiveDir = path4.join(root, ".sdd", "changes", "archive");
|
|
10455
|
+
const fixes = [];
|
|
10456
|
+
for (const entry of await listDirs(archiveDir)) {
|
|
10457
|
+
const dir = path4.join(archiveDir, entry);
|
|
10458
|
+
const fixFile = path4.join(dir, "fix.md");
|
|
10459
|
+
const fixRaw = await readTextIfExists(fixFile);
|
|
10460
|
+
if (fixRaw === void 0) continue;
|
|
10461
|
+
const metaFile = path4.join(dir, "meta.yaml");
|
|
10462
|
+
const metaRaw = await readTextIfExists(metaFile);
|
|
10463
|
+
const meta = metaRaw !== void 0 ? parseChangeMeta(metaRaw, metaFile).meta : void 0;
|
|
10464
|
+
if (meta?.lane !== "fix") continue;
|
|
10465
|
+
const slug = entry.replace(/^\d{4}-\d{2}-/, "");
|
|
10466
|
+
if (known.has(slug)) continue;
|
|
10467
|
+
const evidence = parseVerifyFile(fixRaw, fixFile).evidence;
|
|
10468
|
+
const fix = {
|
|
10469
|
+
slug,
|
|
10470
|
+
file: fixFile,
|
|
10471
|
+
date: /^(\d{4}-\d{2})/.exec(entry)?.[1] ?? "",
|
|
10472
|
+
result: evidence.some((e) => e.result === "pass") ? "pass" : evidence.length > 0 ? "fail" : "pending",
|
|
10473
|
+
covers: parseFixCovers(fixRaw),
|
|
10474
|
+
content: parseFrontmatter(fixRaw, fixFile).body.trimStart(),
|
|
10475
|
+
source: "archive"
|
|
10476
|
+
};
|
|
10477
|
+
if (meta.domain !== void 0) fix.domain = meta.domain;
|
|
10478
|
+
if (meta.title !== void 0) fix.title = meta.title;
|
|
10479
|
+
fixes.push(fix);
|
|
10480
|
+
}
|
|
10481
|
+
return fixes;
|
|
10482
|
+
}
|
|
10483
|
+
async function loadLivingFixes(root) {
|
|
10484
|
+
const dir = path4.join(root, LIVING_FIXES_DIR);
|
|
10485
|
+
const entries = (await listDir(dir)).filter((entry) => entry.toLowerCase().endsWith(".md")).sort();
|
|
10486
|
+
const fixes = [];
|
|
10487
|
+
for (const entry of entries) {
|
|
10488
|
+
const file = path4.join(dir, entry);
|
|
10489
|
+
const content = await readTextIfExists(file);
|
|
10490
|
+
if (content === void 0) continue;
|
|
10491
|
+
fixes.push(parseLivingFix(content, file));
|
|
10492
|
+
}
|
|
10493
|
+
fixes.push(...await archivedFixes(root, new Set(fixes.map((fix) => fix.slug))));
|
|
10494
|
+
return fixes.sort((a, b) => b.date.localeCompare(a.date) || b.slug.localeCompare(a.slug));
|
|
10495
|
+
}
|
|
10496
|
+
async function writeLivingFix(root, input) {
|
|
10497
|
+
const dir = path4.join(root, LIVING_FIXES_DIR);
|
|
10498
|
+
const month = input.date.slice(0, 7);
|
|
10499
|
+
const file = path4.join(dir, `${month}-${input.slug}.md`);
|
|
10500
|
+
const relativePath = toPosix(path4.relative(root, file));
|
|
10501
|
+
if (await exists(file)) return { file, relativePath, created: false };
|
|
10502
|
+
const data = {
|
|
10503
|
+
slug: input.slug,
|
|
10504
|
+
date: input.date,
|
|
10505
|
+
result: input.result ?? "pass"
|
|
10506
|
+
};
|
|
10507
|
+
if (input.domain) data["domain"] = input.domain;
|
|
10508
|
+
if (input.title) data["title"] = input.title;
|
|
10509
|
+
if (input.covers && input.covers.length > 0) data["covers"] = input.covers;
|
|
10510
|
+
const body = input.content.replace(/^[\r\n]+/, "").trimEnd();
|
|
10511
|
+
const text = `---
|
|
10512
|
+
${stringifyYaml3(data, { lineWidth: 120 })}---
|
|
10513
|
+
|
|
10514
|
+
${body}
|
|
10515
|
+
`;
|
|
10516
|
+
await writeText(file, text);
|
|
10517
|
+
return { file, relativePath, created: true };
|
|
10518
|
+
}
|
|
10399
10519
|
var SDD_DIR = ".sdd";
|
|
10400
10520
|
async function findWorkspaceRoot(start) {
|
|
10401
|
-
let current =
|
|
10521
|
+
let current = path5.resolve(start);
|
|
10402
10522
|
for (let i = 0; i < 40; i += 1) {
|
|
10403
|
-
if (await isDirectory(
|
|
10404
|
-
const parent =
|
|
10523
|
+
if (await isDirectory(path5.join(current, SDD_DIR))) return current;
|
|
10524
|
+
const parent = path5.dirname(current);
|
|
10405
10525
|
if (parent === current) return void 0;
|
|
10406
10526
|
current = parent;
|
|
10407
10527
|
}
|
|
10408
10528
|
return void 0;
|
|
10409
10529
|
}
|
|
10410
10530
|
async function loadApprovals(sddDir) {
|
|
10411
|
-
const file =
|
|
10531
|
+
const file = path5.join(sddDir, "approvals.yaml");
|
|
10412
10532
|
const raw = await readTextIfExists(file);
|
|
10413
10533
|
if (raw === void 0) return { byArtifact: /* @__PURE__ */ new Map(), diagnostics: [] };
|
|
10414
10534
|
const parsed = parseApprovals(raw, file);
|
|
10415
10535
|
const map = /* @__PURE__ */ new Map();
|
|
10416
10536
|
for (const a of parsed.approvals?.approvals ?? []) {
|
|
10417
|
-
map.set(a.artifact.split(
|
|
10537
|
+
map.set(a.artifact.split(path5.sep).join("/"), { hash: a.artifactHash, by: a.approvedBy, at: a.approvedAt });
|
|
10418
10538
|
}
|
|
10419
10539
|
return { byArtifact: map, diagnostics: parsed.diagnostics };
|
|
10420
10540
|
}
|
|
10421
10541
|
async function loadSpecs(sddDir) {
|
|
10422
|
-
const specsDir =
|
|
10542
|
+
const specsDir = path5.join(sddDir, "specs");
|
|
10423
10543
|
const domains = await listDirs(specsDir);
|
|
10424
10544
|
const out = [];
|
|
10425
10545
|
for (const domain of domains) {
|
|
10426
|
-
const file =
|
|
10546
|
+
const file = path5.join(specsDir, domain, "spec.md");
|
|
10427
10547
|
if (!await exists(file)) continue;
|
|
10428
10548
|
const content = await readText(file);
|
|
10429
10549
|
out.push({ domain, path: file, spec: parseSpecFile(content, file) });
|
|
@@ -10431,10 +10551,10 @@ async function loadSpecs(sddDir) {
|
|
|
10431
10551
|
return out;
|
|
10432
10552
|
}
|
|
10433
10553
|
async function loadChange(root, slug, relDir) {
|
|
10434
|
-
const dir =
|
|
10554
|
+
const dir = path5.join(root, SDD_DIR, "changes", relDir ?? slug);
|
|
10435
10555
|
const diagnostics = [];
|
|
10436
10556
|
const change = { slug, dir, diagnostics };
|
|
10437
|
-
const metaFile =
|
|
10557
|
+
const metaFile = path5.join(dir, "meta.yaml");
|
|
10438
10558
|
const metaRaw = await readTextIfExists(metaFile);
|
|
10439
10559
|
if (metaRaw === void 0) {
|
|
10440
10560
|
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" }));
|
|
@@ -10443,7 +10563,7 @@ async function loadChange(root, slug, relDir) {
|
|
|
10443
10563
|
diagnostics.push(...parsed.diagnostics);
|
|
10444
10564
|
if (parsed.meta) change.meta = parsed.meta;
|
|
10445
10565
|
}
|
|
10446
|
-
const deltaFile =
|
|
10566
|
+
const deltaFile = path5.join(dir, "spec.md");
|
|
10447
10567
|
const deltaRaw = await readTextIfExists(deltaFile);
|
|
10448
10568
|
if (deltaRaw !== void 0) {
|
|
10449
10569
|
change.delta = parseDelta(deltaRaw, deltaFile);
|
|
@@ -10451,41 +10571,42 @@ async function loadChange(root, slug, relDir) {
|
|
|
10451
10571
|
} else {
|
|
10452
10572
|
diagnostics.push(diag("ATLAS-FILES-002", "warning", `El cambio "${slug}" no tiene spec.md (delta)`, { path: deltaFile }));
|
|
10453
10573
|
}
|
|
10454
|
-
const planFile =
|
|
10574
|
+
const planFile = path5.join(dir, "plan.md");
|
|
10455
10575
|
if (await exists(planFile)) change.planPath = planFile;
|
|
10456
|
-
const reviewFile =
|
|
10576
|
+
const reviewFile = path5.join(dir, "review.md");
|
|
10457
10577
|
if (await exists(reviewFile)) change.reviewPath = reviewFile;
|
|
10458
|
-
const presentationFile =
|
|
10578
|
+
const presentationFile = path5.join(dir, "presentation", "index.html");
|
|
10459
10579
|
if (await exists(presentationFile)) change.presentationPath = presentationFile;
|
|
10460
|
-
const tasksFile =
|
|
10580
|
+
const tasksFile = path5.join(dir, "tasks.md");
|
|
10461
10581
|
const tasksRaw = await readTextIfExists(tasksFile);
|
|
10462
10582
|
if (tasksRaw !== void 0) {
|
|
10463
10583
|
change.tasks = parseTasksFile(tasksRaw, tasksFile);
|
|
10464
10584
|
diagnostics.push(...change.tasks.diagnostics);
|
|
10465
10585
|
}
|
|
10466
|
-
const verifyFile =
|
|
10586
|
+
const verifyFile = path5.join(dir, "verify.md");
|
|
10467
10587
|
const verifyRaw = await readTextIfExists(verifyFile);
|
|
10468
10588
|
if (verifyRaw !== void 0) {
|
|
10469
10589
|
change.verify = parseVerifyFile(verifyRaw, verifyFile);
|
|
10470
10590
|
diagnostics.push(...change.verify.diagnostics);
|
|
10471
10591
|
}
|
|
10472
|
-
const fixFile =
|
|
10592
|
+
const fixFile = path5.join(dir, "fix.md");
|
|
10473
10593
|
const fixRaw = await readTextIfExists(fixFile);
|
|
10474
10594
|
if (fixRaw !== void 0) {
|
|
10475
10595
|
change.fix = parseVerifyFile(fixRaw, fixFile);
|
|
10476
10596
|
diagnostics.push(...change.fix.diagnostics);
|
|
10597
|
+
change.fixCovers = parseFixCovers(fixRaw);
|
|
10477
10598
|
}
|
|
10478
|
-
const mockupManifest =
|
|
10599
|
+
const mockupManifest = path5.join(dir, "mockups", "manifest.yaml");
|
|
10479
10600
|
if (await exists(mockupManifest)) change.mockupManifestPath = mockupManifest;
|
|
10480
10601
|
return change;
|
|
10481
10602
|
}
|
|
10482
10603
|
async function listChangeSlugs(root, opts = {}) {
|
|
10483
|
-
const changesDir =
|
|
10604
|
+
const changesDir = path5.join(root, SDD_DIR, "changes");
|
|
10484
10605
|
const dirs = await listDirs(changesDir);
|
|
10485
10606
|
return dirs.filter((d) => opts.includeArchived ? true : d !== "archive");
|
|
10486
10607
|
}
|
|
10487
10608
|
async function loadWorkspace(root) {
|
|
10488
|
-
const sddDir =
|
|
10609
|
+
const sddDir = path5.join(root, SDD_DIR);
|
|
10489
10610
|
const { config, diagnostics: configDiags } = await loadConfig(sddDir);
|
|
10490
10611
|
const diagnostics = [...configDiags];
|
|
10491
10612
|
const specs = await loadSpecs(sddDir);
|
|
@@ -10498,20 +10619,20 @@ async function loadWorkspace(root) {
|
|
|
10498
10619
|
diagnostics.push(...change.diagnostics);
|
|
10499
10620
|
}
|
|
10500
10621
|
const archived = [];
|
|
10501
|
-
const archivedDir =
|
|
10622
|
+
const archivedDir = path5.join(sddDir, "changes", "archive");
|
|
10502
10623
|
if (await isDirectory(archivedDir)) {
|
|
10503
10624
|
for (const entry of await listDirs(archivedDir)) {
|
|
10504
|
-
archived.push(await loadChange(root, entry.replace(/^\d{4}-\d{2}-/, ""),
|
|
10625
|
+
archived.push(await loadChange(root, entry.replace(/^\d{4}-\d{2}-/, ""), path5.join("archive", entry)));
|
|
10505
10626
|
}
|
|
10506
10627
|
}
|
|
10507
10628
|
return { workspace: { root, sddDir, specs, changes, archived, diagnostics }, config };
|
|
10508
10629
|
}
|
|
10509
10630
|
async function ensureSddDirs(sddDir) {
|
|
10510
10631
|
const created = [];
|
|
10511
|
-
const dirs = ["specs", "changes", "runs", "metrics",
|
|
10632
|
+
const dirs = ["specs", "changes", "runs", "metrics", "fixes", path5.join("profiles", "custom")];
|
|
10512
10633
|
const { ensureDir: ensureDir2 } = await import("./fsx-VF2P7ALA-XKEWTMDH.js");
|
|
10513
10634
|
for (const d of dirs) {
|
|
10514
|
-
const full =
|
|
10635
|
+
const full = path5.join(sddDir, d);
|
|
10515
10636
|
if (!await exists(full)) {
|
|
10516
10637
|
await ensureDir2(full);
|
|
10517
10638
|
created.push(full);
|
|
@@ -10679,6 +10800,10 @@ notes: 12/12 casos
|
|
|
10679
10800
|
|
|
10680
10801
|
## Evidencia
|
|
10681
10802
|
|
|
10803
|
+
<!-- Opcional: declara los requisitos que este fix afecta (aparecen en su trazabilidad).
|
|
10804
|
+
Cubre: REQ-DOMINIO-001
|
|
10805
|
+
-->
|
|
10806
|
+
|
|
10682
10807
|
<!-- Registra la evidencia real con:
|
|
10683
10808
|
satlas verify <slug> --file fix --scenario REQ-DOMINIO-001-S1 --command "<comando>" --by "<nombre>"
|
|
10684
10809
|
o, si es manual:
|
|
@@ -10802,6 +10927,10 @@ by: your-name
|
|
|
10802
10927
|
## Rollback
|
|
10803
10928
|
## Evidence
|
|
10804
10929
|
|
|
10930
|
+
<!-- Optional: declare the requirements this fix affects (they show up in its traceability).
|
|
10931
|
+
Cubre: REQ-DOMAIN-001
|
|
10932
|
+
-->
|
|
10933
|
+
|
|
10805
10934
|
<!-- Register real evidence with:
|
|
10806
10935
|
satlas verify <slug> --file fix --scenario REQ-DOMAIN-001-S1 --command "<command>" --by "<name>"
|
|
10807
10936
|
-->
|
|
@@ -10826,7 +10955,7 @@ function changeMetaYaml(meta) {
|
|
|
10826
10955
|
if (meta.owner) doc["owner"] = meta.owner;
|
|
10827
10956
|
if (meta.mockups) doc["mockups"] = meta.mockups;
|
|
10828
10957
|
return `# Estado del cambio. La fase se DERIVA de los artefactos; aqu\xED solo hechos.
|
|
10829
|
-
` +
|
|
10958
|
+
` + stringifyYaml4(doc, { lineWidth: 120 });
|
|
10830
10959
|
}
|
|
10831
10960
|
function indexMarkdown(input) {
|
|
10832
10961
|
const es = input.language !== "en";
|
|
@@ -10840,6 +10969,15 @@ function indexMarkdown(input) {
|
|
|
10840
10969
|
if (input.specs.length === 0) lines.push(es ? "_Sin specs todav\xEDa._" : "_No specs yet._");
|
|
10841
10970
|
for (const spec of input.specs) lines.push(`- \`${spec.domain}\` \u2014 ${spec.requirements} ${es ? "requisitos" : "requirements"}`);
|
|
10842
10971
|
lines.push("");
|
|
10972
|
+
lines.push(es ? "## Fixes vivos" : "## Living fixes");
|
|
10973
|
+
lines.push("");
|
|
10974
|
+
const fixes = input.fixes ?? [];
|
|
10975
|
+
if (fixes.length === 0) lines.push(es ? "_Sin fixes archivados todav\xEDa._" : "_No archived fixes yet._");
|
|
10976
|
+
for (const fix of fixes) {
|
|
10977
|
+
const parts = [fix.date, fix.domain ? `\`${fix.domain}\`` : "", `\`${fix.slug}\``, fix.result].filter((part) => part !== "");
|
|
10978
|
+
lines.push(`- ${parts.join(" \xB7 ")}`);
|
|
10979
|
+
}
|
|
10980
|
+
lines.push("");
|
|
10843
10981
|
lines.push(es ? "## Cambios activos" : "## Active changes");
|
|
10844
10982
|
lines.push("");
|
|
10845
10983
|
if (input.changes.length === 0) lines.push(es ? "_Sin cambios activos._" : "_No active changes._");
|
|
@@ -10991,8 +11129,8 @@ function issueBody(input) {
|
|
|
10991
11129
|
lines.push(`- Siguiente: \`${input.next}\` \u2014 ${input.nextDescription}`);
|
|
10992
11130
|
lines.push("");
|
|
10993
11131
|
lines.push("### Artefactos");
|
|
10994
|
-
lines.push(`- Spec: \`${
|
|
10995
|
-
if (change.planPath) lines.push(`- Plan: \`${
|
|
11132
|
+
lines.push(`- Spec: \`${path6.posix.join(".sdd", "changes", change.slug, "spec.md")}\``);
|
|
11133
|
+
if (change.planPath) lines.push(`- Plan: \`${path6.posix.join(".sdd", "changes", change.slug, "plan.md")}\``);
|
|
10996
11134
|
if (change.tasks) lines.push(`- Tareas: ${change.tasks.counts.done}/${change.tasks.counts.total}`);
|
|
10997
11135
|
if (change.verify) lines.push(`- Evidencia registrada: ${change.verify.evidence.length}`);
|
|
10998
11136
|
lines.push("");
|
|
@@ -11056,10 +11194,10 @@ async function issueLabels(root, number, runner = defaultRunner) {
|
|
|
11056
11194
|
}
|
|
11057
11195
|
}
|
|
11058
11196
|
async function patchChangeMeta(root, slug, patch) {
|
|
11059
|
-
const file =
|
|
11197
|
+
const file = path6.join(path6.resolve(root), ".sdd", "changes", slug, "meta.yaml");
|
|
11060
11198
|
const raw = await readTextIfExists(file);
|
|
11061
11199
|
if (raw === void 0) {
|
|
11062
|
-
return { path: file, diagnostics: [diag("ATLAS-GH-007", "error", `No existe ${
|
|
11200
|
+
return { path: file, diagnostics: [diag("ATLAS-GH-007", "error", `No existe ${path6.relative(root, file)}`)] };
|
|
11063
11201
|
}
|
|
11064
11202
|
let data;
|
|
11065
11203
|
try {
|
|
@@ -11113,7 +11251,7 @@ async function approveFromGithub(opts) {
|
|
|
11113
11251
|
const { signApproval: signApproval2 } = await import("./approvals-2PO6223I-DENKLEXU.js");
|
|
11114
11252
|
const signed = await signApproval2({
|
|
11115
11253
|
root: opts.root,
|
|
11116
|
-
artifact:
|
|
11254
|
+
artifact: path6.posix.join("changes", opts.slug, "spec.md"),
|
|
11117
11255
|
by,
|
|
11118
11256
|
channel: "tracker",
|
|
11119
11257
|
note: `Aprobado v\xEDa GitHub issue #${number} (etiqueta "${label}")`
|
|
@@ -11208,10 +11346,10 @@ function buildEvidenceYaml(evidence) {
|
|
|
11208
11346
|
return lines.join("\n");
|
|
11209
11347
|
}
|
|
11210
11348
|
async function recordEvidence(opts) {
|
|
11211
|
-
const root =
|
|
11349
|
+
const root = path7.resolve(opts.root);
|
|
11212
11350
|
const slug = opts.slug;
|
|
11213
11351
|
const fileKind = opts.file ?? "verify";
|
|
11214
|
-
const file =
|
|
11352
|
+
const file = path7.join(root, ".sdd", "changes", slug, fileKind === "fix" ? "fix.md" : "verify.md");
|
|
11215
11353
|
const diagnostics = [];
|
|
11216
11354
|
const scenario = opts.scenario.toUpperCase();
|
|
11217
11355
|
if (!SCENARIO_ID_RE.test(scenario)) {
|
|
@@ -11301,7 +11439,7 @@ function generateRunId(now = /* @__PURE__ */ new Date()) {
|
|
|
11301
11439
|
return `${iso.slice(0, 8)}-${iso.slice(8, 14)}-${randomBytes(3).toString("hex")}`;
|
|
11302
11440
|
}
|
|
11303
11441
|
function runsDir(root) {
|
|
11304
|
-
return
|
|
11442
|
+
return path8.join(path8.resolve(root), ".sdd", "runs");
|
|
11305
11443
|
}
|
|
11306
11444
|
async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Date()) {
|
|
11307
11445
|
const runId = generateRunId(now);
|
|
@@ -11314,25 +11452,25 @@ async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Da
|
|
|
11314
11452
|
updatedAt: localStamp(now)
|
|
11315
11453
|
};
|
|
11316
11454
|
if (inputs) state.inputs = inputs;
|
|
11317
|
-
const dir =
|
|
11455
|
+
const dir = path8.join(runsDir(root), runId);
|
|
11318
11456
|
await ensureDir(dir);
|
|
11319
|
-
await writeText(
|
|
11457
|
+
await writeText(path8.join(dir, "state.json"), `${JSON.stringify(state, null, 2)}
|
|
11320
11458
|
`);
|
|
11321
|
-
await writeText(
|
|
11459
|
+
await writeText(path8.join(dir, "events.jsonl"), "");
|
|
11322
11460
|
await appendRunEvent(root, runId, "run_started", { slug, phase });
|
|
11323
11461
|
return state;
|
|
11324
11462
|
}
|
|
11325
11463
|
async function appendRunEvent(root, runId, type, data, now = /* @__PURE__ */ new Date()) {
|
|
11326
11464
|
const event = { eventId: randomBytes(4).toString("hex"), at: localStamp(now), type };
|
|
11327
11465
|
if (data) event.data = data;
|
|
11328
|
-
const file =
|
|
11466
|
+
const file = path8.join(runsDir(root), runId, "events.jsonl");
|
|
11329
11467
|
const previous = await readTextIfExists(file) ?? "";
|
|
11330
11468
|
await writeText(file, `${previous}${JSON.stringify(event)}
|
|
11331
11469
|
`);
|
|
11332
11470
|
return event;
|
|
11333
11471
|
}
|
|
11334
11472
|
async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Date()) {
|
|
11335
|
-
const file =
|
|
11473
|
+
const file = path8.join(runsDir(root), runId, "state.json");
|
|
11336
11474
|
const raw = await readTextIfExists(file);
|
|
11337
11475
|
if (raw === void 0) return;
|
|
11338
11476
|
const state = JSON.parse(raw);
|
|
@@ -11342,13 +11480,13 @@ async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Da
|
|
|
11342
11480
|
`);
|
|
11343
11481
|
}
|
|
11344
11482
|
async function readRun(root, runId) {
|
|
11345
|
-
const dir =
|
|
11346
|
-
const stateRaw = await readTextIfExists(
|
|
11483
|
+
const dir = path8.join(runsDir(root), runId);
|
|
11484
|
+
const stateRaw = await readTextIfExists(path8.join(dir, "state.json"));
|
|
11347
11485
|
if (stateRaw === void 0) {
|
|
11348
11486
|
return { diagnostics: [diag("ATLAS-RUN-001", "error", `No existe el run ${runId}`, { path: dir })] };
|
|
11349
11487
|
}
|
|
11350
11488
|
const state = JSON.parse(stateRaw);
|
|
11351
|
-
const eventsRaw = await readTextIfExists(
|
|
11489
|
+
const eventsRaw = await readTextIfExists(path8.join(dir, "events.jsonl")) ?? "";
|
|
11352
11490
|
const events = [];
|
|
11353
11491
|
for (const line of eventsRaw.split("\n")) {
|
|
11354
11492
|
if (line.trim() === "") continue;
|
|
@@ -11365,7 +11503,7 @@ async function listRuns(root, filter = {}) {
|
|
|
11365
11503
|
const ids = await listDirs(dir);
|
|
11366
11504
|
const out = [];
|
|
11367
11505
|
for (const id of ids) {
|
|
11368
|
-
const raw = await readTextIfExists(
|
|
11506
|
+
const raw = await readTextIfExists(path8.join(dir, id, "state.json"));
|
|
11369
11507
|
if (raw === void 0) continue;
|
|
11370
11508
|
try {
|
|
11371
11509
|
const state = JSON.parse(raw);
|
|
@@ -11400,20 +11538,20 @@ var mockupManifestSchema = z4.object({
|
|
|
11400
11538
|
});
|
|
11401
11539
|
var TOKEN_CANDIDATES = ["design/tokens.json", "DESIGN.md", "design/DESIGN.md", ".sdd/design/tokens.json"];
|
|
11402
11540
|
function mockupsDir(root, slug) {
|
|
11403
|
-
return
|
|
11541
|
+
return path9.join(path9.resolve(root), ".sdd", "changes", slug, "mockups");
|
|
11404
11542
|
}
|
|
11405
11543
|
async function tokensFile(root) {
|
|
11406
11544
|
for (const candidate of TOKEN_CANDIDATES) {
|
|
11407
|
-
const abs =
|
|
11545
|
+
const abs = path9.join(root, candidate);
|
|
11408
11546
|
if (await exists(abs)) return candidate;
|
|
11409
11547
|
}
|
|
11410
11548
|
return void 0;
|
|
11411
11549
|
}
|
|
11412
11550
|
async function computeInputsHash(root, change) {
|
|
11413
|
-
const deltaPath =
|
|
11551
|
+
const deltaPath = path9.join(change.dir, "spec.md");
|
|
11414
11552
|
const delta = await readTextIfExists(deltaPath) ?? "";
|
|
11415
11553
|
const tokens = await tokensFile(root);
|
|
11416
|
-
const tokensContent = tokens ? await readTextIfExists(
|
|
11554
|
+
const tokensContent = tokens ? await readTextIfExists(path9.join(root, tokens)) ?? "" : "";
|
|
11417
11555
|
return artifactHash(`${delta}
|
|
11418
11556
|
---tokens---
|
|
11419
11557
|
${tokensContent}`);
|
|
@@ -11438,7 +11576,7 @@ function planMockups(change, platform = "web") {
|
|
|
11438
11576
|
return { platform, screens };
|
|
11439
11577
|
}
|
|
11440
11578
|
async function readMockupManifest(root, slug) {
|
|
11441
|
-
const file =
|
|
11579
|
+
const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
|
|
11442
11580
|
const raw = await readTextIfExists(file);
|
|
11443
11581
|
if (raw === void 0) return { diagnostics: [] };
|
|
11444
11582
|
let data;
|
|
@@ -11530,7 +11668,7 @@ async function checkMockups(root, slug, change) {
|
|
|
11530
11668
|
const { manifest, path: manifestPath, diagnostics } = await readMockupManifest(root, slug);
|
|
11531
11669
|
const findings = [...diagnostics];
|
|
11532
11670
|
if (!manifest || !manifestPath) {
|
|
11533
|
-
return { findings: [...findings, diag("LINT-MKP-004", "error", "No existe mockups/manifest.yaml", { path:
|
|
11671
|
+
return { findings: [...findings, diag("LINT-MKP-004", "error", "No existe mockups/manifest.yaml", { path: path9.join(dir, "manifest.yaml") })], stale: false };
|
|
11534
11672
|
}
|
|
11535
11673
|
const known = /* @__PURE__ */ new Set();
|
|
11536
11674
|
for (const req of [...change.delta?.added ?? [], ...change.delta?.modified ?? []]) {
|
|
@@ -11538,12 +11676,12 @@ async function checkMockups(root, slug, change) {
|
|
|
11538
11676
|
}
|
|
11539
11677
|
findings.push(...lintMockupManifest(manifest, known, manifestPath));
|
|
11540
11678
|
for (const screen of manifest.screens) {
|
|
11541
|
-
const html = await readTextIfExists(
|
|
11679
|
+
const html = await readTextIfExists(path9.join(dir, screen.file));
|
|
11542
11680
|
if (html === void 0) {
|
|
11543
|
-
findings.push(diag("ATLAS-MKP-004", "error", `Falta el archivo del mockup: ${screen.file}`, { path:
|
|
11681
|
+
findings.push(diag("ATLAS-MKP-004", "error", `Falta el archivo del mockup: ${screen.file}`, { path: path9.join(dir, screen.file) }));
|
|
11544
11682
|
continue;
|
|
11545
11683
|
}
|
|
11546
|
-
findings.push(...lintMockupHtml(html,
|
|
11684
|
+
findings.push(...lintMockupHtml(html, path9.join(dir, screen.file)));
|
|
11547
11685
|
}
|
|
11548
11686
|
const inputsHash = await computeInputsHash(root, change);
|
|
11549
11687
|
const stale = manifest.inputsHash !== void 0 && manifest.inputsHash !== inputsHash;
|
|
@@ -11558,7 +11696,7 @@ async function checkMockups(root, slug, change) {
|
|
|
11558
11696
|
return { findings, stale, manifest, manifestPath };
|
|
11559
11697
|
}
|
|
11560
11698
|
async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date()) {
|
|
11561
|
-
const file =
|
|
11699
|
+
const file = path9.join(mockupsDir(root, slug), "plan.yaml");
|
|
11562
11700
|
if (await exists(file)) return file;
|
|
11563
11701
|
const doc = {
|
|
11564
11702
|
schema_version: 1,
|
|
@@ -11566,11 +11704,11 @@ async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date(
|
|
|
11566
11704
|
platform: plan.platform,
|
|
11567
11705
|
screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints }))
|
|
11568
11706
|
};
|
|
11569
|
-
await writeText(file,
|
|
11707
|
+
await writeText(file, stringifyYaml5(doc, { lineWidth: 120 }));
|
|
11570
11708
|
return file;
|
|
11571
11709
|
}
|
|
11572
11710
|
async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PURE__ */ new Date()) {
|
|
11573
|
-
const file =
|
|
11711
|
+
const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
|
|
11574
11712
|
if (await exists(file)) return file;
|
|
11575
11713
|
const doc = {
|
|
11576
11714
|
schema_version: 1,
|
|
@@ -11582,16 +11720,16 @@ async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PUR
|
|
|
11582
11720
|
screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints })),
|
|
11583
11721
|
screenshots: []
|
|
11584
11722
|
};
|
|
11585
|
-
await writeText(file,
|
|
11723
|
+
await writeText(file, stringifyYaml5(doc, { lineWidth: 120 }));
|
|
11586
11724
|
return file;
|
|
11587
11725
|
}
|
|
11588
11726
|
async function updateMockupScreenshots(root, slug, screenshots) {
|
|
11589
|
-
const file =
|
|
11727
|
+
const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
|
|
11590
11728
|
const raw = await readTextIfExists(file);
|
|
11591
11729
|
if (raw === void 0) return void 0;
|
|
11592
11730
|
const data = parseYaml6(raw) ?? {};
|
|
11593
11731
|
data["screenshots"] = screenshots;
|
|
11594
|
-
await writeText(file,
|
|
11732
|
+
await writeText(file, stringifyYaml5(data, { lineWidth: 120 }));
|
|
11595
11733
|
return file;
|
|
11596
11734
|
}
|
|
11597
11735
|
async function captureMockups(root, slug, manifest, now = /* @__PURE__ */ new Date()) {
|
|
@@ -11616,9 +11754,9 @@ async function captureMockups(root, slug, manifest, now = /* @__PURE__ */ new Da
|
|
|
11616
11754
|
const page2 = await browser.newPage();
|
|
11617
11755
|
for (const bp of screen.breakpoints && screen.breakpoints.length > 0 ? screen.breakpoints : [1440]) {
|
|
11618
11756
|
await page2.setViewportSize({ width: bp, height: 900 });
|
|
11619
|
-
await page2.goto(`file://${
|
|
11620
|
-
const rel =
|
|
11621
|
-
await page2.screenshot({ path:
|
|
11757
|
+
await page2.goto(`file://${path9.join(dir, screen.file).split(path9.sep).join("/")}`);
|
|
11758
|
+
const rel = path9.posix.join("screens", `${screen.id}-${bp}.png`);
|
|
11759
|
+
await page2.screenshot({ path: path9.join(dir, rel) });
|
|
11622
11760
|
screenshots.push(rel);
|
|
11623
11761
|
}
|
|
11624
11762
|
await page2.close();
|
|
@@ -11634,7 +11772,7 @@ async function mockupsReady(root, slug, change) {
|
|
|
11634
11772
|
return Boolean(check.manifest && check.manifest.screens.length > 0 && !check.stale);
|
|
11635
11773
|
}
|
|
11636
11774
|
async function setMockupRequirement(root, slug, value) {
|
|
11637
|
-
const file =
|
|
11775
|
+
const file = path9.join(root, ".sdd", "changes", slug, "meta.yaml");
|
|
11638
11776
|
const raw = await readTextIfExists(file) ?? `schema_version: 1
|
|
11639
11777
|
slug: ${slug}
|
|
11640
11778
|
lane: standard
|
|
@@ -11864,12 +12002,12 @@ ${texts.scenarios}`);
|
|
|
11864
12002
|
}
|
|
11865
12003
|
}
|
|
11866
12004
|
async function loadProjectPacks(sddDir) {
|
|
11867
|
-
const dir =
|
|
12005
|
+
const dir = path10.join(sddDir, "packs");
|
|
11868
12006
|
const diagnostics = [];
|
|
11869
12007
|
const packs = [];
|
|
11870
12008
|
for (const entry of await listDir(dir)) {
|
|
11871
12009
|
if (!/\.ya?ml$/i.test(entry)) continue;
|
|
11872
|
-
const file =
|
|
12010
|
+
const file = path10.join(dir, entry);
|
|
11873
12011
|
const raw = await readTextIfExists(file);
|
|
11874
12012
|
if (raw === void 0) continue;
|
|
11875
12013
|
let data;
|
|
@@ -11956,7 +12094,7 @@ function packFindings(evaluations, change) {
|
|
|
11956
12094
|
}
|
|
11957
12095
|
var UI_DOMAINS = /* @__PURE__ */ new Set(["frontend", "mobile", "fullstack"]);
|
|
11958
12096
|
async function runAnalyze(opts) {
|
|
11959
|
-
const root =
|
|
12097
|
+
const root = path11.resolve(opts.root);
|
|
11960
12098
|
const { workspace, config } = await loadWorkspace(root);
|
|
11961
12099
|
const change = workspace.changes.find((c) => c.slug === opts.slug);
|
|
11962
12100
|
if (!change) {
|
|
@@ -11974,7 +12112,7 @@ async function runAnalyze(opts) {
|
|
|
11974
12112
|
for (const req of spec.spec.requirements) living.set(req.id, req);
|
|
11975
12113
|
}
|
|
11976
12114
|
if (change.delta) {
|
|
11977
|
-
findings.push(...lintDelta(change.delta, living,
|
|
12115
|
+
findings.push(...lintDelta(change.delta, living, path11.join(change.dir, "spec.md"), { language: config.spec.language }));
|
|
11978
12116
|
}
|
|
11979
12117
|
const trace = checkTrace({ specs: workspace.specs, change, requireEvidence: false });
|
|
11980
12118
|
findings.push(...trace.findings);
|
|
@@ -12023,7 +12161,7 @@ async function runAnalyze(opts) {
|
|
|
12023
12161
|
if (mockups) result.mockups = mockups;
|
|
12024
12162
|
if (packs) result.packs = packs;
|
|
12025
12163
|
if (opts.write !== false) {
|
|
12026
|
-
const file =
|
|
12164
|
+
const file = path11.join(change.dir, "analyze.md");
|
|
12027
12165
|
await writeText(file, renderAnalyze(change, result, localStamp(opts.now)));
|
|
12028
12166
|
result.path = file;
|
|
12029
12167
|
}
|
|
@@ -12065,7 +12203,7 @@ function renderAnalyze(change, result, generatedAt) {
|
|
|
12065
12203
|
return lines.join("\n");
|
|
12066
12204
|
}
|
|
12067
12205
|
async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
|
|
12068
|
-
const resolved =
|
|
12206
|
+
const resolved = path12.resolve(root);
|
|
12069
12207
|
const { workspace, config } = await loadWorkspace(resolved);
|
|
12070
12208
|
const approvals = await loadApprovals(workspace.sddDir);
|
|
12071
12209
|
const living = /* @__PURE__ */ new Map();
|
|
@@ -12080,7 +12218,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
|
|
|
12080
12218
|
const deltaContent = await readDelta(change);
|
|
12081
12219
|
const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent);
|
|
12082
12220
|
const findings = [];
|
|
12083
|
-
if (change.delta) findings.push(...lintDelta(change.delta, living,
|
|
12221
|
+
if (change.delta) findings.push(...lintDelta(change.delta, living, path12.join(change.dir, "spec.md"), { language: config.spec.language }));
|
|
12084
12222
|
const trace = checkTrace({
|
|
12085
12223
|
specs: workspace.specs,
|
|
12086
12224
|
change,
|
|
@@ -12145,7 +12283,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
|
|
|
12145
12283
|
attention.push({ slug: change.slug, kind: "missing-evidence", detail: `evidencia ${change.scenariosPassed}/${change.scenariosTotal}` });
|
|
12146
12284
|
}
|
|
12147
12285
|
}
|
|
12148
|
-
const archiveDir =
|
|
12286
|
+
const archiveDir = path12.join(workspace.sddDir, "changes", "archive");
|
|
12149
12287
|
const archivedEntries = await listDirs(archiveDir);
|
|
12150
12288
|
const throughputByMonth = {};
|
|
12151
12289
|
for (const entry of archivedEntries) {
|
|
@@ -12183,10 +12321,10 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
|
|
|
12183
12321
|
}
|
|
12184
12322
|
async function readDelta(change) {
|
|
12185
12323
|
const { readTextIfExists: readTextIfExists2 } = await import("./fsx-VF2P7ALA-XKEWTMDH.js");
|
|
12186
|
-
return readTextIfExists2(
|
|
12324
|
+
return readTextIfExists2(path12.join(change.dir, "spec.md"));
|
|
12187
12325
|
}
|
|
12188
12326
|
async function generatePresentation(opts) {
|
|
12189
|
-
const root =
|
|
12327
|
+
const root = path13.resolve(opts.root);
|
|
12190
12328
|
const { workspace, config } = await loadWorkspace(root);
|
|
12191
12329
|
const language = config.project.language;
|
|
12192
12330
|
const change = workspace.changes.find((c) => c.slug === opts.slug);
|
|
@@ -12196,17 +12334,17 @@ async function generatePresentation(opts) {
|
|
|
12196
12334
|
if (!change.delta) {
|
|
12197
12335
|
return { slug: opts.slug, diagnostics: [diag("ATLAS-PRESENT-001", "error", `El cambio "${opts.slug}" no tiene spec.md (delta)`)] };
|
|
12198
12336
|
}
|
|
12199
|
-
const deltaPath =
|
|
12337
|
+
const deltaPath = path13.join(change.dir, "spec.md");
|
|
12200
12338
|
const deltaContent = await readTextIfExists(deltaPath) ?? "";
|
|
12201
12339
|
const hash = artifactHash(deltaContent);
|
|
12202
12340
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
12203
|
-
const presentationDir =
|
|
12204
|
-
const mockupsCopyDir =
|
|
12341
|
+
const presentationDir = path13.join(change.dir, "presentation");
|
|
12342
|
+
const mockupsCopyDir = path13.join(presentationDir, "mockups");
|
|
12205
12343
|
await ensureDir(presentationDir);
|
|
12206
12344
|
const mockupInfo = await copyMockups(root, change, mockupsCopyDir);
|
|
12207
|
-
const proposalRaw = await readTextIfExists(
|
|
12345
|
+
const proposalRaw = await readTextIfExists(path13.join(change.dir, "proposal.md"));
|
|
12208
12346
|
const proposalBody = proposalRaw ? parseFrontmatter(proposalRaw).body : "";
|
|
12209
|
-
const approvals = await loadApprovals(
|
|
12347
|
+
const approvals = await loadApprovals(path13.join(root, ".sdd"));
|
|
12210
12348
|
const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent);
|
|
12211
12349
|
const labels = labelsFor(language);
|
|
12212
12350
|
const html = page({
|
|
@@ -12227,7 +12365,7 @@ async function generatePresentation(opts) {
|
|
|
12227
12365
|
approveHint: `satlas approve ${change.slug} --by "<nombre>" --channel presentation`,
|
|
12228
12366
|
...approval.status === "valid" && approval.approvedBy ? { approval: { by: approval.approvedBy, at: approval.approvedAt ?? "" } } : {}
|
|
12229
12367
|
});
|
|
12230
|
-
const outFile =
|
|
12368
|
+
const outFile = path13.join(presentationDir, "index.html");
|
|
12231
12369
|
await writeText(outFile, html);
|
|
12232
12370
|
return { slug: change.slug, path: outFile, hash, diagnostics: mockupInfo.diagnostics };
|
|
12233
12371
|
}
|
|
@@ -12239,12 +12377,12 @@ async function copyMockups(root, change, destDir) {
|
|
|
12239
12377
|
await ensureDir(destDir);
|
|
12240
12378
|
const screens = [];
|
|
12241
12379
|
for (const screen of manifest.screens) {
|
|
12242
|
-
const src =
|
|
12380
|
+
const src = path13.join(dir, screen.file);
|
|
12243
12381
|
if (!await exists(src)) {
|
|
12244
12382
|
diagnostics.push(diag("ATLAS-PRESENT-002", "warning", `El mockup ${screen.file} no existe y no se incluir\xE1`, { path: src }));
|
|
12245
12383
|
continue;
|
|
12246
12384
|
}
|
|
12247
|
-
await copyFile(src,
|
|
12385
|
+
await copyFile(src, path13.join(destDir, screen.file));
|
|
12248
12386
|
screens.push({
|
|
12249
12387
|
id: screen.id,
|
|
12250
12388
|
title: screen.title ?? screen.id,
|
|
@@ -12254,13 +12392,13 @@ async function copyMockups(root, change, destDir) {
|
|
|
12254
12392
|
});
|
|
12255
12393
|
}
|
|
12256
12394
|
const screenshots = [];
|
|
12257
|
-
const shotsDir =
|
|
12395
|
+
const shotsDir = path13.join(dir, "screens");
|
|
12258
12396
|
if (await exists(shotsDir)) {
|
|
12259
|
-
await ensureDir(
|
|
12397
|
+
await ensureDir(path13.join(destDir, "screens"));
|
|
12260
12398
|
const { listDir: listDir4 } = await import("./fsx-VF2P7ALA-XKEWTMDH.js");
|
|
12261
12399
|
for (const file of await listDir4(shotsDir)) {
|
|
12262
12400
|
if (!/\.(png|jpe?g|webp)$/i.test(file)) continue;
|
|
12263
|
-
await copyFile(
|
|
12401
|
+
await copyFile(path13.join(shotsDir, file), path13.join(destDir, "screens", file));
|
|
12264
12402
|
screenshots.push(`mockups/screens/${file}`);
|
|
12265
12403
|
}
|
|
12266
12404
|
}
|
|
@@ -12476,11 +12614,11 @@ var SOURCE_EXT = /* @__PURE__ */ new Set([
|
|
|
12476
12614
|
".groovy"
|
|
12477
12615
|
]);
|
|
12478
12616
|
async function adoptWorkspace(opts) {
|
|
12479
|
-
const root =
|
|
12617
|
+
const root = path14.resolve(opts.root);
|
|
12480
12618
|
const dryRun = opts.dryRun ?? false;
|
|
12481
12619
|
const diagnostics = [];
|
|
12482
|
-
const sddDir =
|
|
12483
|
-
if (!await exists(
|
|
12620
|
+
const sddDir = path14.join(root, ".sdd");
|
|
12621
|
+
if (!await exists(path14.join(sddDir, "config.yaml"))) {
|
|
12484
12622
|
return {
|
|
12485
12623
|
root,
|
|
12486
12624
|
domains: [],
|
|
@@ -12491,8 +12629,8 @@ async function adoptWorkspace(opts) {
|
|
|
12491
12629
|
}
|
|
12492
12630
|
const { config } = await loadConfig(sddDir);
|
|
12493
12631
|
const files = await walkFiles(root, { skipDirs: [...SKIP_DIRS] });
|
|
12494
|
-
const inventoried = files.map((f) => toPosix(
|
|
12495
|
-
const relative = inventoried.filter((f) => SOURCE_EXT.has(
|
|
12632
|
+
const inventoried = files.map((f) => toPosix(path14.relative(root, f.path)));
|
|
12633
|
+
const relative = inventoried.filter((f) => SOURCE_EXT.has(path14.extname(f).toLowerCase()));
|
|
12496
12634
|
if (relative.length === 0) {
|
|
12497
12635
|
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>" }));
|
|
12498
12636
|
}
|
|
@@ -12520,13 +12658,13 @@ async function adoptWorkspace(opts) {
|
|
|
12520
12658
|
const createdSpecs = [];
|
|
12521
12659
|
for (const name of domainNames.sort()) {
|
|
12522
12660
|
const domainFiles = (counts.get(name) ?? []).sort();
|
|
12523
|
-
const existingSpec = await exists(
|
|
12661
|
+
const existingSpec = await exists(path14.join(sddDir, "specs", name, "spec.md"));
|
|
12524
12662
|
domains.push({ name, files: domainFiles.length, samples: domainFiles.slice(0, 8), existingSpec });
|
|
12525
12663
|
if (existingSpec) {
|
|
12526
|
-
diagnostics.push(diag("ATLAS-ADOPT-003", "info", `La spec de "${name}" ya existe; se conserva`, { path:
|
|
12664
|
+
diagnostics.push(diag("ATLAS-ADOPT-003", "info", `La spec de "${name}" ya existe; se conserva`, { path: path14.join(sddDir, "specs", name, "spec.md") }));
|
|
12527
12665
|
continue;
|
|
12528
12666
|
}
|
|
12529
|
-
const specPath =
|
|
12667
|
+
const specPath = path14.join(sddDir, "specs", name, "spec.md");
|
|
12530
12668
|
if (!dryRun) {
|
|
12531
12669
|
await writeText(specPath, baselineSpec(name, domainFiles));
|
|
12532
12670
|
createdSpecs.push(specPath);
|
|
@@ -12534,7 +12672,7 @@ async function adoptWorkspace(opts) {
|
|
|
12534
12672
|
createdSpecs.push(specPath);
|
|
12535
12673
|
}
|
|
12536
12674
|
}
|
|
12537
|
-
const reportPath =
|
|
12675
|
+
const reportPath = path14.join(sddDir, "adopt-report.md");
|
|
12538
12676
|
if (!dryRun) {
|
|
12539
12677
|
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() }));
|
|
12540
12678
|
}
|
|
@@ -12554,12 +12692,12 @@ async function loadProjectProfiles(root, dirs) {
|
|
|
12554
12692
|
if (!await exists(dir)) continue;
|
|
12555
12693
|
out.push(...await loadProfilesFromDir(dir));
|
|
12556
12694
|
}
|
|
12557
|
-
const custom =
|
|
12695
|
+
const custom = path14.join(root, ".sdd", "profiles", "custom");
|
|
12558
12696
|
if (await exists(custom)) out.push(...await loadProfilesFromDir(custom));
|
|
12559
12697
|
return out;
|
|
12560
12698
|
}
|
|
12561
12699
|
async function fallbackDomainName(root, projectName) {
|
|
12562
|
-
const pkg = await readTextIfExists(
|
|
12700
|
+
const pkg = await readTextIfExists(path14.join(root, "package.json"));
|
|
12563
12701
|
if (pkg) {
|
|
12564
12702
|
try {
|
|
12565
12703
|
const data = JSON.parse(pkg);
|
|
@@ -12646,11 +12784,11 @@ function adoptReport(input) {
|
|
|
12646
12784
|
return lines.join("\n");
|
|
12647
12785
|
}
|
|
12648
12786
|
async function initWorkspace(opts) {
|
|
12649
|
-
const root =
|
|
12650
|
-
const sddDir =
|
|
12787
|
+
const root = path15.resolve(opts.root);
|
|
12788
|
+
const sddDir = path15.join(root, ".sdd");
|
|
12651
12789
|
const diagnostics = [];
|
|
12652
12790
|
const created = [];
|
|
12653
|
-
if (await exists(
|
|
12791
|
+
if (await exists(path15.join(sddDir, "config.yaml"))) {
|
|
12654
12792
|
return {
|
|
12655
12793
|
sddDir,
|
|
12656
12794
|
created,
|
|
@@ -12660,29 +12798,29 @@ async function initWorkspace(opts) {
|
|
|
12660
12798
|
created.push(...await ensureSddDirs(sddDir));
|
|
12661
12799
|
const language = opts.language ?? "es";
|
|
12662
12800
|
const cfg = defaultConfig({ name: opts.name, language });
|
|
12663
|
-
cfg.project.name = opts.name ??
|
|
12801
|
+
cfg.project.name = opts.name ?? path15.basename(root);
|
|
12664
12802
|
await writeConfig(sddDir, cfg);
|
|
12665
|
-
created.push(
|
|
12803
|
+
created.push(path15.join(sddDir, "config.yaml"));
|
|
12666
12804
|
const templates = templatesFor(language);
|
|
12667
|
-
await writeText(
|
|
12668
|
-
created.push(
|
|
12669
|
-
await writeText(
|
|
12670
|
-
created.push(
|
|
12805
|
+
await writeText(path15.join(sddDir, "constitution.md"), templates.constitution);
|
|
12806
|
+
created.push(path15.join(sddDir, "constitution.md"));
|
|
12807
|
+
await writeText(path15.join(sddDir, "glossary.md"), templates.glossary);
|
|
12808
|
+
created.push(path15.join(sddDir, "glossary.md"));
|
|
12671
12809
|
const detected = await detectIfPossible(root, opts.profilesDir);
|
|
12672
12810
|
if (detected) {
|
|
12673
12811
|
const yaml2 = detectionToYaml(detected, localStamp(opts.now));
|
|
12674
|
-
await writeText(
|
|
12675
|
-
created.push(
|
|
12812
|
+
await writeText(path15.join(sddDir, "profiles", "detected.yaml"), yaml2);
|
|
12813
|
+
created.push(path15.join(sddDir, "profiles", "detected.yaml"));
|
|
12676
12814
|
if (detected.best) {
|
|
12677
12815
|
diagnostics.push(diag("ATLAS-INIT-002", "info", `Stack detectado: ${detected.best.displayName} (${detected.best.score} puntos)`, { suggestion: "Revisa .sdd/profiles/detected.yaml" }));
|
|
12678
12816
|
} else {
|
|
12679
12817
|
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/" }));
|
|
12680
12818
|
}
|
|
12681
12819
|
}
|
|
12682
|
-
await writeText(
|
|
12683
|
-
created.push(
|
|
12820
|
+
await writeText(path15.join(sddDir, "INDEX.md"), initialIndex(language, cfg.project.name));
|
|
12821
|
+
created.push(path15.join(sddDir, "INDEX.md"));
|
|
12684
12822
|
if (opts.local) {
|
|
12685
|
-
const gitignore =
|
|
12823
|
+
const gitignore = path15.join(root, ".gitignore");
|
|
12686
12824
|
const current = await readTextIfExists(gitignore) ?? "";
|
|
12687
12825
|
if (!/(^|\n)\.sdd\/?(\n|$)/.test(current)) {
|
|
12688
12826
|
const next = current.endsWith("\n") || current === "" ? `${current}.sdd/
|
|
@@ -12720,7 +12858,7 @@ async function createChange(opts) {
|
|
|
12720
12858
|
const diagnostics = [];
|
|
12721
12859
|
const slug = opts.slug.trim().toLowerCase();
|
|
12722
12860
|
const language = opts.language ?? opts.cfg?.project.language ?? "es";
|
|
12723
|
-
const dir =
|
|
12861
|
+
const dir = path16.join(path16.resolve(opts.root), ".sdd", "changes", slug);
|
|
12724
12862
|
if (!SLUG_RE.test(slug)) {
|
|
12725
12863
|
return {
|
|
12726
12864
|
slug,
|
|
@@ -12751,20 +12889,20 @@ async function createChange(opts) {
|
|
|
12751
12889
|
await ensureDir(dir);
|
|
12752
12890
|
const templates = templatesFor(language);
|
|
12753
12891
|
const files = [];
|
|
12754
|
-
const metaFile =
|
|
12892
|
+
const metaFile = path16.join(dir, "meta.yaml");
|
|
12755
12893
|
await writeText(metaFile, changeMetaYaml(meta));
|
|
12756
12894
|
files.push(metaFile);
|
|
12757
12895
|
if (lane === "fix") {
|
|
12758
|
-
const fixFile =
|
|
12896
|
+
const fixFile = path16.join(dir, "fix.md");
|
|
12759
12897
|
await writeText(fixFile, renderTemplate(templates.fix, { TITLE: title }));
|
|
12760
12898
|
files.push(fixFile);
|
|
12761
12899
|
return { slug, dir, files, diagnostics };
|
|
12762
12900
|
}
|
|
12763
|
-
const proposalFile =
|
|
12901
|
+
const proposalFile = path16.join(dir, "proposal.md");
|
|
12764
12902
|
await writeText(proposalFile, renderTemplate(templates.proposal, { TITLE: title }));
|
|
12765
12903
|
files.push(proposalFile);
|
|
12766
12904
|
const domainUpper = domain.replace(/[^a-z0-9]/gi, "").toUpperCase() || "GEN";
|
|
12767
|
-
const deltaFile =
|
|
12905
|
+
const deltaFile = path16.join(dir, "spec.md");
|
|
12768
12906
|
await writeText(deltaFile, templates.specDelta({ title, domainUpper, domain }));
|
|
12769
12907
|
files.push(deltaFile);
|
|
12770
12908
|
return { slug, dir, files, diagnostics };
|
|
@@ -12877,7 +13015,7 @@ async function restoreFile(file, previous) {
|
|
|
12877
13015
|
}
|
|
12878
13016
|
}
|
|
12879
13017
|
async function archiveChange(opts) {
|
|
12880
|
-
const root =
|
|
13018
|
+
const root = path17.resolve(opts.root);
|
|
12881
13019
|
const diagnostics = [];
|
|
12882
13020
|
const { config } = await loadWorkspace(root);
|
|
12883
13021
|
const language = opts.language ?? config.project.language;
|
|
@@ -12893,16 +13031,43 @@ async function archiveChange(opts) {
|
|
|
12893
13031
|
return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
|
|
12894
13032
|
}
|
|
12895
13033
|
const nowFix = opts.now ?? /* @__PURE__ */ new Date();
|
|
12896
|
-
const targetFix =
|
|
13034
|
+
const targetFix = path17.join(root, ".sdd", "changes", "archive", `${localMonth(nowFix)}-${opts.slug}`);
|
|
12897
13035
|
if (await exists(targetFix)) {
|
|
12898
13036
|
diagnostics.push(diag("ATLAS-ARCH-002", "error", `Ya existe un cambio archivado en ${targetFix}`, { path: targetFix }));
|
|
12899
13037
|
return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
|
|
12900
13038
|
}
|
|
13039
|
+
let livingFix;
|
|
13040
|
+
let livingCreated = false;
|
|
13041
|
+
if (!opts.dryRun) {
|
|
13042
|
+
const fixRaw = await readTextIfExists(path17.join(change.dir, "fix.md")) ?? "";
|
|
13043
|
+
try {
|
|
13044
|
+
const written = await writeLivingFix(root, {
|
|
13045
|
+
slug: opts.slug,
|
|
13046
|
+
date: localDate(nowFix),
|
|
13047
|
+
result: "pass",
|
|
13048
|
+
...change.meta.domain !== void 0 ? { domain: change.meta.domain } : {},
|
|
13049
|
+
...change.meta.title !== void 0 ? { title: change.meta.title } : {},
|
|
13050
|
+
covers: change.fixCovers ?? [],
|
|
13051
|
+
content: fixRaw
|
|
13052
|
+
});
|
|
13053
|
+
livingFix = written.relativePath;
|
|
13054
|
+
livingCreated = written.created;
|
|
13055
|
+
} catch (error) {
|
|
13056
|
+
diagnostics.push(
|
|
13057
|
+
diag("ATLAS-ARCH-006", "error", `No se pudo conservar el fix vivo: ${error.message}`, {
|
|
13058
|
+
path: path17.join(root, ".sdd", "fixes"),
|
|
13059
|
+
suggestion: "Revisa los permisos de .sdd/fixes y vuelve a intentar; el fix sigue sin archivar"
|
|
13060
|
+
})
|
|
13061
|
+
);
|
|
13062
|
+
return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: false };
|
|
13063
|
+
}
|
|
13064
|
+
}
|
|
12901
13065
|
if (!opts.dryRun) {
|
|
12902
|
-
await ensureDir(
|
|
13066
|
+
await ensureDir(path17.dirname(targetFix));
|
|
12903
13067
|
try {
|
|
12904
13068
|
await moveDirectory(change.dir, targetFix);
|
|
12905
13069
|
} catch (error) {
|
|
13070
|
+
if (livingCreated && livingFix) await removeFile(path17.join(root, livingFix));
|
|
12906
13071
|
diagnostics.push(
|
|
12907
13072
|
diag("ATLAS-ARCH-004", "error", `No se pudo mover el cambio al hist\xF3rico: ${error.message}`, {
|
|
12908
13073
|
path: change.dir,
|
|
@@ -12911,9 +13076,16 @@ async function archiveChange(opts) {
|
|
|
12911
13076
|
);
|
|
12912
13077
|
return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: false };
|
|
12913
13078
|
}
|
|
12914
|
-
await regenerateIndex(root, config);
|
|
13079
|
+
await regenerateIndex(root, config, nowFix);
|
|
12915
13080
|
}
|
|
12916
|
-
return {
|
|
13081
|
+
return {
|
|
13082
|
+
slug: opts.slug,
|
|
13083
|
+
archivedTo: targetFix,
|
|
13084
|
+
...livingFix !== void 0 ? { livingFix } : {},
|
|
13085
|
+
fold: emptyFold,
|
|
13086
|
+
diagnostics,
|
|
13087
|
+
dryRun: opts.dryRun ?? false
|
|
13088
|
+
};
|
|
12917
13089
|
}
|
|
12918
13090
|
if (!change.delta) {
|
|
12919
13091
|
diagnostics.push(diag("ATLAS-ARCH-003", "error", `El cambio "${opts.slug}" no tiene spec.md (delta): no hay nada que plegar`, { path: change.dir }));
|
|
@@ -12921,10 +13093,10 @@ async function archiveChange(opts) {
|
|
|
12921
13093
|
}
|
|
12922
13094
|
const domain = change.meta.domain;
|
|
12923
13095
|
if (!domain) {
|
|
12924
|
-
diagnostics.push(diag("ATLAS-ARCH-001", "error", `El cambio "${opts.slug}" no declara domain en meta.yaml`, { path:
|
|
13096
|
+
diagnostics.push(diag("ATLAS-ARCH-001", "error", `El cambio "${opts.slug}" no declara domain en meta.yaml`, { path: path17.join(change.dir, "meta.yaml") }));
|
|
12925
13097
|
return { slug: opts.slug, fold: { content: "", applied: { added: [], modified: [], removed: [], renamed: [] }, diagnostics }, diagnostics, dryRun: opts.dryRun ?? false };
|
|
12926
13098
|
}
|
|
12927
|
-
const specFile =
|
|
13099
|
+
const specFile = path17.join(root, ".sdd", "specs", domain, "spec.md");
|
|
12928
13100
|
const existing = await readTextIfExists(specFile);
|
|
12929
13101
|
const base = existing ?? `---
|
|
12930
13102
|
domain: ${domain}
|
|
@@ -12954,8 +13126,8 @@ ${Object.entries(nextFm).map(([k, v]) => `${k}: ${String(v)}`).join("\n")}
|
|
|
12954
13126
|
|
|
12955
13127
|
${body}`;
|
|
12956
13128
|
const month = localMonth(now);
|
|
12957
|
-
const archiveDir =
|
|
12958
|
-
const target =
|
|
13129
|
+
const archiveDir = path17.join(root, ".sdd", "changes", "archive");
|
|
13130
|
+
const target = path17.join(archiveDir, `${month}-${opts.slug}`);
|
|
12959
13131
|
if (await exists(target)) {
|
|
12960
13132
|
diagnostics.push(diag("ATLAS-ARCH-002", "error", `Ya existe un cambio archivado en ${target}`, { path: target }));
|
|
12961
13133
|
return { slug: opts.slug, domain, fold, diagnostics, dryRun: opts.dryRun ?? false };
|
|
@@ -12981,17 +13153,25 @@ ${body}`;
|
|
|
12981
13153
|
}
|
|
12982
13154
|
async function regenerateIndex(root, cfg, now = /* @__PURE__ */ new Date()) {
|
|
12983
13155
|
const { workspace, config } = cfg ? { workspace: (await loadWorkspace(root)).workspace, config: cfg } : await loadWorkspace(root);
|
|
12984
|
-
const archiveDir =
|
|
13156
|
+
const archiveDir = path17.join(root, ".sdd", "changes", "archive");
|
|
12985
13157
|
const archived = await exists(archiveDir) ? (await fs.readdir(archiveDir)).filter((e) => !e.startsWith(".")).length : 0;
|
|
13158
|
+
const fixes = await loadLivingFixes(root);
|
|
12986
13159
|
const markdown2 = indexMarkdown({
|
|
12987
13160
|
projectName: config.project.name,
|
|
12988
13161
|
language: config.project.language,
|
|
12989
13162
|
specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length })),
|
|
12990
13163
|
changes: workspace.changes.map((c) => ({ slug: c.slug, lane: c.meta?.lane ?? config.lanes.default })),
|
|
13164
|
+
fixes: fixes.map((fix) => ({ slug: fix.slug, date: fix.date, result: fix.result, ...fix.domain !== void 0 ? { domain: fix.domain } : {} })),
|
|
12991
13165
|
archived
|
|
12992
13166
|
});
|
|
12993
13167
|
void now;
|
|
12994
|
-
await writeText(
|
|
13168
|
+
await writeText(path17.join(root, ".sdd", "INDEX.md"), markdown2);
|
|
13169
|
+
}
|
|
13170
|
+
async function removeFile(file) {
|
|
13171
|
+
try {
|
|
13172
|
+
await rm(file, { force: true });
|
|
13173
|
+
} catch {
|
|
13174
|
+
}
|
|
12995
13175
|
}
|
|
12996
13176
|
var SCHEMA_VERSION = 1;
|
|
12997
13177
|
var BACKUP_DIRNAME = ".backup";
|
|
@@ -13004,28 +13184,28 @@ var SCHEMA_MIGRATIONS = [
|
|
|
13004
13184
|
to: SCHEMA_VERSION
|
|
13005
13185
|
}
|
|
13006
13186
|
];
|
|
13007
|
-
var FIXED_ARTIFACTS = ["config.yaml", "approvals.yaml",
|
|
13008
|
-
var CHANGE_ARTIFACTS = ["meta.yaml",
|
|
13187
|
+
var FIXED_ARTIFACTS = ["config.yaml", "approvals.yaml", path18.join("profiles", "detected.yaml")];
|
|
13188
|
+
var CHANGE_ARTIFACTS = ["meta.yaml", path18.join("mockups", "manifest.yaml")];
|
|
13009
13189
|
async function pushChangeArtifacts(out, sddDir, relDir) {
|
|
13010
13190
|
for (const rel of CHANGE_ARTIFACTS) {
|
|
13011
|
-
const abs =
|
|
13012
|
-
if (await exists(abs)) out.push({ artifact: toPosix(
|
|
13191
|
+
const abs = path18.join(sddDir, relDir, rel);
|
|
13192
|
+
if (await exists(abs)) out.push({ artifact: toPosix(path18.join(relDir, rel)), path: abs });
|
|
13013
13193
|
}
|
|
13014
13194
|
}
|
|
13015
13195
|
async function collectVersionedArtifacts(root) {
|
|
13016
|
-
const sddDir =
|
|
13196
|
+
const sddDir = path18.join(root, ".sdd");
|
|
13017
13197
|
const out = [];
|
|
13018
13198
|
for (const rel of FIXED_ARTIFACTS) {
|
|
13019
|
-
const abs =
|
|
13199
|
+
const abs = path18.join(sddDir, rel);
|
|
13020
13200
|
if (await exists(abs)) out.push({ artifact: toPosix(rel), path: abs });
|
|
13021
13201
|
}
|
|
13022
|
-
const changesDir =
|
|
13202
|
+
const changesDir = path18.join(sddDir, "changes");
|
|
13023
13203
|
for (const slug of await listDirs(changesDir)) {
|
|
13024
13204
|
if (slug === "archive") continue;
|
|
13025
|
-
await pushChangeArtifacts(out, sddDir,
|
|
13205
|
+
await pushChangeArtifacts(out, sddDir, path18.join("changes", slug));
|
|
13026
13206
|
}
|
|
13027
|
-
for (const entry of await listDirs(
|
|
13028
|
-
await pushChangeArtifacts(out, sddDir,
|
|
13207
|
+
for (const entry of await listDirs(path18.join(changesDir, "archive"))) {
|
|
13208
|
+
await pushChangeArtifacts(out, sddDir, path18.join("changes", "archive", entry));
|
|
13029
13209
|
}
|
|
13030
13210
|
return out;
|
|
13031
13211
|
}
|
|
@@ -13110,7 +13290,7 @@ async function removeDir(dir) {
|
|
|
13110
13290
|
} catch {
|
|
13111
13291
|
}
|
|
13112
13292
|
}
|
|
13113
|
-
async function
|
|
13293
|
+
async function removeFile2(file) {
|
|
13114
13294
|
try {
|
|
13115
13295
|
await fs2.rm(file, { force: true });
|
|
13116
13296
|
} catch {
|
|
@@ -13122,22 +13302,22 @@ function backupName(now) {
|
|
|
13122
13302
|
async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
|
|
13123
13303
|
const plan = await planUpgrade(root);
|
|
13124
13304
|
if (plan.pending.length === 0) return { status: "up-to-date", plan, applied: [] };
|
|
13125
|
-
const sddDir =
|
|
13126
|
-
const backupRoot =
|
|
13305
|
+
const sddDir = path18.join(root, ".sdd");
|
|
13306
|
+
const backupRoot = path18.join(sddDir, BACKUP_DIRNAME);
|
|
13127
13307
|
const name = backupName(now);
|
|
13128
|
-
const backupDir =
|
|
13129
|
-
const pointerFile =
|
|
13308
|
+
const backupDir = path18.join(backupRoot, name);
|
|
13309
|
+
const pointerFile = path18.join(backupRoot, BACKUP_POINTER);
|
|
13130
13310
|
const previous = [];
|
|
13131
13311
|
try {
|
|
13132
13312
|
for (const item of plan.pending) {
|
|
13133
13313
|
const content = await readTextIfExists(item.path);
|
|
13134
13314
|
if (content === void 0) throw new Error("el elemento desapareci\xF3 mientras se respaldaba");
|
|
13135
13315
|
previous.push({ item, contents: content });
|
|
13136
|
-
await writeText(
|
|
13316
|
+
await writeText(path18.join(backupDir, "files", ...item.artifact.split("/")), content);
|
|
13137
13317
|
}
|
|
13138
13318
|
await writeText(
|
|
13139
|
-
|
|
13140
|
-
|
|
13319
|
+
path18.join(backupDir, "backup.yaml"),
|
|
13320
|
+
stringifyYaml6(
|
|
13141
13321
|
{
|
|
13142
13322
|
schema_version: SCHEMA_VERSION,
|
|
13143
13323
|
created_at: localStamp(now),
|
|
@@ -13151,7 +13331,7 @@ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
|
|
|
13151
13331
|
`);
|
|
13152
13332
|
} catch (err) {
|
|
13153
13333
|
await removeDir(backupDir);
|
|
13154
|
-
await
|
|
13334
|
+
await removeFile2(pointerFile);
|
|
13155
13335
|
return { status: "failed", plan, applied: [], failure: { artifact: "(respaldo)", message: err.message } };
|
|
13156
13336
|
}
|
|
13157
13337
|
const written = [];
|
|
@@ -13171,13 +13351,13 @@ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
|
|
|
13171
13351
|
}
|
|
13172
13352
|
}
|
|
13173
13353
|
}
|
|
13174
|
-
await
|
|
13354
|
+
await removeFile2(pointerFile);
|
|
13175
13355
|
await removeDir(backupDir);
|
|
13176
13356
|
return { status: "failed", plan, applied: [], failure: { artifact: item.artifact, message: err.message } };
|
|
13177
13357
|
}
|
|
13178
13358
|
}
|
|
13179
13359
|
for (const dir of await listDirs(backupRoot)) {
|
|
13180
|
-
if (dir !== name) await removeDir(
|
|
13360
|
+
if (dir !== name) await removeDir(path18.join(backupRoot, dir));
|
|
13181
13361
|
}
|
|
13182
13362
|
return {
|
|
13183
13363
|
status: "applied",
|
|
@@ -13186,20 +13366,20 @@ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
|
|
|
13186
13366
|
backup: {
|
|
13187
13367
|
name,
|
|
13188
13368
|
dir: backupDir,
|
|
13189
|
-
relativeDir: toPosix(
|
|
13369
|
+
relativeDir: toPosix(path18.relative(root, backupDir)),
|
|
13190
13370
|
createdAt: localStamp(now),
|
|
13191
13371
|
files: plan.pending.map((i) => i.artifact)
|
|
13192
13372
|
}
|
|
13193
13373
|
};
|
|
13194
13374
|
}
|
|
13195
13375
|
async function rollbackUpgrade(root) {
|
|
13196
|
-
const sddDir =
|
|
13197
|
-
const backupRoot =
|
|
13198
|
-
const pointerFile =
|
|
13376
|
+
const sddDir = path18.join(root, ".sdd");
|
|
13377
|
+
const backupRoot = path18.join(sddDir, BACKUP_DIRNAME);
|
|
13378
|
+
const pointerFile = path18.join(backupRoot, BACKUP_POINTER);
|
|
13199
13379
|
const name = (await readTextIfExists(pointerFile))?.trim();
|
|
13200
13380
|
if (!name) return { status: "no-backup", restored: [] };
|
|
13201
|
-
const backupDir =
|
|
13202
|
-
const manifestRaw = await readTextIfExists(
|
|
13381
|
+
const backupDir = path18.join(backupRoot, name);
|
|
13382
|
+
const manifestRaw = await readTextIfExists(path18.join(backupDir, "backup.yaml"));
|
|
13203
13383
|
if (manifestRaw === void 0) return { status: "no-backup", restored: [] };
|
|
13204
13384
|
let files = [];
|
|
13205
13385
|
try {
|
|
@@ -13210,8 +13390,8 @@ async function rollbackUpgrade(root) {
|
|
|
13210
13390
|
}
|
|
13211
13391
|
const restored = [];
|
|
13212
13392
|
for (const file of files) {
|
|
13213
|
-
const from =
|
|
13214
|
-
const to =
|
|
13393
|
+
const from = path18.join(backupDir, "files", ...file.artifact.split("/"));
|
|
13394
|
+
const to = path18.join(sddDir, ...file.artifact.split("/"));
|
|
13215
13395
|
const content = await readTextIfExists(from);
|
|
13216
13396
|
if (content === void 0) continue;
|
|
13217
13397
|
try {
|
|
@@ -13222,7 +13402,7 @@ async function rollbackUpgrade(root) {
|
|
|
13222
13402
|
}
|
|
13223
13403
|
}
|
|
13224
13404
|
await removeDir(backupDir);
|
|
13225
|
-
await
|
|
13405
|
+
await removeFile2(pointerFile);
|
|
13226
13406
|
return { status: "restored", restored, backup: name };
|
|
13227
13407
|
}
|
|
13228
13408
|
async function upgradeAdvisory(root) {
|
|
@@ -13290,7 +13470,7 @@ function resultOf(diagnostic, root) {
|
|
|
13290
13470
|
message: { text: diagnostic.message }
|
|
13291
13471
|
};
|
|
13292
13472
|
if (diagnostic.path) {
|
|
13293
|
-
const rel =
|
|
13473
|
+
const rel = path19.relative(root, diagnostic.path);
|
|
13294
13474
|
if (rel !== "") {
|
|
13295
13475
|
const physicalLocation = {
|
|
13296
13476
|
artifactLocation: { uri: toPosix(rel) },
|
|
@@ -13345,7 +13525,7 @@ async function runDoctor(root) {
|
|
|
13345
13525
|
const approvals = await loadApprovals(workspace.sddDir);
|
|
13346
13526
|
findings.push(...approvals.diagnostics);
|
|
13347
13527
|
for (const change of workspace.changes) {
|
|
13348
|
-
const deltaPath =
|
|
13528
|
+
const deltaPath = path20.join(change.dir, "spec.md");
|
|
13349
13529
|
const deltaContent = await readTextIfExists(deltaPath);
|
|
13350
13530
|
const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
|
|
13351
13531
|
if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
|
|
@@ -13367,7 +13547,7 @@ async function runDoctor(root) {
|
|
|
13367
13547
|
}
|
|
13368
13548
|
for (const override of change.meta?.overrides ?? []) {
|
|
13369
13549
|
if (!override.reason.trim() || !override.by.trim()) {
|
|
13370
|
-
findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path:
|
|
13550
|
+
findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path20.join(change.dir, "meta.yaml") }));
|
|
13371
13551
|
}
|
|
13372
13552
|
}
|
|
13373
13553
|
}
|
|
@@ -13399,7 +13579,7 @@ function livingRequirementsMap(specs) {
|
|
|
13399
13579
|
return map;
|
|
13400
13580
|
}
|
|
13401
13581
|
async function runCiGate(opts) {
|
|
13402
|
-
const root =
|
|
13582
|
+
const root = path21.resolve(opts.root);
|
|
13403
13583
|
const { workspace, config } = await loadWorkspace(root);
|
|
13404
13584
|
const diagnostics = [];
|
|
13405
13585
|
const checks = [];
|
|
@@ -13410,7 +13590,7 @@ async function runCiGate(opts) {
|
|
|
13410
13590
|
let changesErrors = 0;
|
|
13411
13591
|
let changesWarnings = 0;
|
|
13412
13592
|
for (const change of workspace.changes) {
|
|
13413
|
-
const lintFindings = change.delta ? lintDelta(change.delta, living,
|
|
13593
|
+
const lintFindings = change.delta ? lintDelta(change.delta, living, path21.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
|
|
13414
13594
|
const trace = checkTrace({
|
|
13415
13595
|
specs: workspace.specs,
|
|
13416
13596
|
change,
|
|
@@ -13552,6 +13732,7 @@ var ES2 = {
|
|
|
13552
13732
|
"status.title": "Estado de los cambios",
|
|
13553
13733
|
"status.changes": "Cambios activos",
|
|
13554
13734
|
"status.specs": "Specs vivas",
|
|
13735
|
+
"status.fixes": "Fixes vivos",
|
|
13555
13736
|
"status.next": "siguiente:",
|
|
13556
13737
|
"status.tasks": "tareas",
|
|
13557
13738
|
"status.evidence": "evidencia",
|
|
@@ -13586,6 +13767,7 @@ var EN2 = {
|
|
|
13586
13767
|
"status.title": "Change status",
|
|
13587
13768
|
"status.changes": "Active changes",
|
|
13588
13769
|
"status.specs": "Living specs",
|
|
13770
|
+
"status.fixes": "Living fixes",
|
|
13589
13771
|
"status.next": "next:",
|
|
13590
13772
|
"status.tasks": "tasks",
|
|
13591
13773
|
"status.evidence": "evidence",
|
|
@@ -13630,26 +13812,26 @@ function cliVersion() {
|
|
|
13630
13812
|
}
|
|
13631
13813
|
|
|
13632
13814
|
// src/commands/adapters.ts
|
|
13633
|
-
import
|
|
13815
|
+
import path25 from "path";
|
|
13634
13816
|
|
|
13635
13817
|
// ../adapters/dist/index.js
|
|
13636
|
-
import path21 from "path";
|
|
13637
|
-
import { stringify as stringifyYaml6 } from "yaml";
|
|
13638
13818
|
import path22 from "path";
|
|
13819
|
+
import { stringify as stringifyYaml7 } from "yaml";
|
|
13820
|
+
import path23 from "path";
|
|
13639
13821
|
async function loadWorkflow(workflowDir) {
|
|
13640
|
-
const phasesDir =
|
|
13641
|
-
const snippetsDir =
|
|
13822
|
+
const phasesDir = path22.join(workflowDir, "phases");
|
|
13823
|
+
const snippetsDir = path22.join(workflowDir, "snippets");
|
|
13642
13824
|
const phases = [];
|
|
13643
13825
|
const snippets = /* @__PURE__ */ new Map();
|
|
13644
13826
|
const hashParts = [];
|
|
13645
13827
|
for (const entry of (await listDir(phasesDir)).sort()) {
|
|
13646
13828
|
if (!entry.endsWith(".md")) continue;
|
|
13647
|
-
const filePath =
|
|
13829
|
+
const filePath = path22.join(phasesDir, entry);
|
|
13648
13830
|
const raw = await readText(filePath);
|
|
13649
13831
|
hashParts.push(raw);
|
|
13650
13832
|
const fm = parseFrontmatter(raw, filePath);
|
|
13651
13833
|
const data = fm.data;
|
|
13652
|
-
const id = typeof data["id"] === "string" ? data["id"] :
|
|
13834
|
+
const id = typeof data["id"] === "string" ? data["id"] : path22.basename(entry, ".md");
|
|
13653
13835
|
phases.push({
|
|
13654
13836
|
id,
|
|
13655
13837
|
title: typeof data["title"] === "string" ? data["title"] : id,
|
|
@@ -13663,9 +13845,9 @@ async function loadWorkflow(workflowDir) {
|
|
|
13663
13845
|
}
|
|
13664
13846
|
for (const entry of (await listDir(snippetsDir)).sort()) {
|
|
13665
13847
|
if (!entry.endsWith(".md")) continue;
|
|
13666
|
-
const raw = await readTextIfExists(
|
|
13848
|
+
const raw = await readTextIfExists(path22.join(snippetsDir, entry)) ?? "";
|
|
13667
13849
|
hashParts.push(raw);
|
|
13668
|
-
snippets.set(
|
|
13850
|
+
snippets.set(path22.basename(entry, ".md"), raw.trim());
|
|
13669
13851
|
}
|
|
13670
13852
|
return { phases: phases.sort((a, b) => a.id.localeCompare(b.id)), snippets, sourceHash: sha256(hashParts.join("\n---\n")) };
|
|
13671
13853
|
}
|
|
@@ -13689,7 +13871,7 @@ function vars(language, slugToken, commandPrefix) {
|
|
|
13689
13871
|
return { SLUG: slugToken, LANGUAGE: language, LANGUAGE_NAME: LANGUAGE_NAMES[language], SDD_DIR: ".sdd", COMMAND_PREFIX: commandPrefix };
|
|
13690
13872
|
}
|
|
13691
13873
|
function frontmatter(data, body) {
|
|
13692
|
-
const yaml2 =
|
|
13874
|
+
const yaml2 = stringifyYaml7(data, { lineWidth: 0 }).trimEnd();
|
|
13693
13875
|
return `---
|
|
13694
13876
|
${yaml2}
|
|
13695
13877
|
---
|
|
@@ -13847,11 +14029,11 @@ Reglas: la spec es funcional y de negocio (sin tecnolog\xEDa); la trazabilidad e
|
|
|
13847
14029
|
function tomlString(value) {
|
|
13848
14030
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
13849
14031
|
}
|
|
13850
|
-
var MANIFEST_REL =
|
|
14032
|
+
var MANIFEST_REL = path23.join(".sdd", ".generated", "manifest.json");
|
|
13851
14033
|
var BEGIN = "<!-- BEGIN specatlas -->";
|
|
13852
14034
|
var END = "<!-- END specatlas -->";
|
|
13853
14035
|
async function compileTargets(opts) {
|
|
13854
|
-
const root =
|
|
14036
|
+
const root = path23.resolve(opts.root);
|
|
13855
14037
|
const language = opts.language ?? "es";
|
|
13856
14038
|
const diagnostics = [];
|
|
13857
14039
|
const sources = await loadWorkflow(opts.workflowDir);
|
|
@@ -13859,7 +14041,7 @@ async function compileTargets(opts) {
|
|
|
13859
14041
|
diagnostics.push({
|
|
13860
14042
|
code: "ATLAS-ADAPTERS-002",
|
|
13861
14043
|
severity: "error",
|
|
13862
|
-
message: `No hay fases en ${
|
|
14044
|
+
message: `No hay fases en ${path23.join(opts.workflowDir, "phases")}`,
|
|
13863
14045
|
suggestion: "Revisa la carpeta workflow/phases del proyecto"
|
|
13864
14046
|
});
|
|
13865
14047
|
}
|
|
@@ -13871,7 +14053,7 @@ async function compileTargets(opts) {
|
|
|
13871
14053
|
}
|
|
13872
14054
|
}
|
|
13873
14055
|
const compiled = [...deduped.values()];
|
|
13874
|
-
const manifestPath =
|
|
14056
|
+
const manifestPath = path23.join(root, MANIFEST_REL);
|
|
13875
14057
|
const previous = await readManifest(manifestPath);
|
|
13876
14058
|
const previousHashes = /* @__PURE__ */ new Map();
|
|
13877
14059
|
for (const files2 of Object.values(previous?.targets ?? {})) {
|
|
@@ -13882,7 +14064,7 @@ async function compileTargets(opts) {
|
|
|
13882
14064
|
const stale = [];
|
|
13883
14065
|
const missing = [];
|
|
13884
14066
|
for (const file of compiled) {
|
|
13885
|
-
const abs =
|
|
14067
|
+
const abs = path23.join(root, file.path);
|
|
13886
14068
|
const finalContent = file.path === "AGENTS.md" ? mergeAgentsBlock(await readTextIfExists(abs) ?? "", file.content) : file.content;
|
|
13887
14069
|
const hash = artifactHash(finalContent);
|
|
13888
14070
|
const existing = await readTextIfExists(abs);
|
|
@@ -13898,7 +14080,7 @@ async function compileTargets(opts) {
|
|
|
13898
14080
|
stale.push(file.path);
|
|
13899
14081
|
}
|
|
13900
14082
|
if (!opts.check && status !== "unchanged") {
|
|
13901
|
-
await ensureDir(
|
|
14083
|
+
await ensureDir(path23.dirname(abs));
|
|
13902
14084
|
await writeText(abs, finalContent);
|
|
13903
14085
|
written.push(file.path);
|
|
13904
14086
|
}
|
|
@@ -13952,7 +14134,7 @@ async function readManifest(manifestPath) {
|
|
|
13952
14134
|
}
|
|
13953
14135
|
}
|
|
13954
14136
|
async function checkAdapters(opts) {
|
|
13955
|
-
const manifestPath =
|
|
14137
|
+
const manifestPath = path23.join(path23.resolve(opts.root), MANIFEST_REL);
|
|
13956
14138
|
const manifestRaw = await readTextIfExists(manifestPath);
|
|
13957
14139
|
if (manifestRaw === void 0) return { ok: false, manifest: false, stale: [], missing: [], orphaned: [] };
|
|
13958
14140
|
const manifest = await readManifest(manifestPath);
|
|
@@ -13969,7 +14151,7 @@ async function checkAdapters(opts) {
|
|
|
13969
14151
|
}
|
|
13970
14152
|
|
|
13971
14153
|
// src/paths.ts
|
|
13972
|
-
import
|
|
14154
|
+
import path24 from "path";
|
|
13973
14155
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13974
14156
|
async function resolveProfilesDir() {
|
|
13975
14157
|
const env = process.env["SPECATLAS_PROFILES_DIR"];
|
|
@@ -13979,16 +14161,16 @@ async function resolveProfilesDir() {
|
|
|
13979
14161
|
async function resolveWorkflowDir() {
|
|
13980
14162
|
const env = process.env["SPECATLAS_WORKFLOW_DIR"];
|
|
13981
14163
|
if (env && await exists(env)) return env;
|
|
13982
|
-
return walkUpFor(
|
|
14164
|
+
return walkUpFor(path24.join("workflow", "phases"));
|
|
13983
14165
|
}
|
|
13984
14166
|
async function walkUpFor(relative) {
|
|
13985
|
-
let dir =
|
|
14167
|
+
let dir = path24.dirname(fileURLToPath2(import.meta.url));
|
|
13986
14168
|
for (let i = 0; i < 8; i += 1) {
|
|
13987
|
-
const candidate =
|
|
14169
|
+
const candidate = path24.join(dir, relative);
|
|
13988
14170
|
if (await exists(candidate)) {
|
|
13989
|
-
return relative.includes(
|
|
14171
|
+
return relative.includes(path24.sep) ? path24.dirname(candidate) : candidate;
|
|
13990
14172
|
}
|
|
13991
|
-
const parent =
|
|
14173
|
+
const parent = path24.dirname(dir);
|
|
13992
14174
|
if (parent === dir) break;
|
|
13993
14175
|
dir = parent;
|
|
13994
14176
|
}
|
|
@@ -14066,7 +14248,7 @@ async function runAdapters(ctx) {
|
|
|
14066
14248
|
for (const [target, count2] of byTarget) lines.push(` ${target}: ${count2} archivo(s)`);
|
|
14067
14249
|
if (report.written.length > 0) {
|
|
14068
14250
|
lines.push("");
|
|
14069
|
-
for (const p of report.written) lines.push(` + ${
|
|
14251
|
+
for (const p of report.written) lines.push(` + ${path25.relative(ctx.cwd, path25.join(root, p))}`);
|
|
14070
14252
|
} else {
|
|
14071
14253
|
lines.push("");
|
|
14072
14254
|
lines.push("Sin cambios: los adaptadores ya estaban al d\xEDa.");
|
|
@@ -14077,7 +14259,7 @@ async function runAdapters(ctx) {
|
|
|
14077
14259
|
}
|
|
14078
14260
|
|
|
14079
14261
|
// src/commands/adopt.ts
|
|
14080
|
-
import
|
|
14262
|
+
import path26 from "path";
|
|
14081
14263
|
async function runAdopt(ctx) {
|
|
14082
14264
|
const { root } = await requireWorkspace(ctx);
|
|
14083
14265
|
const domainsFlag = flagString(ctx.flags, "domains") ?? flagString(ctx.flags, "domain");
|
|
@@ -14097,11 +14279,11 @@ async function runAdopt(ctx) {
|
|
|
14097
14279
|
}
|
|
14098
14280
|
if (result.createdSpecs.length > 0) {
|
|
14099
14281
|
lines.push("");
|
|
14100
|
-
for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${
|
|
14282
|
+
for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${path26.relative(ctx.cwd, spec)}`);
|
|
14101
14283
|
}
|
|
14102
14284
|
if (result.reportPath && !result.dryRun) {
|
|
14103
14285
|
lines.push("");
|
|
14104
|
-
lines.push(` Informe: ${
|
|
14286
|
+
lines.push(` Informe: ${path26.relative(ctx.cwd, result.reportPath)}`);
|
|
14105
14287
|
}
|
|
14106
14288
|
lines.push("");
|
|
14107
14289
|
lines.push("Siguiente: fase /satlas-adopt por dominio (requisitos AS-IS) y luego:");
|
|
@@ -14114,8 +14296,8 @@ async function runAdopt(ctx) {
|
|
|
14114
14296
|
diagnostics: result.diagnostics,
|
|
14115
14297
|
data: {
|
|
14116
14298
|
domains: result.domains.map((d) => ({ name: d.name, files: d.files, existingSpec: d.existingSpec })),
|
|
14117
|
-
createdSpecs: result.createdSpecs.map((s) =>
|
|
14118
|
-
reportPath: result.reportPath ?
|
|
14299
|
+
createdSpecs: result.createdSpecs.map((s) => path26.relative(ctx.cwd, s)),
|
|
14300
|
+
reportPath: result.reportPath ? path26.relative(ctx.cwd, result.reportPath) : void 0,
|
|
14119
14301
|
stack: result.stack,
|
|
14120
14302
|
dryRun: result.dryRun
|
|
14121
14303
|
},
|
|
@@ -14124,7 +14306,7 @@ async function runAdopt(ctx) {
|
|
|
14124
14306
|
}
|
|
14125
14307
|
|
|
14126
14308
|
// src/commands/analyze.ts
|
|
14127
|
-
import
|
|
14309
|
+
import path27 from "path";
|
|
14128
14310
|
async function runAnalyzeCommand(ctx) {
|
|
14129
14311
|
const { root } = await requireWorkspace(ctx);
|
|
14130
14312
|
const slug = ctx.positionals[0];
|
|
@@ -14135,23 +14317,23 @@ async function runAnalyzeCommand(ctx) {
|
|
|
14135
14317
|
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`];
|
|
14136
14318
|
if (result.waves) lines.push(` olas: ${result.waves.blocks} bloque(s), ${result.waves.waves} ola(s), ${result.waves.tasks} tarea(s)`);
|
|
14137
14319
|
if (result.mockups) lines.push(` mockups: ${result.mockups.screens} pantalla(s)${result.mockups.stale ? " (desactualizados)" : ""}`);
|
|
14138
|
-
if (result.path) lines.push("", ` informe: ${
|
|
14320
|
+
if (result.path) lines.push("", ` informe: ${path27.relative(ctx.cwd, result.path)}`);
|
|
14139
14321
|
for (const finding of result.findings) {
|
|
14140
14322
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}`);
|
|
14141
14323
|
}
|
|
14142
14324
|
return {
|
|
14143
14325
|
exitCode: result.status === "blocked" ? 1 : 0,
|
|
14144
14326
|
diagnostics: result.findings,
|
|
14145
|
-
data: result.path ? { ...result, path:
|
|
14327
|
+
data: result.path ? { ...result, path: path27.relative(ctx.cwd, result.path) } : result,
|
|
14146
14328
|
text: lines
|
|
14147
14329
|
};
|
|
14148
14330
|
}
|
|
14149
14331
|
|
|
14150
14332
|
// src/commands/approve.ts
|
|
14151
|
-
import
|
|
14333
|
+
import path29 from "path";
|
|
14152
14334
|
|
|
14153
14335
|
// src/commands/issue.ts
|
|
14154
|
-
import
|
|
14336
|
+
import path28 from "path";
|
|
14155
14337
|
async function runIssue(ctx) {
|
|
14156
14338
|
const action = ctx.positionals[0] ?? "status";
|
|
14157
14339
|
const slug = ctx.positionals[1];
|
|
@@ -14179,7 +14361,7 @@ async function runIssue(ctx) {
|
|
|
14179
14361
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-ISSUE-000", severity: "error", message: "Falta el slug: satlas issue status <slug>" }] };
|
|
14180
14362
|
}
|
|
14181
14363
|
const change = await loadChange(root, slug);
|
|
14182
|
-
const { config } = await loadConfig(
|
|
14364
|
+
const { config } = await loadConfig(path28.join(root, ".sdd"));
|
|
14183
14365
|
const tracker = change.meta?.tracker;
|
|
14184
14366
|
const lines = [`Issue de ${slug}`, ""];
|
|
14185
14367
|
if (!tracker || tracker.provider !== "github") {
|
|
@@ -14250,7 +14432,7 @@ async function runApprove(ctx) {
|
|
|
14250
14432
|
};
|
|
14251
14433
|
}
|
|
14252
14434
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
14253
|
-
const artifact = target.includes("/") || target.includes("\\") ? target :
|
|
14435
|
+
const artifact = target.includes("/") || target.includes("\\") ? target : path29.join("changes", target, "spec.md");
|
|
14254
14436
|
const channelFlag = flagString(ctx.flags, "channel");
|
|
14255
14437
|
const channel = channelFlag === "presentation" || channelFlag === "editor" || channelFlag === "pr" || channelFlag === "tracker" ? channelFlag : "cli";
|
|
14256
14438
|
const change = workspace.changes.find(
|
|
@@ -14291,13 +14473,13 @@ async function runApprove(ctx) {
|
|
|
14291
14473
|
return {
|
|
14292
14474
|
exitCode: hasErrors2 ? 1 : 0,
|
|
14293
14475
|
diagnostics: result.diagnostics,
|
|
14294
|
-
data: result.approval ? { ...result.approval, file:
|
|
14476
|
+
data: result.approval ? { ...result.approval, file: path29.relative(ctx.cwd, result.file) } : void 0,
|
|
14295
14477
|
text: lines
|
|
14296
14478
|
};
|
|
14297
14479
|
}
|
|
14298
14480
|
|
|
14299
14481
|
// src/commands/archive.ts
|
|
14300
|
-
import
|
|
14482
|
+
import path30 from "path";
|
|
14301
14483
|
async function runArchive(ctx) {
|
|
14302
14484
|
const slug = ctx.positionals[0];
|
|
14303
14485
|
if (!slug) {
|
|
@@ -14321,27 +14503,31 @@ async function runArchive(ctx) {
|
|
|
14321
14503
|
}
|
|
14322
14504
|
const result = await archiveChange({ root, slug, language: config.project.language, dryRun });
|
|
14323
14505
|
const lines = [msg("archive.title", ctx.language), ""];
|
|
14324
|
-
|
|
14506
|
+
const operations = result.fold.applied.added.length + result.fold.applied.modified.length + result.fold.applied.removed.length + result.fold.applied.renamed.length;
|
|
14507
|
+
if (operations > 0) {
|
|
14325
14508
|
lines.push(`${dryRun ? "[dry-run] " : ""}${msg("archive.done", ctx.language)}`);
|
|
14326
14509
|
lines.push(` agregados: ${result.fold.applied.added.join(", ") || "\u2014"}`);
|
|
14327
14510
|
lines.push(` modificados: ${result.fold.applied.modified.join(", ") || "\u2014"}`);
|
|
14328
14511
|
lines.push(` eliminados: ${result.fold.applied.removed.join(", ") || "\u2014"}`);
|
|
14329
14512
|
lines.push(` renombrados: ${result.fold.applied.renamed.join(", ") || "\u2014"}`);
|
|
14330
|
-
|
|
14513
|
+
} else if (result.livingFix) {
|
|
14514
|
+
lines.push(`${dryRun ? "[dry-run] " : ""}Fix archivado y conservado como fix vivo.`);
|
|
14515
|
+
lines.push(` fix vivo: ${result.livingFix}`);
|
|
14331
14516
|
} else {
|
|
14332
14517
|
lines.push("El delta no contiene operaciones (ADDED/MODIFIED/REMOVED/RENAMED).");
|
|
14333
14518
|
}
|
|
14519
|
+
if (result.archivedTo) lines.push(` archivado en: ${path30.relative(ctx.cwd, result.archivedTo)}`);
|
|
14334
14520
|
const hasErrors2 = result.diagnostics.some((d) => d.severity === "error");
|
|
14335
14521
|
return {
|
|
14336
14522
|
exitCode: hasErrors2 ? 1 : 0,
|
|
14337
14523
|
diagnostics: result.diagnostics,
|
|
14338
|
-
data: { slug: result.slug, domain: result.domain, archivedTo: result.archivedTo, applied: result.fold.applied, dryRun: result.dryRun },
|
|
14524
|
+
data: { slug: result.slug, domain: result.domain, archivedTo: result.archivedTo, livingFix: result.livingFix, applied: result.fold.applied, dryRun: result.dryRun },
|
|
14339
14525
|
text: lines
|
|
14340
14526
|
};
|
|
14341
14527
|
}
|
|
14342
14528
|
|
|
14343
14529
|
// src/commands/ci.ts
|
|
14344
|
-
import
|
|
14530
|
+
import path31 from "path";
|
|
14345
14531
|
async function runCi(ctx) {
|
|
14346
14532
|
const { root, config } = await requireWorkspace(ctx);
|
|
14347
14533
|
const strict = flagBool(ctx.flags, "strict");
|
|
@@ -14367,10 +14553,10 @@ async function runCi(ctx) {
|
|
|
14367
14553
|
const lines = ["CI de SpecAtlas", ""];
|
|
14368
14554
|
let sarifWritten;
|
|
14369
14555
|
if (sarifPath) {
|
|
14370
|
-
const target =
|
|
14556
|
+
const target = path31.resolve(ctx.cwd, sarifPath);
|
|
14371
14557
|
try {
|
|
14372
14558
|
await writeText(target, toSarifText({ diagnostics: gate.diagnostics, root, version: cliVersion(), failed: gate.failed }));
|
|
14373
|
-
sarifWritten =
|
|
14559
|
+
sarifWritten = path31.relative(ctx.cwd, target);
|
|
14374
14560
|
} catch (err) {
|
|
14375
14561
|
diagnostics.push({
|
|
14376
14562
|
code: "ATLAS-CI-SARIF-001",
|
|
@@ -14405,12 +14591,12 @@ async function runCi(ctx) {
|
|
|
14405
14591
|
}
|
|
14406
14592
|
|
|
14407
14593
|
// src/commands/profile.ts
|
|
14408
|
-
import
|
|
14594
|
+
import path32 from "path";
|
|
14409
14595
|
async function runProfile(ctx) {
|
|
14410
14596
|
const action = ctx.positionals[0] ?? "detect";
|
|
14411
14597
|
const { root, workspace } = await requireWorkspace(ctx);
|
|
14412
14598
|
const officialDir = await resolveProfilesDir();
|
|
14413
|
-
const customDir =
|
|
14599
|
+
const customDir = path32.join(workspace.sddDir, "profiles", "custom");
|
|
14414
14600
|
const profiles = [...officialDir ? await loadProfilesFromDir(officialDir) : [], ...await loadProfilesFromDir(customDir)];
|
|
14415
14601
|
if (action === "detect") {
|
|
14416
14602
|
const result = await detectProfiles(root, profiles);
|
|
@@ -14439,7 +14625,7 @@ async function runProfile(ctx) {
|
|
|
14439
14625
|
if (!name) {
|
|
14440
14626
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-001", severity: "error", message: "Falta el nombre del perfil: satlas profile create <nombre>" }] };
|
|
14441
14627
|
}
|
|
14442
|
-
const file =
|
|
14628
|
+
const file = path32.join(customDir, `${name}.yaml`);
|
|
14443
14629
|
if (await exists(file)) {
|
|
14444
14630
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-002", severity: "error", message: `El perfil ya existe: ${file}` }] };
|
|
14445
14631
|
}
|
|
@@ -14448,7 +14634,7 @@ async function runProfile(ctx) {
|
|
|
14448
14634
|
exitCode: 0,
|
|
14449
14635
|
diagnostics: [],
|
|
14450
14636
|
data: { file },
|
|
14451
|
-
text: [`Perfil creado: ${
|
|
14637
|
+
text: [`Perfil creado: ${path32.relative(ctx.cwd, file)}`, "", "Ed\xEDtalo con los comandos reales de tu stack (build/lint/test) y tus anti-patrones."]
|
|
14452
14638
|
};
|
|
14453
14639
|
}
|
|
14454
14640
|
return {
|
|
@@ -14457,7 +14643,7 @@ async function runProfile(ctx) {
|
|
|
14457
14643
|
};
|
|
14458
14644
|
}
|
|
14459
14645
|
async function isInside(dir, name) {
|
|
14460
|
-
return exists(
|
|
14646
|
+
return exists(path32.join(dir, `${name}.yaml`));
|
|
14461
14647
|
}
|
|
14462
14648
|
function profileTemplate(name) {
|
|
14463
14649
|
return `name: ${name}
|
|
@@ -14485,7 +14671,7 @@ assumptions: []
|
|
|
14485
14671
|
}
|
|
14486
14672
|
|
|
14487
14673
|
// src/commands/doctor.ts
|
|
14488
|
-
import
|
|
14674
|
+
import path33 from "path";
|
|
14489
14675
|
async function runDoctorCommand(ctx) {
|
|
14490
14676
|
const { root, config } = await requireWorkspace(ctx);
|
|
14491
14677
|
const report = await runDoctor(root);
|
|
@@ -14510,7 +14696,7 @@ async function runDoctorCommand(ctx) {
|
|
|
14510
14696
|
lines.push("Sin hallazgos. El workspace est\xE1 sano.");
|
|
14511
14697
|
} else {
|
|
14512
14698
|
for (const finding of report.findings) {
|
|
14513
|
-
const location = finding.path ? ` ${
|
|
14699
|
+
const location = finding.path ? ` ${path33.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
|
|
14514
14700
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${location} \u2014 ${finding.message}`);
|
|
14515
14701
|
if (finding.suggestion) lines.push(` \u21B3 ${finding.suggestion}`);
|
|
14516
14702
|
}
|
|
@@ -14584,7 +14770,7 @@ async function runHelp(ctx, commandName) {
|
|
|
14584
14770
|
}
|
|
14585
14771
|
|
|
14586
14772
|
// src/commands/init.ts
|
|
14587
|
-
import
|
|
14773
|
+
import path34 from "path";
|
|
14588
14774
|
async function runInit(ctx) {
|
|
14589
14775
|
const languageFlag = flagString(ctx.flags, "language");
|
|
14590
14776
|
const language = languageFlag === "en" ? "en" : languageFlag === "es" ? "es" : void 0;
|
|
@@ -14631,11 +14817,11 @@ async function runInit(ctx) {
|
|
|
14631
14817
|
const lines = [msg("init.title", ctx.language), ""];
|
|
14632
14818
|
if (result.created.length > 0) {
|
|
14633
14819
|
lines.push(msg("init.done", ctx.language));
|
|
14634
|
-
for (const file of result.created) lines.push(` + ${
|
|
14820
|
+
for (const file of result.created) lines.push(` + ${path34.relative(ctx.cwd, file)}`);
|
|
14635
14821
|
if (compiled.length > 0) {
|
|
14636
14822
|
lines.push("");
|
|
14637
14823
|
lines.push("Adaptadores compilados:");
|
|
14638
|
-
for (const file of compiled) lines.push(` + ${
|
|
14824
|
+
for (const file of compiled) lines.push(` + ${path34.relative(ctx.cwd, path34.join(ctx.cwd, file))}`);
|
|
14639
14825
|
}
|
|
14640
14826
|
lines.push("");
|
|
14641
14827
|
lines.push("Siguiente: crea un cambio con `satlas new <slug>` y escribe la spec funcional.");
|
|
@@ -14645,8 +14831,8 @@ async function runInit(ctx) {
|
|
|
14645
14831
|
exitCode: hasErrors2 ? 1 : 0,
|
|
14646
14832
|
diagnostics: result.diagnostics,
|
|
14647
14833
|
data: {
|
|
14648
|
-
sddDir:
|
|
14649
|
-
created: result.created.map((f) =>
|
|
14834
|
+
sddDir: path34.relative(ctx.cwd, result.sddDir),
|
|
14835
|
+
created: result.created.map((f) => path34.relative(ctx.cwd, f)),
|
|
14650
14836
|
detected: result.detected?.matches.map((m) => ({ name: m.name, score: m.score })),
|
|
14651
14837
|
best: result.detected?.best?.name
|
|
14652
14838
|
},
|
|
@@ -14687,7 +14873,7 @@ async function runMetrics(ctx) {
|
|
|
14687
14873
|
}
|
|
14688
14874
|
|
|
14689
14875
|
// src/commands/mockup.ts
|
|
14690
|
-
import
|
|
14876
|
+
import path35 from "path";
|
|
14691
14877
|
async function runMockup(ctx) {
|
|
14692
14878
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
14693
14879
|
const slug = ctx.positionals[0];
|
|
@@ -14736,17 +14922,17 @@ async function runMockup(ctx) {
|
|
|
14736
14922
|
` pantallas: ${plan.screens.length}`,
|
|
14737
14923
|
...plan.screens.map((s) => ` - ${s.id}: ${s.title} (${s.illustrates.length} escenario(s))`),
|
|
14738
14924
|
"",
|
|
14739
|
-
` plan: ${
|
|
14740
|
-
` manifiesto: ${
|
|
14925
|
+
` plan: ${path35.relative(ctx.cwd, planFile)}`,
|
|
14926
|
+
` manifiesto: ${path35.relative(ctx.cwd, manifestFile)}`,
|
|
14741
14927
|
"",
|
|
14742
14928
|
"Siguiente: genera el HTML de cada pantalla (fase /satlas-mockup) y valida con `satlas mockup " + slug + " --check`."
|
|
14743
14929
|
];
|
|
14744
|
-
return { exitCode: 0, diagnostics: [], data: { plan, planFile:
|
|
14930
|
+
return { exitCode: 0, diagnostics: [], data: { plan, planFile: path35.relative(ctx.cwd, planFile), manifestFile: path35.relative(ctx.cwd, manifestFile) }, text: lines };
|
|
14745
14931
|
}
|
|
14746
14932
|
|
|
14747
14933
|
// src/mcp/server.ts
|
|
14748
14934
|
import { promises as fs3 } from "fs";
|
|
14749
|
-
import
|
|
14935
|
+
import path38 from "path";
|
|
14750
14936
|
import { createInterface } from "readline/promises";
|
|
14751
14937
|
|
|
14752
14938
|
// src/mcp/protocol.ts
|
|
@@ -14807,13 +14993,40 @@ function stringArg(args, key) {
|
|
|
14807
14993
|
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
14808
14994
|
}
|
|
14809
14995
|
|
|
14996
|
+
// src/mcp/tools/fixes.ts
|
|
14997
|
+
async function runAtlasFixes(_args, host) {
|
|
14998
|
+
const resolved = await host.getWorkspace();
|
|
14999
|
+
if (!resolved) return noWorkspaceResult();
|
|
15000
|
+
const fixes = await loadLivingFixes(resolved.root);
|
|
15001
|
+
if (fixes.length === 0) {
|
|
15002
|
+
const activeFix = resolved.workspace.changes.find((change) => change.meta?.lane === "fix");
|
|
15003
|
+
return jsonResult({
|
|
15004
|
+
fixes: [],
|
|
15005
|
+
message: "No hay fixes vivos registrados.",
|
|
15006
|
+
action: activeFix ? `satlas archive ${activeFix.slug}` : "satlas new <slug> --lane fix"
|
|
15007
|
+
});
|
|
15008
|
+
}
|
|
15009
|
+
return jsonResult({
|
|
15010
|
+
fixes: fixes.map((fix) => ({
|
|
15011
|
+
slug: fix.slug,
|
|
15012
|
+
date: fix.date,
|
|
15013
|
+
result: fix.result,
|
|
15014
|
+
source: fix.source,
|
|
15015
|
+
domain: fix.domain,
|
|
15016
|
+
title: fix.title,
|
|
15017
|
+
covers: fix.covers,
|
|
15018
|
+
content: fix.content
|
|
15019
|
+
}))
|
|
15020
|
+
});
|
|
15021
|
+
}
|
|
15022
|
+
|
|
14810
15023
|
// src/mcp/tools/glossary.ts
|
|
14811
|
-
import
|
|
15024
|
+
import path36 from "path";
|
|
14812
15025
|
async function runAtlasGlossary(_args, host) {
|
|
14813
15026
|
const resolved = await host.getWorkspace();
|
|
14814
15027
|
if (!resolved) return noWorkspaceResult();
|
|
14815
15028
|
const { root, config } = resolved;
|
|
14816
|
-
const glossaryPath =
|
|
15029
|
+
const glossaryPath = path36.resolve(root, config.spec.glossary);
|
|
14817
15030
|
const raw = await readTextIfExists(glossaryPath);
|
|
14818
15031
|
if (raw === void 0) {
|
|
14819
15032
|
return jsonResult({
|
|
@@ -14859,7 +15072,7 @@ async function runAtlasImpact(args, host) {
|
|
|
14859
15072
|
}
|
|
14860
15073
|
|
|
14861
15074
|
// src/evaluate.ts
|
|
14862
|
-
import
|
|
15075
|
+
import path37 from "path";
|
|
14863
15076
|
function livingRequirementsMap2(specs) {
|
|
14864
15077
|
const map = /* @__PURE__ */ new Map();
|
|
14865
15078
|
for (const spec of specs) {
|
|
@@ -14868,7 +15081,7 @@ function livingRequirementsMap2(specs) {
|
|
|
14868
15081
|
return map;
|
|
14869
15082
|
}
|
|
14870
15083
|
async function evaluateChange(workspace, config, change, approvals) {
|
|
14871
|
-
const deltaPath =
|
|
15084
|
+
const deltaPath = path37.join(change.dir, "spec.md");
|
|
14872
15085
|
const deltaContent = await readTextIfExists(deltaPath);
|
|
14873
15086
|
const approval = verifyApproval(change, approvals, config, deltaContent ?? void 0);
|
|
14874
15087
|
const living = livingRequirementsMap2(workspace.specs);
|
|
@@ -15044,7 +15257,8 @@ var HANDLERS = {
|
|
|
15044
15257
|
atlas_validate: runAtlasValidate,
|
|
15045
15258
|
atlas_trace: runAtlasTrace,
|
|
15046
15259
|
atlas_impact: runAtlasImpact,
|
|
15047
|
-
atlas_glossary: runAtlasGlossary
|
|
15260
|
+
atlas_glossary: runAtlasGlossary,
|
|
15261
|
+
atlas_fixes: runAtlasFixes
|
|
15048
15262
|
};
|
|
15049
15263
|
async function runToolByName(name, args, host) {
|
|
15050
15264
|
const handler = HANDLERS[name];
|
|
@@ -15113,6 +15327,15 @@ function listTools() {
|
|
|
15113
15327
|
properties: {},
|
|
15114
15328
|
additionalProperties: false
|
|
15115
15329
|
}
|
|
15330
|
+
},
|
|
15331
|
+
{
|
|
15332
|
+
name: "atlas_fixes",
|
|
15333
|
+
description: "Fixes vivos (carril express ya archivado): identidad, resultado de su evidencia, contenido y requisitos que declaran. Solo lectura.",
|
|
15334
|
+
inputSchema: {
|
|
15335
|
+
type: "object",
|
|
15336
|
+
properties: {},
|
|
15337
|
+
additionalProperties: false
|
|
15338
|
+
}
|
|
15116
15339
|
}
|
|
15117
15340
|
];
|
|
15118
15341
|
}
|
|
@@ -15130,8 +15353,8 @@ var WorkspaceCache = class {
|
|
|
15130
15353
|
}
|
|
15131
15354
|
};
|
|
15132
15355
|
async function workspaceStamp(root) {
|
|
15133
|
-
const sddDir =
|
|
15134
|
-
const markers = [sddDir,
|
|
15356
|
+
const sddDir = path38.join(root, ".sdd");
|
|
15357
|
+
const markers = [sddDir, path38.join(sddDir, "config.yaml"), path38.join(sddDir, "changes"), path38.join(sddDir, "specs"), path38.join(sddDir, "approvals.yaml")];
|
|
15135
15358
|
let stamp = 0;
|
|
15136
15359
|
for (const marker of markers) {
|
|
15137
15360
|
try {
|
|
@@ -15149,7 +15372,7 @@ async function resolveMcpWorkspace(cwd, cache) {
|
|
|
15149
15372
|
const cached2 = cache.get(root);
|
|
15150
15373
|
if (cached2 && cached2.mtimeMs === stamp) return cached2.value;
|
|
15151
15374
|
const { workspace, config } = await loadWorkspace(root);
|
|
15152
|
-
const approvals = await loadApprovals(
|
|
15375
|
+
const approvals = await loadApprovals(path38.join(root, ".sdd"));
|
|
15153
15376
|
const value = { root, workspace, config, approvals: approvals.byArtifact };
|
|
15154
15377
|
cache.set(root, stamp, value);
|
|
15155
15378
|
return value;
|
|
@@ -15241,7 +15464,7 @@ async function runMcp(ctx) {
|
|
|
15241
15464
|
}
|
|
15242
15465
|
|
|
15243
15466
|
// src/commands/packs.ts
|
|
15244
|
-
import
|
|
15467
|
+
import path39 from "path";
|
|
15245
15468
|
async function runPacks(ctx) {
|
|
15246
15469
|
const { root, config, workspace } = await requireWorkspace(ctx);
|
|
15247
15470
|
const slug = flagString(ctx.flags, "check");
|
|
@@ -15299,14 +15522,14 @@ async function runPacks(ctx) {
|
|
|
15299
15522
|
data: {
|
|
15300
15523
|
slug,
|
|
15301
15524
|
packs: evaluations.map((evaluation) => ({ id: evaluation.pack.id, status: evaluation.status, passed: evaluation.passed, failed: evaluation.failed, results: evaluation.results })),
|
|
15302
|
-
analyzePath:
|
|
15525
|
+
analyzePath: path39.posix.join(".sdd", "changes", slug, "analyze.md")
|
|
15303
15526
|
},
|
|
15304
15527
|
text: lines
|
|
15305
15528
|
};
|
|
15306
15529
|
}
|
|
15307
15530
|
|
|
15308
15531
|
// src/commands/new.ts
|
|
15309
|
-
import
|
|
15532
|
+
import path40 from "path";
|
|
15310
15533
|
async function runNew(ctx) {
|
|
15311
15534
|
const slug = ctx.positionals[0];
|
|
15312
15535
|
if (!slug) {
|
|
@@ -15332,14 +15555,14 @@ async function runNew(ctx) {
|
|
|
15332
15555
|
msg("new.title", ctx.language),
|
|
15333
15556
|
"",
|
|
15334
15557
|
msg("new.done", ctx.language),
|
|
15335
|
-
...result.files.map((f) => ` + ${
|
|
15558
|
+
...result.files.map((f) => ` + ${path40.relative(ctx.cwd, f)}`),
|
|
15336
15559
|
"",
|
|
15337
15560
|
`Siguiente: /satlas.specify ${result.slug} \u2014 escribe la especificaci\xF3n 100% funcional y de negocio.`
|
|
15338
15561
|
];
|
|
15339
15562
|
return {
|
|
15340
15563
|
exitCode: hasErrors2 ? 1 : 0,
|
|
15341
15564
|
diagnostics: result.diagnostics,
|
|
15342
|
-
data: { slug: result.slug, files: result.files.map((f) =>
|
|
15565
|
+
data: { slug: result.slug, files: result.files.map((f) => path40.relative(ctx.cwd, f)) },
|
|
15343
15566
|
text: lines
|
|
15344
15567
|
};
|
|
15345
15568
|
}
|
|
@@ -15374,7 +15597,7 @@ async function runNext(ctx) {
|
|
|
15374
15597
|
}
|
|
15375
15598
|
|
|
15376
15599
|
// src/commands/present.ts
|
|
15377
|
-
import
|
|
15600
|
+
import path41 from "path";
|
|
15378
15601
|
async function runPresentCommand(ctx) {
|
|
15379
15602
|
const { root } = await requireWorkspace(ctx);
|
|
15380
15603
|
const slug = ctx.positionals[0];
|
|
@@ -15386,7 +15609,7 @@ async function runPresentCommand(ctx) {
|
|
|
15386
15609
|
if (result.path) {
|
|
15387
15610
|
lines.push("Propuesta generada");
|
|
15388
15611
|
lines.push("");
|
|
15389
|
-
lines.push(` archivo: ${
|
|
15612
|
+
lines.push(` archivo: ${path41.relative(ctx.cwd, result.path)}`);
|
|
15390
15613
|
if (result.hash) lines.push(` hash de la spec: ${shortHash(result.hash)}`);
|
|
15391
15614
|
lines.push("");
|
|
15392
15615
|
lines.push("\xC1brela en el navegador y comp\xE1rtela con el stakeholder.");
|
|
@@ -15396,7 +15619,7 @@ async function runPresentCommand(ctx) {
|
|
|
15396
15619
|
return {
|
|
15397
15620
|
exitCode: hasErrors2 ? 1 : 0,
|
|
15398
15621
|
diagnostics: result.diagnostics,
|
|
15399
|
-
data: result.path ? { path:
|
|
15622
|
+
data: result.path ? { path: path41.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
|
|
15400
15623
|
text: lines
|
|
15401
15624
|
};
|
|
15402
15625
|
}
|
|
@@ -15505,6 +15728,17 @@ async function runStatus(ctx) {
|
|
|
15505
15728
|
for (const spec of workspace.specs) {
|
|
15506
15729
|
lines.push(` ${spec.domain} \u2014 ${spec.spec.requirements.length} requisitos`);
|
|
15507
15730
|
}
|
|
15731
|
+
const fixes = await loadLivingFixes(root);
|
|
15732
|
+
lines.push("");
|
|
15733
|
+
lines.push(`${msg("status.fixes", ctx.language)}: ${fixes.length}`);
|
|
15734
|
+
if (fixes.length === 0) {
|
|
15735
|
+
lines.push(" (se llenan al archivar un fix)");
|
|
15736
|
+
} else {
|
|
15737
|
+
for (const fix of fixes) {
|
|
15738
|
+
const origin = fix.source === "archive" ? " (hist\xF3rico)" : "";
|
|
15739
|
+
lines.push(` ${fix.date || "\u2014"} ${(fix.domain ?? "\u2014").padEnd(10)} ${fix.slug} \u2014 ${fix.result}${origin}`);
|
|
15740
|
+
}
|
|
15741
|
+
}
|
|
15508
15742
|
const diagnostics = [...workspace.diagnostics, ...advisory.diagnostics];
|
|
15509
15743
|
const errors = diagnostics.filter((d) => d.severity === "error").length;
|
|
15510
15744
|
return {
|
|
@@ -15513,6 +15747,14 @@ async function runStatus(ctx) {
|
|
|
15513
15747
|
data: {
|
|
15514
15748
|
changes,
|
|
15515
15749
|
specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length })),
|
|
15750
|
+
fixes: fixes.map((fix) => ({
|
|
15751
|
+
slug: fix.slug,
|
|
15752
|
+
date: fix.date,
|
|
15753
|
+
result: fix.result,
|
|
15754
|
+
source: fix.source,
|
|
15755
|
+
...fix.domain !== void 0 ? { domain: fix.domain } : {},
|
|
15756
|
+
covers: fix.covers
|
|
15757
|
+
})),
|
|
15516
15758
|
upgrade: {
|
|
15517
15759
|
currentVersion: advisory.plan.currentVersion,
|
|
15518
15760
|
pending: advisory.plan.pending.length,
|
|
@@ -15525,7 +15767,7 @@ async function runStatus(ctx) {
|
|
|
15525
15767
|
}
|
|
15526
15768
|
|
|
15527
15769
|
// src/commands/trace.ts
|
|
15528
|
-
import
|
|
15770
|
+
import path42 from "path";
|
|
15529
15771
|
async function runTrace(ctx) {
|
|
15530
15772
|
const { workspace, config } = await requireWorkspace(ctx);
|
|
15531
15773
|
const slug = flagString(ctx.flags, "change");
|
|
@@ -15545,7 +15787,7 @@ async function runTrace(ctx) {
|
|
|
15545
15787
|
data.push({ slug: change.slug, nodes: result.graph.nodes.length, edges: result.graph.edges.length, summary: result.summary, findings: result.findings });
|
|
15546
15788
|
lines.push(` ${change.slug}: ${result.graph.nodes.length} nodos, ${result.graph.edges.length} aristas, ${errors} errores, ${result.summary.warnings} avisos`);
|
|
15547
15789
|
for (const finding of result.findings) {
|
|
15548
|
-
const location = finding.path ? ` (${
|
|
15790
|
+
const location = finding.path ? ` (${path42.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
|
|
15549
15791
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}${location}`);
|
|
15550
15792
|
}
|
|
15551
15793
|
}
|
|
@@ -15555,7 +15797,7 @@ async function runTrace(ctx) {
|
|
|
15555
15797
|
}
|
|
15556
15798
|
|
|
15557
15799
|
// src/commands/upgrade.ts
|
|
15558
|
-
import
|
|
15800
|
+
import path43 from "path";
|
|
15559
15801
|
async function runUpgrade(ctx) {
|
|
15560
15802
|
const { root } = await requireWorkspace(ctx);
|
|
15561
15803
|
const apply = flagBool(ctx.flags, "apply");
|
|
@@ -15609,7 +15851,7 @@ function renderPreview(plan, root, ctx) {
|
|
|
15609
15851
|
}
|
|
15610
15852
|
for (const item of plan.unreadable) {
|
|
15611
15853
|
lines.push(` Ilegible: "${item.artifact}" \u2014 ${item.reason}`);
|
|
15612
|
-
lines.push(` \u21B3 ${
|
|
15854
|
+
lines.push(` \u21B3 ${path43.relative(ctx.cwd, item.path)}`);
|
|
15613
15855
|
}
|
|
15614
15856
|
return { exitCode: 0, diagnostics: [], data: summarizable(plan), text: lines };
|
|
15615
15857
|
}
|
|
@@ -15672,7 +15914,7 @@ function renderRollback(report, ctx) {
|
|
|
15672
15914
|
}
|
|
15673
15915
|
|
|
15674
15916
|
// src/commands/validate.ts
|
|
15675
|
-
import
|
|
15917
|
+
import path44 from "path";
|
|
15676
15918
|
async function runValidate(ctx) {
|
|
15677
15919
|
const { root, workspace, config, approvals } = await requireWorkspace(ctx);
|
|
15678
15920
|
const slug = flagString(ctx.flags, "change");
|
|
@@ -15699,7 +15941,7 @@ async function runValidate(ctx) {
|
|
|
15699
15941
|
for (const finding of evaluation.lintFindings) {
|
|
15700
15942
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${finding.line ? `:${finding.line}` : ""} \u2014 ${finding.message}`);
|
|
15701
15943
|
}
|
|
15702
|
-
lines.push(` archivo: ${
|
|
15944
|
+
lines.push(` archivo: ${path44.relative(ctx.cwd, path44.join(change.dir, "spec.md"))}`);
|
|
15703
15945
|
}
|
|
15704
15946
|
if (targets.length === 0) lines.push("Sin cambios activos.");
|
|
15705
15947
|
if (advisory && advisory.plan.pending.length > 0) {
|
|
@@ -15727,7 +15969,7 @@ async function runValidate(ctx) {
|
|
|
15727
15969
|
}
|
|
15728
15970
|
|
|
15729
15971
|
// src/commands/verify.ts
|
|
15730
|
-
import
|
|
15972
|
+
import path45 from "path";
|
|
15731
15973
|
async function runVerify(ctx) {
|
|
15732
15974
|
const { root, workspace } = await requireWorkspace(ctx);
|
|
15733
15975
|
const slug = ctx.positionals[0];
|
|
@@ -15770,7 +16012,7 @@ async function runVerify(ctx) {
|
|
|
15770
16012
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-VERIFY-001", severity: "error", message: "Falta --by <nombre> (o define meta.owner)" }] };
|
|
15771
16013
|
}
|
|
15772
16014
|
const profilesDir = await resolveProfilesDir();
|
|
15773
|
-
const profile = await loadActiveProfile(
|
|
16015
|
+
const profile = await loadActiveProfile(path45.join(root, ".sdd"), [profilesDir ?? "", path45.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
|
|
15774
16016
|
const record = await recordEvidence({
|
|
15775
16017
|
root,
|
|
15776
16018
|
slug,
|
|
@@ -15792,7 +16034,7 @@ async function runVerify(ctx) {
|
|
|
15792
16034
|
if (record.evidence.command) lines.push(` comando: ${record.evidence.command}`);
|
|
15793
16035
|
lines.push(` resultado: ${record.evidence.result}`);
|
|
15794
16036
|
if (record.evidence.outputHash) lines.push(` hash: ${record.evidence.outputHash}`);
|
|
15795
|
-
lines.push(` archivo: ${
|
|
16037
|
+
lines.push(` archivo: ${path45.relative(ctx.cwd, record.path)}`);
|
|
15796
16038
|
if (record.stdout) {
|
|
15797
16039
|
lines.push("");
|
|
15798
16040
|
lines.push(" salida (\xFAltimas l\xEDneas):");
|
|
@@ -15807,7 +16049,7 @@ async function runVerify(ctx) {
|
|
|
15807
16049
|
return {
|
|
15808
16050
|
exitCode: record.exitCode === 0 ? 0 : 1,
|
|
15809
16051
|
diagnostics: record.diagnostics,
|
|
15810
|
-
data: { evidence: record.evidence, path:
|
|
16052
|
+
data: { evidence: record.evidence, path: path45.relative(ctx.cwd, record.path), exitCode: record.exitCode },
|
|
15811
16053
|
text: lines
|
|
15812
16054
|
};
|
|
15813
16055
|
}
|
|
@@ -15907,7 +16149,7 @@ async function requireWorkspace(ctx) {
|
|
|
15907
16149
|
throw new CliError("ATLAS-WS-001", msg("cli.noWorkspace", ctx.language), 2);
|
|
15908
16150
|
}
|
|
15909
16151
|
const { workspace, config } = await loadWorkspace(root);
|
|
15910
|
-
const approvals = await loadApprovals(
|
|
16152
|
+
const approvals = await loadApprovals(path46.join(root, ".sdd"));
|
|
15911
16153
|
return { root, workspace, config, approvals: approvals.byArtifact };
|
|
15912
16154
|
}
|
|
15913
16155
|
var CliError = class extends Error {
|
|
@@ -15974,7 +16216,7 @@ function printResult(command, result, json2, language) {
|
|
|
15974
16216
|
}
|
|
15975
16217
|
const lines = [...result.text ?? []];
|
|
15976
16218
|
for (const d of result.diagnostics) {
|
|
15977
|
-
const location = d.path ? ` ${
|
|
16219
|
+
const location = d.path ? ` ${path46.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
|
|
15978
16220
|
lines.push(`${d.severity === "error" ? "ERROR" : d.severity === "warning" ? "AVISO" : "NOTA"} ${d.code}${location} \u2014 ${d.message}`);
|
|
15979
16221
|
if (d.suggestion) lines.push(` \u21B3 ${d.suggestion}`);
|
|
15980
16222
|
}
|