specatlas 0.1.27 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +482 -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,110 @@ 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
|
+
};
|
|
10448
|
+
if (typeof data["domain"] === "string") fix.domain = data["domain"];
|
|
10449
|
+
if (typeof data["title"] === "string") fix.title = data["title"];
|
|
10450
|
+
return fix;
|
|
10451
|
+
}
|
|
10452
|
+
async function loadLivingFixes(root) {
|
|
10453
|
+
const dir = path4.join(root, LIVING_FIXES_DIR);
|
|
10454
|
+
const entries = (await listDir(dir)).filter((entry) => entry.toLowerCase().endsWith(".md")).sort();
|
|
10455
|
+
const fixes = [];
|
|
10456
|
+
for (const entry of entries) {
|
|
10457
|
+
const file = path4.join(dir, entry);
|
|
10458
|
+
const content = await readTextIfExists(file);
|
|
10459
|
+
if (content === void 0) continue;
|
|
10460
|
+
fixes.push(parseLivingFix(content, file));
|
|
10461
|
+
}
|
|
10462
|
+
return fixes.sort((a, b) => b.date.localeCompare(a.date) || b.slug.localeCompare(a.slug));
|
|
10463
|
+
}
|
|
10464
|
+
async function writeLivingFix(root, input) {
|
|
10465
|
+
const dir = path4.join(root, LIVING_FIXES_DIR);
|
|
10466
|
+
const month = input.date.slice(0, 7);
|
|
10467
|
+
const file = path4.join(dir, `${month}-${input.slug}.md`);
|
|
10468
|
+
const relativePath = toPosix(path4.relative(root, file));
|
|
10469
|
+
if (await exists(file)) return { file, relativePath, created: false };
|
|
10470
|
+
const data = {
|
|
10471
|
+
slug: input.slug,
|
|
10472
|
+
date: input.date,
|
|
10473
|
+
result: input.result ?? "pass"
|
|
10474
|
+
};
|
|
10475
|
+
if (input.domain) data["domain"] = input.domain;
|
|
10476
|
+
if (input.title) data["title"] = input.title;
|
|
10477
|
+
if (input.covers && input.covers.length > 0) data["covers"] = input.covers;
|
|
10478
|
+
const body = input.content.replace(/^[\r\n]+/, "").trimEnd();
|
|
10479
|
+
const text = `---
|
|
10480
|
+
${stringifyYaml3(data, { lineWidth: 120 })}---
|
|
10481
|
+
|
|
10482
|
+
${body}
|
|
10483
|
+
`;
|
|
10484
|
+
await writeText(file, text);
|
|
10485
|
+
return { file, relativePath, created: true };
|
|
10486
|
+
}
|
|
10399
10487
|
var SDD_DIR = ".sdd";
|
|
10400
10488
|
async function findWorkspaceRoot(start) {
|
|
10401
|
-
let current =
|
|
10489
|
+
let current = path5.resolve(start);
|
|
10402
10490
|
for (let i = 0; i < 40; i += 1) {
|
|
10403
|
-
if (await isDirectory(
|
|
10404
|
-
const parent =
|
|
10491
|
+
if (await isDirectory(path5.join(current, SDD_DIR))) return current;
|
|
10492
|
+
const parent = path5.dirname(current);
|
|
10405
10493
|
if (parent === current) return void 0;
|
|
10406
10494
|
current = parent;
|
|
10407
10495
|
}
|
|
10408
10496
|
return void 0;
|
|
10409
10497
|
}
|
|
10410
10498
|
async function loadApprovals(sddDir) {
|
|
10411
|
-
const file =
|
|
10499
|
+
const file = path5.join(sddDir, "approvals.yaml");
|
|
10412
10500
|
const raw = await readTextIfExists(file);
|
|
10413
10501
|
if (raw === void 0) return { byArtifact: /* @__PURE__ */ new Map(), diagnostics: [] };
|
|
10414
10502
|
const parsed = parseApprovals(raw, file);
|
|
10415
10503
|
const map = /* @__PURE__ */ new Map();
|
|
10416
10504
|
for (const a of parsed.approvals?.approvals ?? []) {
|
|
10417
|
-
map.set(a.artifact.split(
|
|
10505
|
+
map.set(a.artifact.split(path5.sep).join("/"), { hash: a.artifactHash, by: a.approvedBy, at: a.approvedAt });
|
|
10418
10506
|
}
|
|
10419
10507
|
return { byArtifact: map, diagnostics: parsed.diagnostics };
|
|
10420
10508
|
}
|
|
10421
10509
|
async function loadSpecs(sddDir) {
|
|
10422
|
-
const specsDir =
|
|
10510
|
+
const specsDir = path5.join(sddDir, "specs");
|
|
10423
10511
|
const domains = await listDirs(specsDir);
|
|
10424
10512
|
const out = [];
|
|
10425
10513
|
for (const domain of domains) {
|
|
10426
|
-
const file =
|
|
10514
|
+
const file = path5.join(specsDir, domain, "spec.md");
|
|
10427
10515
|
if (!await exists(file)) continue;
|
|
10428
10516
|
const content = await readText(file);
|
|
10429
10517
|
out.push({ domain, path: file, spec: parseSpecFile(content, file) });
|
|
@@ -10431,10 +10519,10 @@ async function loadSpecs(sddDir) {
|
|
|
10431
10519
|
return out;
|
|
10432
10520
|
}
|
|
10433
10521
|
async function loadChange(root, slug, relDir) {
|
|
10434
|
-
const dir =
|
|
10522
|
+
const dir = path5.join(root, SDD_DIR, "changes", relDir ?? slug);
|
|
10435
10523
|
const diagnostics = [];
|
|
10436
10524
|
const change = { slug, dir, diagnostics };
|
|
10437
|
-
const metaFile =
|
|
10525
|
+
const metaFile = path5.join(dir, "meta.yaml");
|
|
10438
10526
|
const metaRaw = await readTextIfExists(metaFile);
|
|
10439
10527
|
if (metaRaw === void 0) {
|
|
10440
10528
|
diagnostics.push(diag("ATLAS-FILES-001", "error", `El cambio "${slug}" no tiene meta.yaml`, { path: metaFile, suggestion: "Crea meta.yaml con slug, lane y dominio" }));
|
|
@@ -10443,7 +10531,7 @@ async function loadChange(root, slug, relDir) {
|
|
|
10443
10531
|
diagnostics.push(...parsed.diagnostics);
|
|
10444
10532
|
if (parsed.meta) change.meta = parsed.meta;
|
|
10445
10533
|
}
|
|
10446
|
-
const deltaFile =
|
|
10534
|
+
const deltaFile = path5.join(dir, "spec.md");
|
|
10447
10535
|
const deltaRaw = await readTextIfExists(deltaFile);
|
|
10448
10536
|
if (deltaRaw !== void 0) {
|
|
10449
10537
|
change.delta = parseDelta(deltaRaw, deltaFile);
|
|
@@ -10451,41 +10539,42 @@ async function loadChange(root, slug, relDir) {
|
|
|
10451
10539
|
} else {
|
|
10452
10540
|
diagnostics.push(diag("ATLAS-FILES-002", "warning", `El cambio "${slug}" no tiene spec.md (delta)`, { path: deltaFile }));
|
|
10453
10541
|
}
|
|
10454
|
-
const planFile =
|
|
10542
|
+
const planFile = path5.join(dir, "plan.md");
|
|
10455
10543
|
if (await exists(planFile)) change.planPath = planFile;
|
|
10456
|
-
const reviewFile =
|
|
10544
|
+
const reviewFile = path5.join(dir, "review.md");
|
|
10457
10545
|
if (await exists(reviewFile)) change.reviewPath = reviewFile;
|
|
10458
|
-
const presentationFile =
|
|
10546
|
+
const presentationFile = path5.join(dir, "presentation", "index.html");
|
|
10459
10547
|
if (await exists(presentationFile)) change.presentationPath = presentationFile;
|
|
10460
|
-
const tasksFile =
|
|
10548
|
+
const tasksFile = path5.join(dir, "tasks.md");
|
|
10461
10549
|
const tasksRaw = await readTextIfExists(tasksFile);
|
|
10462
10550
|
if (tasksRaw !== void 0) {
|
|
10463
10551
|
change.tasks = parseTasksFile(tasksRaw, tasksFile);
|
|
10464
10552
|
diagnostics.push(...change.tasks.diagnostics);
|
|
10465
10553
|
}
|
|
10466
|
-
const verifyFile =
|
|
10554
|
+
const verifyFile = path5.join(dir, "verify.md");
|
|
10467
10555
|
const verifyRaw = await readTextIfExists(verifyFile);
|
|
10468
10556
|
if (verifyRaw !== void 0) {
|
|
10469
10557
|
change.verify = parseVerifyFile(verifyRaw, verifyFile);
|
|
10470
10558
|
diagnostics.push(...change.verify.diagnostics);
|
|
10471
10559
|
}
|
|
10472
|
-
const fixFile =
|
|
10560
|
+
const fixFile = path5.join(dir, "fix.md");
|
|
10473
10561
|
const fixRaw = await readTextIfExists(fixFile);
|
|
10474
10562
|
if (fixRaw !== void 0) {
|
|
10475
10563
|
change.fix = parseVerifyFile(fixRaw, fixFile);
|
|
10476
10564
|
diagnostics.push(...change.fix.diagnostics);
|
|
10565
|
+
change.fixCovers = parseFixCovers(fixRaw);
|
|
10477
10566
|
}
|
|
10478
|
-
const mockupManifest =
|
|
10567
|
+
const mockupManifest = path5.join(dir, "mockups", "manifest.yaml");
|
|
10479
10568
|
if (await exists(mockupManifest)) change.mockupManifestPath = mockupManifest;
|
|
10480
10569
|
return change;
|
|
10481
10570
|
}
|
|
10482
10571
|
async function listChangeSlugs(root, opts = {}) {
|
|
10483
|
-
const changesDir =
|
|
10572
|
+
const changesDir = path5.join(root, SDD_DIR, "changes");
|
|
10484
10573
|
const dirs = await listDirs(changesDir);
|
|
10485
10574
|
return dirs.filter((d) => opts.includeArchived ? true : d !== "archive");
|
|
10486
10575
|
}
|
|
10487
10576
|
async function loadWorkspace(root) {
|
|
10488
|
-
const sddDir =
|
|
10577
|
+
const sddDir = path5.join(root, SDD_DIR);
|
|
10489
10578
|
const { config, diagnostics: configDiags } = await loadConfig(sddDir);
|
|
10490
10579
|
const diagnostics = [...configDiags];
|
|
10491
10580
|
const specs = await loadSpecs(sddDir);
|
|
@@ -10498,20 +10587,20 @@ async function loadWorkspace(root) {
|
|
|
10498
10587
|
diagnostics.push(...change.diagnostics);
|
|
10499
10588
|
}
|
|
10500
10589
|
const archived = [];
|
|
10501
|
-
const archivedDir =
|
|
10590
|
+
const archivedDir = path5.join(sddDir, "changes", "archive");
|
|
10502
10591
|
if (await isDirectory(archivedDir)) {
|
|
10503
10592
|
for (const entry of await listDirs(archivedDir)) {
|
|
10504
|
-
archived.push(await loadChange(root, entry.replace(/^\d{4}-\d{2}-/, ""),
|
|
10593
|
+
archived.push(await loadChange(root, entry.replace(/^\d{4}-\d{2}-/, ""), path5.join("archive", entry)));
|
|
10505
10594
|
}
|
|
10506
10595
|
}
|
|
10507
10596
|
return { workspace: { root, sddDir, specs, changes, archived, diagnostics }, config };
|
|
10508
10597
|
}
|
|
10509
10598
|
async function ensureSddDirs(sddDir) {
|
|
10510
10599
|
const created = [];
|
|
10511
|
-
const dirs = ["specs", "changes", "runs", "metrics",
|
|
10600
|
+
const dirs = ["specs", "changes", "runs", "metrics", "fixes", path5.join("profiles", "custom")];
|
|
10512
10601
|
const { ensureDir: ensureDir2 } = await import("./fsx-VF2P7ALA-XKEWTMDH.js");
|
|
10513
10602
|
for (const d of dirs) {
|
|
10514
|
-
const full =
|
|
10603
|
+
const full = path5.join(sddDir, d);
|
|
10515
10604
|
if (!await exists(full)) {
|
|
10516
10605
|
await ensureDir2(full);
|
|
10517
10606
|
created.push(full);
|
|
@@ -10679,6 +10768,10 @@ notes: 12/12 casos
|
|
|
10679
10768
|
|
|
10680
10769
|
## Evidencia
|
|
10681
10770
|
|
|
10771
|
+
<!-- Opcional: declara los requisitos que este fix afecta (aparecen en su trazabilidad).
|
|
10772
|
+
Cubre: REQ-DOMINIO-001
|
|
10773
|
+
-->
|
|
10774
|
+
|
|
10682
10775
|
<!-- Registra la evidencia real con:
|
|
10683
10776
|
satlas verify <slug> --file fix --scenario REQ-DOMINIO-001-S1 --command "<comando>" --by "<nombre>"
|
|
10684
10777
|
o, si es manual:
|
|
@@ -10802,6 +10895,10 @@ by: your-name
|
|
|
10802
10895
|
## Rollback
|
|
10803
10896
|
## Evidence
|
|
10804
10897
|
|
|
10898
|
+
<!-- Optional: declare the requirements this fix affects (they show up in its traceability).
|
|
10899
|
+
Cubre: REQ-DOMAIN-001
|
|
10900
|
+
-->
|
|
10901
|
+
|
|
10805
10902
|
<!-- Register real evidence with:
|
|
10806
10903
|
satlas verify <slug> --file fix --scenario REQ-DOMAIN-001-S1 --command "<command>" --by "<name>"
|
|
10807
10904
|
-->
|
|
@@ -10826,7 +10923,7 @@ function changeMetaYaml(meta) {
|
|
|
10826
10923
|
if (meta.owner) doc["owner"] = meta.owner;
|
|
10827
10924
|
if (meta.mockups) doc["mockups"] = meta.mockups;
|
|
10828
10925
|
return `# Estado del cambio. La fase se DERIVA de los artefactos; aqu\xED solo hechos.
|
|
10829
|
-
` +
|
|
10926
|
+
` + stringifyYaml4(doc, { lineWidth: 120 });
|
|
10830
10927
|
}
|
|
10831
10928
|
function indexMarkdown(input) {
|
|
10832
10929
|
const es = input.language !== "en";
|
|
@@ -10840,6 +10937,15 @@ function indexMarkdown(input) {
|
|
|
10840
10937
|
if (input.specs.length === 0) lines.push(es ? "_Sin specs todav\xEDa._" : "_No specs yet._");
|
|
10841
10938
|
for (const spec of input.specs) lines.push(`- \`${spec.domain}\` \u2014 ${spec.requirements} ${es ? "requisitos" : "requirements"}`);
|
|
10842
10939
|
lines.push("");
|
|
10940
|
+
lines.push(es ? "## Fixes vivos" : "## Living fixes");
|
|
10941
|
+
lines.push("");
|
|
10942
|
+
const fixes = input.fixes ?? [];
|
|
10943
|
+
if (fixes.length === 0) lines.push(es ? "_Sin fixes archivados todav\xEDa._" : "_No archived fixes yet._");
|
|
10944
|
+
for (const fix of fixes) {
|
|
10945
|
+
const parts = [fix.date, fix.domain ? `\`${fix.domain}\`` : "", `\`${fix.slug}\``, fix.result].filter((part) => part !== "");
|
|
10946
|
+
lines.push(`- ${parts.join(" \xB7 ")}`);
|
|
10947
|
+
}
|
|
10948
|
+
lines.push("");
|
|
10843
10949
|
lines.push(es ? "## Cambios activos" : "## Active changes");
|
|
10844
10950
|
lines.push("");
|
|
10845
10951
|
if (input.changes.length === 0) lines.push(es ? "_Sin cambios activos._" : "_No active changes._");
|
|
@@ -10991,8 +11097,8 @@ function issueBody(input) {
|
|
|
10991
11097
|
lines.push(`- Siguiente: \`${input.next}\` \u2014 ${input.nextDescription}`);
|
|
10992
11098
|
lines.push("");
|
|
10993
11099
|
lines.push("### Artefactos");
|
|
10994
|
-
lines.push(`- Spec: \`${
|
|
10995
|
-
if (change.planPath) lines.push(`- Plan: \`${
|
|
11100
|
+
lines.push(`- Spec: \`${path6.posix.join(".sdd", "changes", change.slug, "spec.md")}\``);
|
|
11101
|
+
if (change.planPath) lines.push(`- Plan: \`${path6.posix.join(".sdd", "changes", change.slug, "plan.md")}\``);
|
|
10996
11102
|
if (change.tasks) lines.push(`- Tareas: ${change.tasks.counts.done}/${change.tasks.counts.total}`);
|
|
10997
11103
|
if (change.verify) lines.push(`- Evidencia registrada: ${change.verify.evidence.length}`);
|
|
10998
11104
|
lines.push("");
|
|
@@ -11056,10 +11162,10 @@ async function issueLabels(root, number, runner = defaultRunner) {
|
|
|
11056
11162
|
}
|
|
11057
11163
|
}
|
|
11058
11164
|
async function patchChangeMeta(root, slug, patch) {
|
|
11059
|
-
const file =
|
|
11165
|
+
const file = path6.join(path6.resolve(root), ".sdd", "changes", slug, "meta.yaml");
|
|
11060
11166
|
const raw = await readTextIfExists(file);
|
|
11061
11167
|
if (raw === void 0) {
|
|
11062
|
-
return { path: file, diagnostics: [diag("ATLAS-GH-007", "error", `No existe ${
|
|
11168
|
+
return { path: file, diagnostics: [diag("ATLAS-GH-007", "error", `No existe ${path6.relative(root, file)}`)] };
|
|
11063
11169
|
}
|
|
11064
11170
|
let data;
|
|
11065
11171
|
try {
|
|
@@ -11113,7 +11219,7 @@ async function approveFromGithub(opts) {
|
|
|
11113
11219
|
const { signApproval: signApproval2 } = await import("./approvals-2PO6223I-DENKLEXU.js");
|
|
11114
11220
|
const signed = await signApproval2({
|
|
11115
11221
|
root: opts.root,
|
|
11116
|
-
artifact:
|
|
11222
|
+
artifact: path6.posix.join("changes", opts.slug, "spec.md"),
|
|
11117
11223
|
by,
|
|
11118
11224
|
channel: "tracker",
|
|
11119
11225
|
note: `Aprobado v\xEDa GitHub issue #${number} (etiqueta "${label}")`
|
|
@@ -11208,10 +11314,10 @@ function buildEvidenceYaml(evidence) {
|
|
|
11208
11314
|
return lines.join("\n");
|
|
11209
11315
|
}
|
|
11210
11316
|
async function recordEvidence(opts) {
|
|
11211
|
-
const root =
|
|
11317
|
+
const root = path7.resolve(opts.root);
|
|
11212
11318
|
const slug = opts.slug;
|
|
11213
11319
|
const fileKind = opts.file ?? "verify";
|
|
11214
|
-
const file =
|
|
11320
|
+
const file = path7.join(root, ".sdd", "changes", slug, fileKind === "fix" ? "fix.md" : "verify.md");
|
|
11215
11321
|
const diagnostics = [];
|
|
11216
11322
|
const scenario = opts.scenario.toUpperCase();
|
|
11217
11323
|
if (!SCENARIO_ID_RE.test(scenario)) {
|
|
@@ -11301,7 +11407,7 @@ function generateRunId(now = /* @__PURE__ */ new Date()) {
|
|
|
11301
11407
|
return `${iso.slice(0, 8)}-${iso.slice(8, 14)}-${randomBytes(3).toString("hex")}`;
|
|
11302
11408
|
}
|
|
11303
11409
|
function runsDir(root) {
|
|
11304
|
-
return
|
|
11410
|
+
return path8.join(path8.resolve(root), ".sdd", "runs");
|
|
11305
11411
|
}
|
|
11306
11412
|
async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Date()) {
|
|
11307
11413
|
const runId = generateRunId(now);
|
|
@@ -11314,25 +11420,25 @@ async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Da
|
|
|
11314
11420
|
updatedAt: localStamp(now)
|
|
11315
11421
|
};
|
|
11316
11422
|
if (inputs) state.inputs = inputs;
|
|
11317
|
-
const dir =
|
|
11423
|
+
const dir = path8.join(runsDir(root), runId);
|
|
11318
11424
|
await ensureDir(dir);
|
|
11319
|
-
await writeText(
|
|
11425
|
+
await writeText(path8.join(dir, "state.json"), `${JSON.stringify(state, null, 2)}
|
|
11320
11426
|
`);
|
|
11321
|
-
await writeText(
|
|
11427
|
+
await writeText(path8.join(dir, "events.jsonl"), "");
|
|
11322
11428
|
await appendRunEvent(root, runId, "run_started", { slug, phase });
|
|
11323
11429
|
return state;
|
|
11324
11430
|
}
|
|
11325
11431
|
async function appendRunEvent(root, runId, type, data, now = /* @__PURE__ */ new Date()) {
|
|
11326
11432
|
const event = { eventId: randomBytes(4).toString("hex"), at: localStamp(now), type };
|
|
11327
11433
|
if (data) event.data = data;
|
|
11328
|
-
const file =
|
|
11434
|
+
const file = path8.join(runsDir(root), runId, "events.jsonl");
|
|
11329
11435
|
const previous = await readTextIfExists(file) ?? "";
|
|
11330
11436
|
await writeText(file, `${previous}${JSON.stringify(event)}
|
|
11331
11437
|
`);
|
|
11332
11438
|
return event;
|
|
11333
11439
|
}
|
|
11334
11440
|
async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Date()) {
|
|
11335
|
-
const file =
|
|
11441
|
+
const file = path8.join(runsDir(root), runId, "state.json");
|
|
11336
11442
|
const raw = await readTextIfExists(file);
|
|
11337
11443
|
if (raw === void 0) return;
|
|
11338
11444
|
const state = JSON.parse(raw);
|
|
@@ -11342,13 +11448,13 @@ async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Da
|
|
|
11342
11448
|
`);
|
|
11343
11449
|
}
|
|
11344
11450
|
async function readRun(root, runId) {
|
|
11345
|
-
const dir =
|
|
11346
|
-
const stateRaw = await readTextIfExists(
|
|
11451
|
+
const dir = path8.join(runsDir(root), runId);
|
|
11452
|
+
const stateRaw = await readTextIfExists(path8.join(dir, "state.json"));
|
|
11347
11453
|
if (stateRaw === void 0) {
|
|
11348
11454
|
return { diagnostics: [diag("ATLAS-RUN-001", "error", `No existe el run ${runId}`, { path: dir })] };
|
|
11349
11455
|
}
|
|
11350
11456
|
const state = JSON.parse(stateRaw);
|
|
11351
|
-
const eventsRaw = await readTextIfExists(
|
|
11457
|
+
const eventsRaw = await readTextIfExists(path8.join(dir, "events.jsonl")) ?? "";
|
|
11352
11458
|
const events = [];
|
|
11353
11459
|
for (const line of eventsRaw.split("\n")) {
|
|
11354
11460
|
if (line.trim() === "") continue;
|
|
@@ -11365,7 +11471,7 @@ async function listRuns(root, filter = {}) {
|
|
|
11365
11471
|
const ids = await listDirs(dir);
|
|
11366
11472
|
const out = [];
|
|
11367
11473
|
for (const id of ids) {
|
|
11368
|
-
const raw = await readTextIfExists(
|
|
11474
|
+
const raw = await readTextIfExists(path8.join(dir, id, "state.json"));
|
|
11369
11475
|
if (raw === void 0) continue;
|
|
11370
11476
|
try {
|
|
11371
11477
|
const state = JSON.parse(raw);
|
|
@@ -11400,20 +11506,20 @@ var mockupManifestSchema = z4.object({
|
|
|
11400
11506
|
});
|
|
11401
11507
|
var TOKEN_CANDIDATES = ["design/tokens.json", "DESIGN.md", "design/DESIGN.md", ".sdd/design/tokens.json"];
|
|
11402
11508
|
function mockupsDir(root, slug) {
|
|
11403
|
-
return
|
|
11509
|
+
return path9.join(path9.resolve(root), ".sdd", "changes", slug, "mockups");
|
|
11404
11510
|
}
|
|
11405
11511
|
async function tokensFile(root) {
|
|
11406
11512
|
for (const candidate of TOKEN_CANDIDATES) {
|
|
11407
|
-
const abs =
|
|
11513
|
+
const abs = path9.join(root, candidate);
|
|
11408
11514
|
if (await exists(abs)) return candidate;
|
|
11409
11515
|
}
|
|
11410
11516
|
return void 0;
|
|
11411
11517
|
}
|
|
11412
11518
|
async function computeInputsHash(root, change) {
|
|
11413
|
-
const deltaPath =
|
|
11519
|
+
const deltaPath = path9.join(change.dir, "spec.md");
|
|
11414
11520
|
const delta = await readTextIfExists(deltaPath) ?? "";
|
|
11415
11521
|
const tokens = await tokensFile(root);
|
|
11416
|
-
const tokensContent = tokens ? await readTextIfExists(
|
|
11522
|
+
const tokensContent = tokens ? await readTextIfExists(path9.join(root, tokens)) ?? "" : "";
|
|
11417
11523
|
return artifactHash(`${delta}
|
|
11418
11524
|
---tokens---
|
|
11419
11525
|
${tokensContent}`);
|
|
@@ -11438,7 +11544,7 @@ function planMockups(change, platform = "web") {
|
|
|
11438
11544
|
return { platform, screens };
|
|
11439
11545
|
}
|
|
11440
11546
|
async function readMockupManifest(root, slug) {
|
|
11441
|
-
const file =
|
|
11547
|
+
const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
|
|
11442
11548
|
const raw = await readTextIfExists(file);
|
|
11443
11549
|
if (raw === void 0) return { diagnostics: [] };
|
|
11444
11550
|
let data;
|
|
@@ -11530,7 +11636,7 @@ async function checkMockups(root, slug, change) {
|
|
|
11530
11636
|
const { manifest, path: manifestPath, diagnostics } = await readMockupManifest(root, slug);
|
|
11531
11637
|
const findings = [...diagnostics];
|
|
11532
11638
|
if (!manifest || !manifestPath) {
|
|
11533
|
-
return { findings: [...findings, diag("LINT-MKP-004", "error", "No existe mockups/manifest.yaml", { path:
|
|
11639
|
+
return { findings: [...findings, diag("LINT-MKP-004", "error", "No existe mockups/manifest.yaml", { path: path9.join(dir, "manifest.yaml") })], stale: false };
|
|
11534
11640
|
}
|
|
11535
11641
|
const known = /* @__PURE__ */ new Set();
|
|
11536
11642
|
for (const req of [...change.delta?.added ?? [], ...change.delta?.modified ?? []]) {
|
|
@@ -11538,12 +11644,12 @@ async function checkMockups(root, slug, change) {
|
|
|
11538
11644
|
}
|
|
11539
11645
|
findings.push(...lintMockupManifest(manifest, known, manifestPath));
|
|
11540
11646
|
for (const screen of manifest.screens) {
|
|
11541
|
-
const html = await readTextIfExists(
|
|
11647
|
+
const html = await readTextIfExists(path9.join(dir, screen.file));
|
|
11542
11648
|
if (html === void 0) {
|
|
11543
|
-
findings.push(diag("ATLAS-MKP-004", "error", `Falta el archivo del mockup: ${screen.file}`, { path:
|
|
11649
|
+
findings.push(diag("ATLAS-MKP-004", "error", `Falta el archivo del mockup: ${screen.file}`, { path: path9.join(dir, screen.file) }));
|
|
11544
11650
|
continue;
|
|
11545
11651
|
}
|
|
11546
|
-
findings.push(...lintMockupHtml(html,
|
|
11652
|
+
findings.push(...lintMockupHtml(html, path9.join(dir, screen.file)));
|
|
11547
11653
|
}
|
|
11548
11654
|
const inputsHash = await computeInputsHash(root, change);
|
|
11549
11655
|
const stale = manifest.inputsHash !== void 0 && manifest.inputsHash !== inputsHash;
|
|
@@ -11558,7 +11664,7 @@ async function checkMockups(root, slug, change) {
|
|
|
11558
11664
|
return { findings, stale, manifest, manifestPath };
|
|
11559
11665
|
}
|
|
11560
11666
|
async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date()) {
|
|
11561
|
-
const file =
|
|
11667
|
+
const file = path9.join(mockupsDir(root, slug), "plan.yaml");
|
|
11562
11668
|
if (await exists(file)) return file;
|
|
11563
11669
|
const doc = {
|
|
11564
11670
|
schema_version: 1,
|
|
@@ -11566,11 +11672,11 @@ async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date(
|
|
|
11566
11672
|
platform: plan.platform,
|
|
11567
11673
|
screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints }))
|
|
11568
11674
|
};
|
|
11569
|
-
await writeText(file,
|
|
11675
|
+
await writeText(file, stringifyYaml5(doc, { lineWidth: 120 }));
|
|
11570
11676
|
return file;
|
|
11571
11677
|
}
|
|
11572
11678
|
async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PURE__ */ new Date()) {
|
|
11573
|
-
const file =
|
|
11679
|
+
const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
|
|
11574
11680
|
if (await exists(file)) return file;
|
|
11575
11681
|
const doc = {
|
|
11576
11682
|
schema_version: 1,
|
|
@@ -11582,16 +11688,16 @@ async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PUR
|
|
|
11582
11688
|
screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints })),
|
|
11583
11689
|
screenshots: []
|
|
11584
11690
|
};
|
|
11585
|
-
await writeText(file,
|
|
11691
|
+
await writeText(file, stringifyYaml5(doc, { lineWidth: 120 }));
|
|
11586
11692
|
return file;
|
|
11587
11693
|
}
|
|
11588
11694
|
async function updateMockupScreenshots(root, slug, screenshots) {
|
|
11589
|
-
const file =
|
|
11695
|
+
const file = path9.join(mockupsDir(root, slug), "manifest.yaml");
|
|
11590
11696
|
const raw = await readTextIfExists(file);
|
|
11591
11697
|
if (raw === void 0) return void 0;
|
|
11592
11698
|
const data = parseYaml6(raw) ?? {};
|
|
11593
11699
|
data["screenshots"] = screenshots;
|
|
11594
|
-
await writeText(file,
|
|
11700
|
+
await writeText(file, stringifyYaml5(data, { lineWidth: 120 }));
|
|
11595
11701
|
return file;
|
|
11596
11702
|
}
|
|
11597
11703
|
async function captureMockups(root, slug, manifest, now = /* @__PURE__ */ new Date()) {
|
|
@@ -11616,9 +11722,9 @@ async function captureMockups(root, slug, manifest, now = /* @__PURE__ */ new Da
|
|
|
11616
11722
|
const page2 = await browser.newPage();
|
|
11617
11723
|
for (const bp of screen.breakpoints && screen.breakpoints.length > 0 ? screen.breakpoints : [1440]) {
|
|
11618
11724
|
await page2.setViewportSize({ width: bp, height: 900 });
|
|
11619
|
-
await page2.goto(`file://${
|
|
11620
|
-
const rel =
|
|
11621
|
-
await page2.screenshot({ path:
|
|
11725
|
+
await page2.goto(`file://${path9.join(dir, screen.file).split(path9.sep).join("/")}`);
|
|
11726
|
+
const rel = path9.posix.join("screens", `${screen.id}-${bp}.png`);
|
|
11727
|
+
await page2.screenshot({ path: path9.join(dir, rel) });
|
|
11622
11728
|
screenshots.push(rel);
|
|
11623
11729
|
}
|
|
11624
11730
|
await page2.close();
|
|
@@ -11634,7 +11740,7 @@ async function mockupsReady(root, slug, change) {
|
|
|
11634
11740
|
return Boolean(check.manifest && check.manifest.screens.length > 0 && !check.stale);
|
|
11635
11741
|
}
|
|
11636
11742
|
async function setMockupRequirement(root, slug, value) {
|
|
11637
|
-
const file =
|
|
11743
|
+
const file = path9.join(root, ".sdd", "changes", slug, "meta.yaml");
|
|
11638
11744
|
const raw = await readTextIfExists(file) ?? `schema_version: 1
|
|
11639
11745
|
slug: ${slug}
|
|
11640
11746
|
lane: standard
|
|
@@ -11864,12 +11970,12 @@ ${texts.scenarios}`);
|
|
|
11864
11970
|
}
|
|
11865
11971
|
}
|
|
11866
11972
|
async function loadProjectPacks(sddDir) {
|
|
11867
|
-
const dir =
|
|
11973
|
+
const dir = path10.join(sddDir, "packs");
|
|
11868
11974
|
const diagnostics = [];
|
|
11869
11975
|
const packs = [];
|
|
11870
11976
|
for (const entry of await listDir(dir)) {
|
|
11871
11977
|
if (!/\.ya?ml$/i.test(entry)) continue;
|
|
11872
|
-
const file =
|
|
11978
|
+
const file = path10.join(dir, entry);
|
|
11873
11979
|
const raw = await readTextIfExists(file);
|
|
11874
11980
|
if (raw === void 0) continue;
|
|
11875
11981
|
let data;
|
|
@@ -11956,7 +12062,7 @@ function packFindings(evaluations, change) {
|
|
|
11956
12062
|
}
|
|
11957
12063
|
var UI_DOMAINS = /* @__PURE__ */ new Set(["frontend", "mobile", "fullstack"]);
|
|
11958
12064
|
async function runAnalyze(opts) {
|
|
11959
|
-
const root =
|
|
12065
|
+
const root = path11.resolve(opts.root);
|
|
11960
12066
|
const { workspace, config } = await loadWorkspace(root);
|
|
11961
12067
|
const change = workspace.changes.find((c) => c.slug === opts.slug);
|
|
11962
12068
|
if (!change) {
|
|
@@ -11974,7 +12080,7 @@ async function runAnalyze(opts) {
|
|
|
11974
12080
|
for (const req of spec.spec.requirements) living.set(req.id, req);
|
|
11975
12081
|
}
|
|
11976
12082
|
if (change.delta) {
|
|
11977
|
-
findings.push(...lintDelta(change.delta, living,
|
|
12083
|
+
findings.push(...lintDelta(change.delta, living, path11.join(change.dir, "spec.md"), { language: config.spec.language }));
|
|
11978
12084
|
}
|
|
11979
12085
|
const trace = checkTrace({ specs: workspace.specs, change, requireEvidence: false });
|
|
11980
12086
|
findings.push(...trace.findings);
|
|
@@ -12023,7 +12129,7 @@ async function runAnalyze(opts) {
|
|
|
12023
12129
|
if (mockups) result.mockups = mockups;
|
|
12024
12130
|
if (packs) result.packs = packs;
|
|
12025
12131
|
if (opts.write !== false) {
|
|
12026
|
-
const file =
|
|
12132
|
+
const file = path11.join(change.dir, "analyze.md");
|
|
12027
12133
|
await writeText(file, renderAnalyze(change, result, localStamp(opts.now)));
|
|
12028
12134
|
result.path = file;
|
|
12029
12135
|
}
|
|
@@ -12065,7 +12171,7 @@ function renderAnalyze(change, result, generatedAt) {
|
|
|
12065
12171
|
return lines.join("\n");
|
|
12066
12172
|
}
|
|
12067
12173
|
async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
|
|
12068
|
-
const resolved =
|
|
12174
|
+
const resolved = path12.resolve(root);
|
|
12069
12175
|
const { workspace, config } = await loadWorkspace(resolved);
|
|
12070
12176
|
const approvals = await loadApprovals(workspace.sddDir);
|
|
12071
12177
|
const living = /* @__PURE__ */ new Map();
|
|
@@ -12080,7 +12186,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
|
|
|
12080
12186
|
const deltaContent = await readDelta(change);
|
|
12081
12187
|
const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent);
|
|
12082
12188
|
const findings = [];
|
|
12083
|
-
if (change.delta) findings.push(...lintDelta(change.delta, living,
|
|
12189
|
+
if (change.delta) findings.push(...lintDelta(change.delta, living, path12.join(change.dir, "spec.md"), { language: config.spec.language }));
|
|
12084
12190
|
const trace = checkTrace({
|
|
12085
12191
|
specs: workspace.specs,
|
|
12086
12192
|
change,
|
|
@@ -12145,7 +12251,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
|
|
|
12145
12251
|
attention.push({ slug: change.slug, kind: "missing-evidence", detail: `evidencia ${change.scenariosPassed}/${change.scenariosTotal}` });
|
|
12146
12252
|
}
|
|
12147
12253
|
}
|
|
12148
|
-
const archiveDir =
|
|
12254
|
+
const archiveDir = path12.join(workspace.sddDir, "changes", "archive");
|
|
12149
12255
|
const archivedEntries = await listDirs(archiveDir);
|
|
12150
12256
|
const throughputByMonth = {};
|
|
12151
12257
|
for (const entry of archivedEntries) {
|
|
@@ -12183,10 +12289,10 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
|
|
|
12183
12289
|
}
|
|
12184
12290
|
async function readDelta(change) {
|
|
12185
12291
|
const { readTextIfExists: readTextIfExists2 } = await import("./fsx-VF2P7ALA-XKEWTMDH.js");
|
|
12186
|
-
return readTextIfExists2(
|
|
12292
|
+
return readTextIfExists2(path12.join(change.dir, "spec.md"));
|
|
12187
12293
|
}
|
|
12188
12294
|
async function generatePresentation(opts) {
|
|
12189
|
-
const root =
|
|
12295
|
+
const root = path13.resolve(opts.root);
|
|
12190
12296
|
const { workspace, config } = await loadWorkspace(root);
|
|
12191
12297
|
const language = config.project.language;
|
|
12192
12298
|
const change = workspace.changes.find((c) => c.slug === opts.slug);
|
|
@@ -12196,17 +12302,17 @@ async function generatePresentation(opts) {
|
|
|
12196
12302
|
if (!change.delta) {
|
|
12197
12303
|
return { slug: opts.slug, diagnostics: [diag("ATLAS-PRESENT-001", "error", `El cambio "${opts.slug}" no tiene spec.md (delta)`)] };
|
|
12198
12304
|
}
|
|
12199
|
-
const deltaPath =
|
|
12305
|
+
const deltaPath = path13.join(change.dir, "spec.md");
|
|
12200
12306
|
const deltaContent = await readTextIfExists(deltaPath) ?? "";
|
|
12201
12307
|
const hash = artifactHash(deltaContent);
|
|
12202
12308
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
12203
|
-
const presentationDir =
|
|
12204
|
-
const mockupsCopyDir =
|
|
12309
|
+
const presentationDir = path13.join(change.dir, "presentation");
|
|
12310
|
+
const mockupsCopyDir = path13.join(presentationDir, "mockups");
|
|
12205
12311
|
await ensureDir(presentationDir);
|
|
12206
12312
|
const mockupInfo = await copyMockups(root, change, mockupsCopyDir);
|
|
12207
|
-
const proposalRaw = await readTextIfExists(
|
|
12313
|
+
const proposalRaw = await readTextIfExists(path13.join(change.dir, "proposal.md"));
|
|
12208
12314
|
const proposalBody = proposalRaw ? parseFrontmatter(proposalRaw).body : "";
|
|
12209
|
-
const approvals = await loadApprovals(
|
|
12315
|
+
const approvals = await loadApprovals(path13.join(root, ".sdd"));
|
|
12210
12316
|
const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent);
|
|
12211
12317
|
const labels = labelsFor(language);
|
|
12212
12318
|
const html = page({
|
|
@@ -12227,7 +12333,7 @@ async function generatePresentation(opts) {
|
|
|
12227
12333
|
approveHint: `satlas approve ${change.slug} --by "<nombre>" --channel presentation`,
|
|
12228
12334
|
...approval.status === "valid" && approval.approvedBy ? { approval: { by: approval.approvedBy, at: approval.approvedAt ?? "" } } : {}
|
|
12229
12335
|
});
|
|
12230
|
-
const outFile =
|
|
12336
|
+
const outFile = path13.join(presentationDir, "index.html");
|
|
12231
12337
|
await writeText(outFile, html);
|
|
12232
12338
|
return { slug: change.slug, path: outFile, hash, diagnostics: mockupInfo.diagnostics };
|
|
12233
12339
|
}
|
|
@@ -12239,12 +12345,12 @@ async function copyMockups(root, change, destDir) {
|
|
|
12239
12345
|
await ensureDir(destDir);
|
|
12240
12346
|
const screens = [];
|
|
12241
12347
|
for (const screen of manifest.screens) {
|
|
12242
|
-
const src =
|
|
12348
|
+
const src = path13.join(dir, screen.file);
|
|
12243
12349
|
if (!await exists(src)) {
|
|
12244
12350
|
diagnostics.push(diag("ATLAS-PRESENT-002", "warning", `El mockup ${screen.file} no existe y no se incluir\xE1`, { path: src }));
|
|
12245
12351
|
continue;
|
|
12246
12352
|
}
|
|
12247
|
-
await copyFile(src,
|
|
12353
|
+
await copyFile(src, path13.join(destDir, screen.file));
|
|
12248
12354
|
screens.push({
|
|
12249
12355
|
id: screen.id,
|
|
12250
12356
|
title: screen.title ?? screen.id,
|
|
@@ -12254,13 +12360,13 @@ async function copyMockups(root, change, destDir) {
|
|
|
12254
12360
|
});
|
|
12255
12361
|
}
|
|
12256
12362
|
const screenshots = [];
|
|
12257
|
-
const shotsDir =
|
|
12363
|
+
const shotsDir = path13.join(dir, "screens");
|
|
12258
12364
|
if (await exists(shotsDir)) {
|
|
12259
|
-
await ensureDir(
|
|
12365
|
+
await ensureDir(path13.join(destDir, "screens"));
|
|
12260
12366
|
const { listDir: listDir4 } = await import("./fsx-VF2P7ALA-XKEWTMDH.js");
|
|
12261
12367
|
for (const file of await listDir4(shotsDir)) {
|
|
12262
12368
|
if (!/\.(png|jpe?g|webp)$/i.test(file)) continue;
|
|
12263
|
-
await copyFile(
|
|
12369
|
+
await copyFile(path13.join(shotsDir, file), path13.join(destDir, "screens", file));
|
|
12264
12370
|
screenshots.push(`mockups/screens/${file}`);
|
|
12265
12371
|
}
|
|
12266
12372
|
}
|
|
@@ -12476,11 +12582,11 @@ var SOURCE_EXT = /* @__PURE__ */ new Set([
|
|
|
12476
12582
|
".groovy"
|
|
12477
12583
|
]);
|
|
12478
12584
|
async function adoptWorkspace(opts) {
|
|
12479
|
-
const root =
|
|
12585
|
+
const root = path14.resolve(opts.root);
|
|
12480
12586
|
const dryRun = opts.dryRun ?? false;
|
|
12481
12587
|
const diagnostics = [];
|
|
12482
|
-
const sddDir =
|
|
12483
|
-
if (!await exists(
|
|
12588
|
+
const sddDir = path14.join(root, ".sdd");
|
|
12589
|
+
if (!await exists(path14.join(sddDir, "config.yaml"))) {
|
|
12484
12590
|
return {
|
|
12485
12591
|
root,
|
|
12486
12592
|
domains: [],
|
|
@@ -12491,8 +12597,8 @@ async function adoptWorkspace(opts) {
|
|
|
12491
12597
|
}
|
|
12492
12598
|
const { config } = await loadConfig(sddDir);
|
|
12493
12599
|
const files = await walkFiles(root, { skipDirs: [...SKIP_DIRS] });
|
|
12494
|
-
const inventoried = files.map((f) => toPosix(
|
|
12495
|
-
const relative = inventoried.filter((f) => SOURCE_EXT.has(
|
|
12600
|
+
const inventoried = files.map((f) => toPosix(path14.relative(root, f.path)));
|
|
12601
|
+
const relative = inventoried.filter((f) => SOURCE_EXT.has(path14.extname(f).toLowerCase()));
|
|
12496
12602
|
if (relative.length === 0) {
|
|
12497
12603
|
diagnostics.push(diag("ATLAS-ADOPT-002", "warning", "No se encontraron archivos de c\xF3digo para inventariar", { path: root, suggestion: "A\xF1ade el dominio a mano con --domains <nombre>" }));
|
|
12498
12604
|
}
|
|
@@ -12520,13 +12626,13 @@ async function adoptWorkspace(opts) {
|
|
|
12520
12626
|
const createdSpecs = [];
|
|
12521
12627
|
for (const name of domainNames.sort()) {
|
|
12522
12628
|
const domainFiles = (counts.get(name) ?? []).sort();
|
|
12523
|
-
const existingSpec = await exists(
|
|
12629
|
+
const existingSpec = await exists(path14.join(sddDir, "specs", name, "spec.md"));
|
|
12524
12630
|
domains.push({ name, files: domainFiles.length, samples: domainFiles.slice(0, 8), existingSpec });
|
|
12525
12631
|
if (existingSpec) {
|
|
12526
|
-
diagnostics.push(diag("ATLAS-ADOPT-003", "info", `La spec de "${name}" ya existe; se conserva`, { path:
|
|
12632
|
+
diagnostics.push(diag("ATLAS-ADOPT-003", "info", `La spec de "${name}" ya existe; se conserva`, { path: path14.join(sddDir, "specs", name, "spec.md") }));
|
|
12527
12633
|
continue;
|
|
12528
12634
|
}
|
|
12529
|
-
const specPath =
|
|
12635
|
+
const specPath = path14.join(sddDir, "specs", name, "spec.md");
|
|
12530
12636
|
if (!dryRun) {
|
|
12531
12637
|
await writeText(specPath, baselineSpec(name, domainFiles));
|
|
12532
12638
|
createdSpecs.push(specPath);
|
|
@@ -12534,7 +12640,7 @@ async function adoptWorkspace(opts) {
|
|
|
12534
12640
|
createdSpecs.push(specPath);
|
|
12535
12641
|
}
|
|
12536
12642
|
}
|
|
12537
|
-
const reportPath =
|
|
12643
|
+
const reportPath = path14.join(sddDir, "adopt-report.md");
|
|
12538
12644
|
if (!dryRun) {
|
|
12539
12645
|
await writeText(reportPath, adoptReport({ root, config, domains, detection: detection?.best, language: config.project.language, files: relative.length, inventoried: inventoried.length, now: opts.now ?? /* @__PURE__ */ new Date() }));
|
|
12540
12646
|
}
|
|
@@ -12554,12 +12660,12 @@ async function loadProjectProfiles(root, dirs) {
|
|
|
12554
12660
|
if (!await exists(dir)) continue;
|
|
12555
12661
|
out.push(...await loadProfilesFromDir(dir));
|
|
12556
12662
|
}
|
|
12557
|
-
const custom =
|
|
12663
|
+
const custom = path14.join(root, ".sdd", "profiles", "custom");
|
|
12558
12664
|
if (await exists(custom)) out.push(...await loadProfilesFromDir(custom));
|
|
12559
12665
|
return out;
|
|
12560
12666
|
}
|
|
12561
12667
|
async function fallbackDomainName(root, projectName) {
|
|
12562
|
-
const pkg = await readTextIfExists(
|
|
12668
|
+
const pkg = await readTextIfExists(path14.join(root, "package.json"));
|
|
12563
12669
|
if (pkg) {
|
|
12564
12670
|
try {
|
|
12565
12671
|
const data = JSON.parse(pkg);
|
|
@@ -12646,11 +12752,11 @@ function adoptReport(input) {
|
|
|
12646
12752
|
return lines.join("\n");
|
|
12647
12753
|
}
|
|
12648
12754
|
async function initWorkspace(opts) {
|
|
12649
|
-
const root =
|
|
12650
|
-
const sddDir =
|
|
12755
|
+
const root = path15.resolve(opts.root);
|
|
12756
|
+
const sddDir = path15.join(root, ".sdd");
|
|
12651
12757
|
const diagnostics = [];
|
|
12652
12758
|
const created = [];
|
|
12653
|
-
if (await exists(
|
|
12759
|
+
if (await exists(path15.join(sddDir, "config.yaml"))) {
|
|
12654
12760
|
return {
|
|
12655
12761
|
sddDir,
|
|
12656
12762
|
created,
|
|
@@ -12660,29 +12766,29 @@ async function initWorkspace(opts) {
|
|
|
12660
12766
|
created.push(...await ensureSddDirs(sddDir));
|
|
12661
12767
|
const language = opts.language ?? "es";
|
|
12662
12768
|
const cfg = defaultConfig({ name: opts.name, language });
|
|
12663
|
-
cfg.project.name = opts.name ??
|
|
12769
|
+
cfg.project.name = opts.name ?? path15.basename(root);
|
|
12664
12770
|
await writeConfig(sddDir, cfg);
|
|
12665
|
-
created.push(
|
|
12771
|
+
created.push(path15.join(sddDir, "config.yaml"));
|
|
12666
12772
|
const templates = templatesFor(language);
|
|
12667
|
-
await writeText(
|
|
12668
|
-
created.push(
|
|
12669
|
-
await writeText(
|
|
12670
|
-
created.push(
|
|
12773
|
+
await writeText(path15.join(sddDir, "constitution.md"), templates.constitution);
|
|
12774
|
+
created.push(path15.join(sddDir, "constitution.md"));
|
|
12775
|
+
await writeText(path15.join(sddDir, "glossary.md"), templates.glossary);
|
|
12776
|
+
created.push(path15.join(sddDir, "glossary.md"));
|
|
12671
12777
|
const detected = await detectIfPossible(root, opts.profilesDir);
|
|
12672
12778
|
if (detected) {
|
|
12673
12779
|
const yaml2 = detectionToYaml(detected, localStamp(opts.now));
|
|
12674
|
-
await writeText(
|
|
12675
|
-
created.push(
|
|
12780
|
+
await writeText(path15.join(sddDir, "profiles", "detected.yaml"), yaml2);
|
|
12781
|
+
created.push(path15.join(sddDir, "profiles", "detected.yaml"));
|
|
12676
12782
|
if (detected.best) {
|
|
12677
12783
|
diagnostics.push(diag("ATLAS-INIT-002", "info", `Stack detectado: ${detected.best.displayName} (${detected.best.score} puntos)`, { suggestion: "Revisa .sdd/profiles/detected.yaml" }));
|
|
12678
12784
|
} else {
|
|
12679
12785
|
diagnostics.push(diag("ATLAS-INIT-003", "info", "No se detect\xF3 un stack conocido: se usar\xE1 el perfil gen\xE9rico", { suggestion: "Crea un perfil propio con `satlas profile` (F2) o edita .sdd/profiles/custom/" }));
|
|
12680
12786
|
}
|
|
12681
12787
|
}
|
|
12682
|
-
await writeText(
|
|
12683
|
-
created.push(
|
|
12788
|
+
await writeText(path15.join(sddDir, "INDEX.md"), initialIndex(language, cfg.project.name));
|
|
12789
|
+
created.push(path15.join(sddDir, "INDEX.md"));
|
|
12684
12790
|
if (opts.local) {
|
|
12685
|
-
const gitignore =
|
|
12791
|
+
const gitignore = path15.join(root, ".gitignore");
|
|
12686
12792
|
const current = await readTextIfExists(gitignore) ?? "";
|
|
12687
12793
|
if (!/(^|\n)\.sdd\/?(\n|$)/.test(current)) {
|
|
12688
12794
|
const next = current.endsWith("\n") || current === "" ? `${current}.sdd/
|
|
@@ -12720,7 +12826,7 @@ async function createChange(opts) {
|
|
|
12720
12826
|
const diagnostics = [];
|
|
12721
12827
|
const slug = opts.slug.trim().toLowerCase();
|
|
12722
12828
|
const language = opts.language ?? opts.cfg?.project.language ?? "es";
|
|
12723
|
-
const dir =
|
|
12829
|
+
const dir = path16.join(path16.resolve(opts.root), ".sdd", "changes", slug);
|
|
12724
12830
|
if (!SLUG_RE.test(slug)) {
|
|
12725
12831
|
return {
|
|
12726
12832
|
slug,
|
|
@@ -12751,20 +12857,20 @@ async function createChange(opts) {
|
|
|
12751
12857
|
await ensureDir(dir);
|
|
12752
12858
|
const templates = templatesFor(language);
|
|
12753
12859
|
const files = [];
|
|
12754
|
-
const metaFile =
|
|
12860
|
+
const metaFile = path16.join(dir, "meta.yaml");
|
|
12755
12861
|
await writeText(metaFile, changeMetaYaml(meta));
|
|
12756
12862
|
files.push(metaFile);
|
|
12757
12863
|
if (lane === "fix") {
|
|
12758
|
-
const fixFile =
|
|
12864
|
+
const fixFile = path16.join(dir, "fix.md");
|
|
12759
12865
|
await writeText(fixFile, renderTemplate(templates.fix, { TITLE: title }));
|
|
12760
12866
|
files.push(fixFile);
|
|
12761
12867
|
return { slug, dir, files, diagnostics };
|
|
12762
12868
|
}
|
|
12763
|
-
const proposalFile =
|
|
12869
|
+
const proposalFile = path16.join(dir, "proposal.md");
|
|
12764
12870
|
await writeText(proposalFile, renderTemplate(templates.proposal, { TITLE: title }));
|
|
12765
12871
|
files.push(proposalFile);
|
|
12766
12872
|
const domainUpper = domain.replace(/[^a-z0-9]/gi, "").toUpperCase() || "GEN";
|
|
12767
|
-
const deltaFile =
|
|
12873
|
+
const deltaFile = path16.join(dir, "spec.md");
|
|
12768
12874
|
await writeText(deltaFile, templates.specDelta({ title, domainUpper, domain }));
|
|
12769
12875
|
files.push(deltaFile);
|
|
12770
12876
|
return { slug, dir, files, diagnostics };
|
|
@@ -12877,7 +12983,7 @@ async function restoreFile(file, previous) {
|
|
|
12877
12983
|
}
|
|
12878
12984
|
}
|
|
12879
12985
|
async function archiveChange(opts) {
|
|
12880
|
-
const root =
|
|
12986
|
+
const root = path17.resolve(opts.root);
|
|
12881
12987
|
const diagnostics = [];
|
|
12882
12988
|
const { config } = await loadWorkspace(root);
|
|
12883
12989
|
const language = opts.language ?? config.project.language;
|
|
@@ -12893,16 +12999,43 @@ async function archiveChange(opts) {
|
|
|
12893
12999
|
return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
|
|
12894
13000
|
}
|
|
12895
13001
|
const nowFix = opts.now ?? /* @__PURE__ */ new Date();
|
|
12896
|
-
const targetFix =
|
|
13002
|
+
const targetFix = path17.join(root, ".sdd", "changes", "archive", `${localMonth(nowFix)}-${opts.slug}`);
|
|
12897
13003
|
if (await exists(targetFix)) {
|
|
12898
13004
|
diagnostics.push(diag("ATLAS-ARCH-002", "error", `Ya existe un cambio archivado en ${targetFix}`, { path: targetFix }));
|
|
12899
13005
|
return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
|
|
12900
13006
|
}
|
|
13007
|
+
let livingFix;
|
|
13008
|
+
let livingCreated = false;
|
|
12901
13009
|
if (!opts.dryRun) {
|
|
12902
|
-
await
|
|
13010
|
+
const fixRaw = await readTextIfExists(path17.join(change.dir, "fix.md")) ?? "";
|
|
13011
|
+
try {
|
|
13012
|
+
const written = await writeLivingFix(root, {
|
|
13013
|
+
slug: opts.slug,
|
|
13014
|
+
date: localDate(nowFix),
|
|
13015
|
+
result: "pass",
|
|
13016
|
+
...change.meta.domain !== void 0 ? { domain: change.meta.domain } : {},
|
|
13017
|
+
...change.meta.title !== void 0 ? { title: change.meta.title } : {},
|
|
13018
|
+
covers: change.fixCovers ?? [],
|
|
13019
|
+
content: fixRaw
|
|
13020
|
+
});
|
|
13021
|
+
livingFix = written.relativePath;
|
|
13022
|
+
livingCreated = written.created;
|
|
13023
|
+
} catch (error) {
|
|
13024
|
+
diagnostics.push(
|
|
13025
|
+
diag("ATLAS-ARCH-006", "error", `No se pudo conservar el fix vivo: ${error.message}`, {
|
|
13026
|
+
path: path17.join(root, ".sdd", "fixes"),
|
|
13027
|
+
suggestion: "Revisa los permisos de .sdd/fixes y vuelve a intentar; el fix sigue sin archivar"
|
|
13028
|
+
})
|
|
13029
|
+
);
|
|
13030
|
+
return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: false };
|
|
13031
|
+
}
|
|
13032
|
+
}
|
|
13033
|
+
if (!opts.dryRun) {
|
|
13034
|
+
await ensureDir(path17.dirname(targetFix));
|
|
12903
13035
|
try {
|
|
12904
13036
|
await moveDirectory(change.dir, targetFix);
|
|
12905
13037
|
} catch (error) {
|
|
13038
|
+
if (livingCreated && livingFix) await removeFile(path17.join(root, livingFix));
|
|
12906
13039
|
diagnostics.push(
|
|
12907
13040
|
diag("ATLAS-ARCH-004", "error", `No se pudo mover el cambio al hist\xF3rico: ${error.message}`, {
|
|
12908
13041
|
path: change.dir,
|
|
@@ -12911,9 +13044,16 @@ async function archiveChange(opts) {
|
|
|
12911
13044
|
);
|
|
12912
13045
|
return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: false };
|
|
12913
13046
|
}
|
|
12914
|
-
await regenerateIndex(root, config);
|
|
13047
|
+
await regenerateIndex(root, config, nowFix);
|
|
12915
13048
|
}
|
|
12916
|
-
return {
|
|
13049
|
+
return {
|
|
13050
|
+
slug: opts.slug,
|
|
13051
|
+
archivedTo: targetFix,
|
|
13052
|
+
...livingFix !== void 0 ? { livingFix } : {},
|
|
13053
|
+
fold: emptyFold,
|
|
13054
|
+
diagnostics,
|
|
13055
|
+
dryRun: opts.dryRun ?? false
|
|
13056
|
+
};
|
|
12917
13057
|
}
|
|
12918
13058
|
if (!change.delta) {
|
|
12919
13059
|
diagnostics.push(diag("ATLAS-ARCH-003", "error", `El cambio "${opts.slug}" no tiene spec.md (delta): no hay nada que plegar`, { path: change.dir }));
|
|
@@ -12921,10 +13061,10 @@ async function archiveChange(opts) {
|
|
|
12921
13061
|
}
|
|
12922
13062
|
const domain = change.meta.domain;
|
|
12923
13063
|
if (!domain) {
|
|
12924
|
-
diagnostics.push(diag("ATLAS-ARCH-001", "error", `El cambio "${opts.slug}" no declara domain en meta.yaml`, { path:
|
|
13064
|
+
diagnostics.push(diag("ATLAS-ARCH-001", "error", `El cambio "${opts.slug}" no declara domain en meta.yaml`, { path: path17.join(change.dir, "meta.yaml") }));
|
|
12925
13065
|
return { slug: opts.slug, fold: { content: "", applied: { added: [], modified: [], removed: [], renamed: [] }, diagnostics }, diagnostics, dryRun: opts.dryRun ?? false };
|
|
12926
13066
|
}
|
|
12927
|
-
const specFile =
|
|
13067
|
+
const specFile = path17.join(root, ".sdd", "specs", domain, "spec.md");
|
|
12928
13068
|
const existing = await readTextIfExists(specFile);
|
|
12929
13069
|
const base = existing ?? `---
|
|
12930
13070
|
domain: ${domain}
|
|
@@ -12954,8 +13094,8 @@ ${Object.entries(nextFm).map(([k, v]) => `${k}: ${String(v)}`).join("\n")}
|
|
|
12954
13094
|
|
|
12955
13095
|
${body}`;
|
|
12956
13096
|
const month = localMonth(now);
|
|
12957
|
-
const archiveDir =
|
|
12958
|
-
const target =
|
|
13097
|
+
const archiveDir = path17.join(root, ".sdd", "changes", "archive");
|
|
13098
|
+
const target = path17.join(archiveDir, `${month}-${opts.slug}`);
|
|
12959
13099
|
if (await exists(target)) {
|
|
12960
13100
|
diagnostics.push(diag("ATLAS-ARCH-002", "error", `Ya existe un cambio archivado en ${target}`, { path: target }));
|
|
12961
13101
|
return { slug: opts.slug, domain, fold, diagnostics, dryRun: opts.dryRun ?? false };
|
|
@@ -12981,17 +13121,25 @@ ${body}`;
|
|
|
12981
13121
|
}
|
|
12982
13122
|
async function regenerateIndex(root, cfg, now = /* @__PURE__ */ new Date()) {
|
|
12983
13123
|
const { workspace, config } = cfg ? { workspace: (await loadWorkspace(root)).workspace, config: cfg } : await loadWorkspace(root);
|
|
12984
|
-
const archiveDir =
|
|
13124
|
+
const archiveDir = path17.join(root, ".sdd", "changes", "archive");
|
|
12985
13125
|
const archived = await exists(archiveDir) ? (await fs.readdir(archiveDir)).filter((e) => !e.startsWith(".")).length : 0;
|
|
13126
|
+
const fixes = await loadLivingFixes(root);
|
|
12986
13127
|
const markdown2 = indexMarkdown({
|
|
12987
13128
|
projectName: config.project.name,
|
|
12988
13129
|
language: config.project.language,
|
|
12989
13130
|
specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length })),
|
|
12990
13131
|
changes: workspace.changes.map((c) => ({ slug: c.slug, lane: c.meta?.lane ?? config.lanes.default })),
|
|
13132
|
+
fixes: fixes.map((fix) => ({ slug: fix.slug, date: fix.date, result: fix.result, ...fix.domain !== void 0 ? { domain: fix.domain } : {} })),
|
|
12991
13133
|
archived
|
|
12992
13134
|
});
|
|
12993
13135
|
void now;
|
|
12994
|
-
await writeText(
|
|
13136
|
+
await writeText(path17.join(root, ".sdd", "INDEX.md"), markdown2);
|
|
13137
|
+
}
|
|
13138
|
+
async function removeFile(file) {
|
|
13139
|
+
try {
|
|
13140
|
+
await rm(file, { force: true });
|
|
13141
|
+
} catch {
|
|
13142
|
+
}
|
|
12995
13143
|
}
|
|
12996
13144
|
var SCHEMA_VERSION = 1;
|
|
12997
13145
|
var BACKUP_DIRNAME = ".backup";
|
|
@@ -13004,28 +13152,28 @@ var SCHEMA_MIGRATIONS = [
|
|
|
13004
13152
|
to: SCHEMA_VERSION
|
|
13005
13153
|
}
|
|
13006
13154
|
];
|
|
13007
|
-
var FIXED_ARTIFACTS = ["config.yaml", "approvals.yaml",
|
|
13008
|
-
var CHANGE_ARTIFACTS = ["meta.yaml",
|
|
13155
|
+
var FIXED_ARTIFACTS = ["config.yaml", "approvals.yaml", path18.join("profiles", "detected.yaml")];
|
|
13156
|
+
var CHANGE_ARTIFACTS = ["meta.yaml", path18.join("mockups", "manifest.yaml")];
|
|
13009
13157
|
async function pushChangeArtifacts(out, sddDir, relDir) {
|
|
13010
13158
|
for (const rel of CHANGE_ARTIFACTS) {
|
|
13011
|
-
const abs =
|
|
13012
|
-
if (await exists(abs)) out.push({ artifact: toPosix(
|
|
13159
|
+
const abs = path18.join(sddDir, relDir, rel);
|
|
13160
|
+
if (await exists(abs)) out.push({ artifact: toPosix(path18.join(relDir, rel)), path: abs });
|
|
13013
13161
|
}
|
|
13014
13162
|
}
|
|
13015
13163
|
async function collectVersionedArtifacts(root) {
|
|
13016
|
-
const sddDir =
|
|
13164
|
+
const sddDir = path18.join(root, ".sdd");
|
|
13017
13165
|
const out = [];
|
|
13018
13166
|
for (const rel of FIXED_ARTIFACTS) {
|
|
13019
|
-
const abs =
|
|
13167
|
+
const abs = path18.join(sddDir, rel);
|
|
13020
13168
|
if (await exists(abs)) out.push({ artifact: toPosix(rel), path: abs });
|
|
13021
13169
|
}
|
|
13022
|
-
const changesDir =
|
|
13170
|
+
const changesDir = path18.join(sddDir, "changes");
|
|
13023
13171
|
for (const slug of await listDirs(changesDir)) {
|
|
13024
13172
|
if (slug === "archive") continue;
|
|
13025
|
-
await pushChangeArtifacts(out, sddDir,
|
|
13173
|
+
await pushChangeArtifacts(out, sddDir, path18.join("changes", slug));
|
|
13026
13174
|
}
|
|
13027
|
-
for (const entry of await listDirs(
|
|
13028
|
-
await pushChangeArtifacts(out, sddDir,
|
|
13175
|
+
for (const entry of await listDirs(path18.join(changesDir, "archive"))) {
|
|
13176
|
+
await pushChangeArtifacts(out, sddDir, path18.join("changes", "archive", entry));
|
|
13029
13177
|
}
|
|
13030
13178
|
return out;
|
|
13031
13179
|
}
|
|
@@ -13110,7 +13258,7 @@ async function removeDir(dir) {
|
|
|
13110
13258
|
} catch {
|
|
13111
13259
|
}
|
|
13112
13260
|
}
|
|
13113
|
-
async function
|
|
13261
|
+
async function removeFile2(file) {
|
|
13114
13262
|
try {
|
|
13115
13263
|
await fs2.rm(file, { force: true });
|
|
13116
13264
|
} catch {
|
|
@@ -13122,22 +13270,22 @@ function backupName(now) {
|
|
|
13122
13270
|
async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
|
|
13123
13271
|
const plan = await planUpgrade(root);
|
|
13124
13272
|
if (plan.pending.length === 0) return { status: "up-to-date", plan, applied: [] };
|
|
13125
|
-
const sddDir =
|
|
13126
|
-
const backupRoot =
|
|
13273
|
+
const sddDir = path18.join(root, ".sdd");
|
|
13274
|
+
const backupRoot = path18.join(sddDir, BACKUP_DIRNAME);
|
|
13127
13275
|
const name = backupName(now);
|
|
13128
|
-
const backupDir =
|
|
13129
|
-
const pointerFile =
|
|
13276
|
+
const backupDir = path18.join(backupRoot, name);
|
|
13277
|
+
const pointerFile = path18.join(backupRoot, BACKUP_POINTER);
|
|
13130
13278
|
const previous = [];
|
|
13131
13279
|
try {
|
|
13132
13280
|
for (const item of plan.pending) {
|
|
13133
13281
|
const content = await readTextIfExists(item.path);
|
|
13134
13282
|
if (content === void 0) throw new Error("el elemento desapareci\xF3 mientras se respaldaba");
|
|
13135
13283
|
previous.push({ item, contents: content });
|
|
13136
|
-
await writeText(
|
|
13284
|
+
await writeText(path18.join(backupDir, "files", ...item.artifact.split("/")), content);
|
|
13137
13285
|
}
|
|
13138
13286
|
await writeText(
|
|
13139
|
-
|
|
13140
|
-
|
|
13287
|
+
path18.join(backupDir, "backup.yaml"),
|
|
13288
|
+
stringifyYaml6(
|
|
13141
13289
|
{
|
|
13142
13290
|
schema_version: SCHEMA_VERSION,
|
|
13143
13291
|
created_at: localStamp(now),
|
|
@@ -13151,7 +13299,7 @@ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
|
|
|
13151
13299
|
`);
|
|
13152
13300
|
} catch (err) {
|
|
13153
13301
|
await removeDir(backupDir);
|
|
13154
|
-
await
|
|
13302
|
+
await removeFile2(pointerFile);
|
|
13155
13303
|
return { status: "failed", plan, applied: [], failure: { artifact: "(respaldo)", message: err.message } };
|
|
13156
13304
|
}
|
|
13157
13305
|
const written = [];
|
|
@@ -13171,13 +13319,13 @@ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
|
|
|
13171
13319
|
}
|
|
13172
13320
|
}
|
|
13173
13321
|
}
|
|
13174
|
-
await
|
|
13322
|
+
await removeFile2(pointerFile);
|
|
13175
13323
|
await removeDir(backupDir);
|
|
13176
13324
|
return { status: "failed", plan, applied: [], failure: { artifact: item.artifact, message: err.message } };
|
|
13177
13325
|
}
|
|
13178
13326
|
}
|
|
13179
13327
|
for (const dir of await listDirs(backupRoot)) {
|
|
13180
|
-
if (dir !== name) await removeDir(
|
|
13328
|
+
if (dir !== name) await removeDir(path18.join(backupRoot, dir));
|
|
13181
13329
|
}
|
|
13182
13330
|
return {
|
|
13183
13331
|
status: "applied",
|
|
@@ -13186,20 +13334,20 @@ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
|
|
|
13186
13334
|
backup: {
|
|
13187
13335
|
name,
|
|
13188
13336
|
dir: backupDir,
|
|
13189
|
-
relativeDir: toPosix(
|
|
13337
|
+
relativeDir: toPosix(path18.relative(root, backupDir)),
|
|
13190
13338
|
createdAt: localStamp(now),
|
|
13191
13339
|
files: plan.pending.map((i) => i.artifact)
|
|
13192
13340
|
}
|
|
13193
13341
|
};
|
|
13194
13342
|
}
|
|
13195
13343
|
async function rollbackUpgrade(root) {
|
|
13196
|
-
const sddDir =
|
|
13197
|
-
const backupRoot =
|
|
13198
|
-
const pointerFile =
|
|
13344
|
+
const sddDir = path18.join(root, ".sdd");
|
|
13345
|
+
const backupRoot = path18.join(sddDir, BACKUP_DIRNAME);
|
|
13346
|
+
const pointerFile = path18.join(backupRoot, BACKUP_POINTER);
|
|
13199
13347
|
const name = (await readTextIfExists(pointerFile))?.trim();
|
|
13200
13348
|
if (!name) return { status: "no-backup", restored: [] };
|
|
13201
|
-
const backupDir =
|
|
13202
|
-
const manifestRaw = await readTextIfExists(
|
|
13349
|
+
const backupDir = path18.join(backupRoot, name);
|
|
13350
|
+
const manifestRaw = await readTextIfExists(path18.join(backupDir, "backup.yaml"));
|
|
13203
13351
|
if (manifestRaw === void 0) return { status: "no-backup", restored: [] };
|
|
13204
13352
|
let files = [];
|
|
13205
13353
|
try {
|
|
@@ -13210,8 +13358,8 @@ async function rollbackUpgrade(root) {
|
|
|
13210
13358
|
}
|
|
13211
13359
|
const restored = [];
|
|
13212
13360
|
for (const file of files) {
|
|
13213
|
-
const from =
|
|
13214
|
-
const to =
|
|
13361
|
+
const from = path18.join(backupDir, "files", ...file.artifact.split("/"));
|
|
13362
|
+
const to = path18.join(sddDir, ...file.artifact.split("/"));
|
|
13215
13363
|
const content = await readTextIfExists(from);
|
|
13216
13364
|
if (content === void 0) continue;
|
|
13217
13365
|
try {
|
|
@@ -13222,7 +13370,7 @@ async function rollbackUpgrade(root) {
|
|
|
13222
13370
|
}
|
|
13223
13371
|
}
|
|
13224
13372
|
await removeDir(backupDir);
|
|
13225
|
-
await
|
|
13373
|
+
await removeFile2(pointerFile);
|
|
13226
13374
|
return { status: "restored", restored, backup: name };
|
|
13227
13375
|
}
|
|
13228
13376
|
async function upgradeAdvisory(root) {
|
|
@@ -13290,7 +13438,7 @@ function resultOf(diagnostic, root) {
|
|
|
13290
13438
|
message: { text: diagnostic.message }
|
|
13291
13439
|
};
|
|
13292
13440
|
if (diagnostic.path) {
|
|
13293
|
-
const rel =
|
|
13441
|
+
const rel = path19.relative(root, diagnostic.path);
|
|
13294
13442
|
if (rel !== "") {
|
|
13295
13443
|
const physicalLocation = {
|
|
13296
13444
|
artifactLocation: { uri: toPosix(rel) },
|
|
@@ -13345,7 +13493,7 @@ async function runDoctor(root) {
|
|
|
13345
13493
|
const approvals = await loadApprovals(workspace.sddDir);
|
|
13346
13494
|
findings.push(...approvals.diagnostics);
|
|
13347
13495
|
for (const change of workspace.changes) {
|
|
13348
|
-
const deltaPath =
|
|
13496
|
+
const deltaPath = path20.join(change.dir, "spec.md");
|
|
13349
13497
|
const deltaContent = await readTextIfExists(deltaPath);
|
|
13350
13498
|
const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
|
|
13351
13499
|
if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
|
|
@@ -13367,7 +13515,7 @@ async function runDoctor(root) {
|
|
|
13367
13515
|
}
|
|
13368
13516
|
for (const override of change.meta?.overrides ?? []) {
|
|
13369
13517
|
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:
|
|
13518
|
+
findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path20.join(change.dir, "meta.yaml") }));
|
|
13371
13519
|
}
|
|
13372
13520
|
}
|
|
13373
13521
|
}
|
|
@@ -13399,7 +13547,7 @@ function livingRequirementsMap(specs) {
|
|
|
13399
13547
|
return map;
|
|
13400
13548
|
}
|
|
13401
13549
|
async function runCiGate(opts) {
|
|
13402
|
-
const root =
|
|
13550
|
+
const root = path21.resolve(opts.root);
|
|
13403
13551
|
const { workspace, config } = await loadWorkspace(root);
|
|
13404
13552
|
const diagnostics = [];
|
|
13405
13553
|
const checks = [];
|
|
@@ -13410,7 +13558,7 @@ async function runCiGate(opts) {
|
|
|
13410
13558
|
let changesErrors = 0;
|
|
13411
13559
|
let changesWarnings = 0;
|
|
13412
13560
|
for (const change of workspace.changes) {
|
|
13413
|
-
const lintFindings = change.delta ? lintDelta(change.delta, living,
|
|
13561
|
+
const lintFindings = change.delta ? lintDelta(change.delta, living, path21.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
|
|
13414
13562
|
const trace = checkTrace({
|
|
13415
13563
|
specs: workspace.specs,
|
|
13416
13564
|
change,
|
|
@@ -13552,6 +13700,7 @@ var ES2 = {
|
|
|
13552
13700
|
"status.title": "Estado de los cambios",
|
|
13553
13701
|
"status.changes": "Cambios activos",
|
|
13554
13702
|
"status.specs": "Specs vivas",
|
|
13703
|
+
"status.fixes": "Fixes vivos",
|
|
13555
13704
|
"status.next": "siguiente:",
|
|
13556
13705
|
"status.tasks": "tareas",
|
|
13557
13706
|
"status.evidence": "evidencia",
|
|
@@ -13586,6 +13735,7 @@ var EN2 = {
|
|
|
13586
13735
|
"status.title": "Change status",
|
|
13587
13736
|
"status.changes": "Active changes",
|
|
13588
13737
|
"status.specs": "Living specs",
|
|
13738
|
+
"status.fixes": "Living fixes",
|
|
13589
13739
|
"status.next": "next:",
|
|
13590
13740
|
"status.tasks": "tasks",
|
|
13591
13741
|
"status.evidence": "evidence",
|
|
@@ -13630,26 +13780,26 @@ function cliVersion() {
|
|
|
13630
13780
|
}
|
|
13631
13781
|
|
|
13632
13782
|
// src/commands/adapters.ts
|
|
13633
|
-
import
|
|
13783
|
+
import path25 from "path";
|
|
13634
13784
|
|
|
13635
13785
|
// ../adapters/dist/index.js
|
|
13636
|
-
import path21 from "path";
|
|
13637
|
-
import { stringify as stringifyYaml6 } from "yaml";
|
|
13638
13786
|
import path22 from "path";
|
|
13787
|
+
import { stringify as stringifyYaml7 } from "yaml";
|
|
13788
|
+
import path23 from "path";
|
|
13639
13789
|
async function loadWorkflow(workflowDir) {
|
|
13640
|
-
const phasesDir =
|
|
13641
|
-
const snippetsDir =
|
|
13790
|
+
const phasesDir = path22.join(workflowDir, "phases");
|
|
13791
|
+
const snippetsDir = path22.join(workflowDir, "snippets");
|
|
13642
13792
|
const phases = [];
|
|
13643
13793
|
const snippets = /* @__PURE__ */ new Map();
|
|
13644
13794
|
const hashParts = [];
|
|
13645
13795
|
for (const entry of (await listDir(phasesDir)).sort()) {
|
|
13646
13796
|
if (!entry.endsWith(".md")) continue;
|
|
13647
|
-
const filePath =
|
|
13797
|
+
const filePath = path22.join(phasesDir, entry);
|
|
13648
13798
|
const raw = await readText(filePath);
|
|
13649
13799
|
hashParts.push(raw);
|
|
13650
13800
|
const fm = parseFrontmatter(raw, filePath);
|
|
13651
13801
|
const data = fm.data;
|
|
13652
|
-
const id = typeof data["id"] === "string" ? data["id"] :
|
|
13802
|
+
const id = typeof data["id"] === "string" ? data["id"] : path22.basename(entry, ".md");
|
|
13653
13803
|
phases.push({
|
|
13654
13804
|
id,
|
|
13655
13805
|
title: typeof data["title"] === "string" ? data["title"] : id,
|
|
@@ -13663,9 +13813,9 @@ async function loadWorkflow(workflowDir) {
|
|
|
13663
13813
|
}
|
|
13664
13814
|
for (const entry of (await listDir(snippetsDir)).sort()) {
|
|
13665
13815
|
if (!entry.endsWith(".md")) continue;
|
|
13666
|
-
const raw = await readTextIfExists(
|
|
13816
|
+
const raw = await readTextIfExists(path22.join(snippetsDir, entry)) ?? "";
|
|
13667
13817
|
hashParts.push(raw);
|
|
13668
|
-
snippets.set(
|
|
13818
|
+
snippets.set(path22.basename(entry, ".md"), raw.trim());
|
|
13669
13819
|
}
|
|
13670
13820
|
return { phases: phases.sort((a, b) => a.id.localeCompare(b.id)), snippets, sourceHash: sha256(hashParts.join("\n---\n")) };
|
|
13671
13821
|
}
|
|
@@ -13689,7 +13839,7 @@ function vars(language, slugToken, commandPrefix) {
|
|
|
13689
13839
|
return { SLUG: slugToken, LANGUAGE: language, LANGUAGE_NAME: LANGUAGE_NAMES[language], SDD_DIR: ".sdd", COMMAND_PREFIX: commandPrefix };
|
|
13690
13840
|
}
|
|
13691
13841
|
function frontmatter(data, body) {
|
|
13692
|
-
const yaml2 =
|
|
13842
|
+
const yaml2 = stringifyYaml7(data, { lineWidth: 0 }).trimEnd();
|
|
13693
13843
|
return `---
|
|
13694
13844
|
${yaml2}
|
|
13695
13845
|
---
|
|
@@ -13847,11 +13997,11 @@ Reglas: la spec es funcional y de negocio (sin tecnolog\xEDa); la trazabilidad e
|
|
|
13847
13997
|
function tomlString(value) {
|
|
13848
13998
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
13849
13999
|
}
|
|
13850
|
-
var MANIFEST_REL =
|
|
14000
|
+
var MANIFEST_REL = path23.join(".sdd", ".generated", "manifest.json");
|
|
13851
14001
|
var BEGIN = "<!-- BEGIN specatlas -->";
|
|
13852
14002
|
var END = "<!-- END specatlas -->";
|
|
13853
14003
|
async function compileTargets(opts) {
|
|
13854
|
-
const root =
|
|
14004
|
+
const root = path23.resolve(opts.root);
|
|
13855
14005
|
const language = opts.language ?? "es";
|
|
13856
14006
|
const diagnostics = [];
|
|
13857
14007
|
const sources = await loadWorkflow(opts.workflowDir);
|
|
@@ -13859,7 +14009,7 @@ async function compileTargets(opts) {
|
|
|
13859
14009
|
diagnostics.push({
|
|
13860
14010
|
code: "ATLAS-ADAPTERS-002",
|
|
13861
14011
|
severity: "error",
|
|
13862
|
-
message: `No hay fases en ${
|
|
14012
|
+
message: `No hay fases en ${path23.join(opts.workflowDir, "phases")}`,
|
|
13863
14013
|
suggestion: "Revisa la carpeta workflow/phases del proyecto"
|
|
13864
14014
|
});
|
|
13865
14015
|
}
|
|
@@ -13871,7 +14021,7 @@ async function compileTargets(opts) {
|
|
|
13871
14021
|
}
|
|
13872
14022
|
}
|
|
13873
14023
|
const compiled = [...deduped.values()];
|
|
13874
|
-
const manifestPath =
|
|
14024
|
+
const manifestPath = path23.join(root, MANIFEST_REL);
|
|
13875
14025
|
const previous = await readManifest(manifestPath);
|
|
13876
14026
|
const previousHashes = /* @__PURE__ */ new Map();
|
|
13877
14027
|
for (const files2 of Object.values(previous?.targets ?? {})) {
|
|
@@ -13882,7 +14032,7 @@ async function compileTargets(opts) {
|
|
|
13882
14032
|
const stale = [];
|
|
13883
14033
|
const missing = [];
|
|
13884
14034
|
for (const file of compiled) {
|
|
13885
|
-
const abs =
|
|
14035
|
+
const abs = path23.join(root, file.path);
|
|
13886
14036
|
const finalContent = file.path === "AGENTS.md" ? mergeAgentsBlock(await readTextIfExists(abs) ?? "", file.content) : file.content;
|
|
13887
14037
|
const hash = artifactHash(finalContent);
|
|
13888
14038
|
const existing = await readTextIfExists(abs);
|
|
@@ -13898,7 +14048,7 @@ async function compileTargets(opts) {
|
|
|
13898
14048
|
stale.push(file.path);
|
|
13899
14049
|
}
|
|
13900
14050
|
if (!opts.check && status !== "unchanged") {
|
|
13901
|
-
await ensureDir(
|
|
14051
|
+
await ensureDir(path23.dirname(abs));
|
|
13902
14052
|
await writeText(abs, finalContent);
|
|
13903
14053
|
written.push(file.path);
|
|
13904
14054
|
}
|
|
@@ -13952,7 +14102,7 @@ async function readManifest(manifestPath) {
|
|
|
13952
14102
|
}
|
|
13953
14103
|
}
|
|
13954
14104
|
async function checkAdapters(opts) {
|
|
13955
|
-
const manifestPath =
|
|
14105
|
+
const manifestPath = path23.join(path23.resolve(opts.root), MANIFEST_REL);
|
|
13956
14106
|
const manifestRaw = await readTextIfExists(manifestPath);
|
|
13957
14107
|
if (manifestRaw === void 0) return { ok: false, manifest: false, stale: [], missing: [], orphaned: [] };
|
|
13958
14108
|
const manifest = await readManifest(manifestPath);
|
|
@@ -13969,7 +14119,7 @@ async function checkAdapters(opts) {
|
|
|
13969
14119
|
}
|
|
13970
14120
|
|
|
13971
14121
|
// src/paths.ts
|
|
13972
|
-
import
|
|
14122
|
+
import path24 from "path";
|
|
13973
14123
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13974
14124
|
async function resolveProfilesDir() {
|
|
13975
14125
|
const env = process.env["SPECATLAS_PROFILES_DIR"];
|
|
@@ -13979,16 +14129,16 @@ async function resolveProfilesDir() {
|
|
|
13979
14129
|
async function resolveWorkflowDir() {
|
|
13980
14130
|
const env = process.env["SPECATLAS_WORKFLOW_DIR"];
|
|
13981
14131
|
if (env && await exists(env)) return env;
|
|
13982
|
-
return walkUpFor(
|
|
14132
|
+
return walkUpFor(path24.join("workflow", "phases"));
|
|
13983
14133
|
}
|
|
13984
14134
|
async function walkUpFor(relative) {
|
|
13985
|
-
let dir =
|
|
14135
|
+
let dir = path24.dirname(fileURLToPath2(import.meta.url));
|
|
13986
14136
|
for (let i = 0; i < 8; i += 1) {
|
|
13987
|
-
const candidate =
|
|
14137
|
+
const candidate = path24.join(dir, relative);
|
|
13988
14138
|
if (await exists(candidate)) {
|
|
13989
|
-
return relative.includes(
|
|
14139
|
+
return relative.includes(path24.sep) ? path24.dirname(candidate) : candidate;
|
|
13990
14140
|
}
|
|
13991
|
-
const parent =
|
|
14141
|
+
const parent = path24.dirname(dir);
|
|
13992
14142
|
if (parent === dir) break;
|
|
13993
14143
|
dir = parent;
|
|
13994
14144
|
}
|
|
@@ -14066,7 +14216,7 @@ async function runAdapters(ctx) {
|
|
|
14066
14216
|
for (const [target, count2] of byTarget) lines.push(` ${target}: ${count2} archivo(s)`);
|
|
14067
14217
|
if (report.written.length > 0) {
|
|
14068
14218
|
lines.push("");
|
|
14069
|
-
for (const p of report.written) lines.push(` + ${
|
|
14219
|
+
for (const p of report.written) lines.push(` + ${path25.relative(ctx.cwd, path25.join(root, p))}`);
|
|
14070
14220
|
} else {
|
|
14071
14221
|
lines.push("");
|
|
14072
14222
|
lines.push("Sin cambios: los adaptadores ya estaban al d\xEDa.");
|
|
@@ -14077,7 +14227,7 @@ async function runAdapters(ctx) {
|
|
|
14077
14227
|
}
|
|
14078
14228
|
|
|
14079
14229
|
// src/commands/adopt.ts
|
|
14080
|
-
import
|
|
14230
|
+
import path26 from "path";
|
|
14081
14231
|
async function runAdopt(ctx) {
|
|
14082
14232
|
const { root } = await requireWorkspace(ctx);
|
|
14083
14233
|
const domainsFlag = flagString(ctx.flags, "domains") ?? flagString(ctx.flags, "domain");
|
|
@@ -14097,11 +14247,11 @@ async function runAdopt(ctx) {
|
|
|
14097
14247
|
}
|
|
14098
14248
|
if (result.createdSpecs.length > 0) {
|
|
14099
14249
|
lines.push("");
|
|
14100
|
-
for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${
|
|
14250
|
+
for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${path26.relative(ctx.cwd, spec)}`);
|
|
14101
14251
|
}
|
|
14102
14252
|
if (result.reportPath && !result.dryRun) {
|
|
14103
14253
|
lines.push("");
|
|
14104
|
-
lines.push(` Informe: ${
|
|
14254
|
+
lines.push(` Informe: ${path26.relative(ctx.cwd, result.reportPath)}`);
|
|
14105
14255
|
}
|
|
14106
14256
|
lines.push("");
|
|
14107
14257
|
lines.push("Siguiente: fase /satlas-adopt por dominio (requisitos AS-IS) y luego:");
|
|
@@ -14114,8 +14264,8 @@ async function runAdopt(ctx) {
|
|
|
14114
14264
|
diagnostics: result.diagnostics,
|
|
14115
14265
|
data: {
|
|
14116
14266
|
domains: result.domains.map((d) => ({ name: d.name, files: d.files, existingSpec: d.existingSpec })),
|
|
14117
|
-
createdSpecs: result.createdSpecs.map((s) =>
|
|
14118
|
-
reportPath: result.reportPath ?
|
|
14267
|
+
createdSpecs: result.createdSpecs.map((s) => path26.relative(ctx.cwd, s)),
|
|
14268
|
+
reportPath: result.reportPath ? path26.relative(ctx.cwd, result.reportPath) : void 0,
|
|
14119
14269
|
stack: result.stack,
|
|
14120
14270
|
dryRun: result.dryRun
|
|
14121
14271
|
},
|
|
@@ -14124,7 +14274,7 @@ async function runAdopt(ctx) {
|
|
|
14124
14274
|
}
|
|
14125
14275
|
|
|
14126
14276
|
// src/commands/analyze.ts
|
|
14127
|
-
import
|
|
14277
|
+
import path27 from "path";
|
|
14128
14278
|
async function runAnalyzeCommand(ctx) {
|
|
14129
14279
|
const { root } = await requireWorkspace(ctx);
|
|
14130
14280
|
const slug = ctx.positionals[0];
|
|
@@ -14135,23 +14285,23 @@ async function runAnalyzeCommand(ctx) {
|
|
|
14135
14285
|
const lines = [`An\xE1lisis \u2014 ${slug}`, "", ` estado: ${result.status}`, ` hallazgos: ${result.summary.errors} errores, ${result.summary.warnings} avisos`, ` evidencia: ${result.evidence.done}/${result.evidence.total} escenarios`];
|
|
14136
14286
|
if (result.waves) lines.push(` olas: ${result.waves.blocks} bloque(s), ${result.waves.waves} ola(s), ${result.waves.tasks} tarea(s)`);
|
|
14137
14287
|
if (result.mockups) lines.push(` mockups: ${result.mockups.screens} pantalla(s)${result.mockups.stale ? " (desactualizados)" : ""}`);
|
|
14138
|
-
if (result.path) lines.push("", ` informe: ${
|
|
14288
|
+
if (result.path) lines.push("", ` informe: ${path27.relative(ctx.cwd, result.path)}`);
|
|
14139
14289
|
for (const finding of result.findings) {
|
|
14140
14290
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}`);
|
|
14141
14291
|
}
|
|
14142
14292
|
return {
|
|
14143
14293
|
exitCode: result.status === "blocked" ? 1 : 0,
|
|
14144
14294
|
diagnostics: result.findings,
|
|
14145
|
-
data: result.path ? { ...result, path:
|
|
14295
|
+
data: result.path ? { ...result, path: path27.relative(ctx.cwd, result.path) } : result,
|
|
14146
14296
|
text: lines
|
|
14147
14297
|
};
|
|
14148
14298
|
}
|
|
14149
14299
|
|
|
14150
14300
|
// src/commands/approve.ts
|
|
14151
|
-
import
|
|
14301
|
+
import path29 from "path";
|
|
14152
14302
|
|
|
14153
14303
|
// src/commands/issue.ts
|
|
14154
|
-
import
|
|
14304
|
+
import path28 from "path";
|
|
14155
14305
|
async function runIssue(ctx) {
|
|
14156
14306
|
const action = ctx.positionals[0] ?? "status";
|
|
14157
14307
|
const slug = ctx.positionals[1];
|
|
@@ -14179,7 +14329,7 @@ async function runIssue(ctx) {
|
|
|
14179
14329
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-ISSUE-000", severity: "error", message: "Falta el slug: satlas issue status <slug>" }] };
|
|
14180
14330
|
}
|
|
14181
14331
|
const change = await loadChange(root, slug);
|
|
14182
|
-
const { config } = await loadConfig(
|
|
14332
|
+
const { config } = await loadConfig(path28.join(root, ".sdd"));
|
|
14183
14333
|
const tracker = change.meta?.tracker;
|
|
14184
14334
|
const lines = [`Issue de ${slug}`, ""];
|
|
14185
14335
|
if (!tracker || tracker.provider !== "github") {
|
|
@@ -14250,7 +14400,7 @@ async function runApprove(ctx) {
|
|
|
14250
14400
|
};
|
|
14251
14401
|
}
|
|
14252
14402
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
14253
|
-
const artifact = target.includes("/") || target.includes("\\") ? target :
|
|
14403
|
+
const artifact = target.includes("/") || target.includes("\\") ? target : path29.join("changes", target, "spec.md");
|
|
14254
14404
|
const channelFlag = flagString(ctx.flags, "channel");
|
|
14255
14405
|
const channel = channelFlag === "presentation" || channelFlag === "editor" || channelFlag === "pr" || channelFlag === "tracker" ? channelFlag : "cli";
|
|
14256
14406
|
const change = workspace.changes.find(
|
|
@@ -14291,13 +14441,13 @@ async function runApprove(ctx) {
|
|
|
14291
14441
|
return {
|
|
14292
14442
|
exitCode: hasErrors2 ? 1 : 0,
|
|
14293
14443
|
diagnostics: result.diagnostics,
|
|
14294
|
-
data: result.approval ? { ...result.approval, file:
|
|
14444
|
+
data: result.approval ? { ...result.approval, file: path29.relative(ctx.cwd, result.file) } : void 0,
|
|
14295
14445
|
text: lines
|
|
14296
14446
|
};
|
|
14297
14447
|
}
|
|
14298
14448
|
|
|
14299
14449
|
// src/commands/archive.ts
|
|
14300
|
-
import
|
|
14450
|
+
import path30 from "path";
|
|
14301
14451
|
async function runArchive(ctx) {
|
|
14302
14452
|
const slug = ctx.positionals[0];
|
|
14303
14453
|
if (!slug) {
|
|
@@ -14321,27 +14471,31 @@ async function runArchive(ctx) {
|
|
|
14321
14471
|
}
|
|
14322
14472
|
const result = await archiveChange({ root, slug, language: config.project.language, dryRun });
|
|
14323
14473
|
const lines = [msg("archive.title", ctx.language), ""];
|
|
14324
|
-
|
|
14474
|
+
const operations = result.fold.applied.added.length + result.fold.applied.modified.length + result.fold.applied.removed.length + result.fold.applied.renamed.length;
|
|
14475
|
+
if (operations > 0) {
|
|
14325
14476
|
lines.push(`${dryRun ? "[dry-run] " : ""}${msg("archive.done", ctx.language)}`);
|
|
14326
14477
|
lines.push(` agregados: ${result.fold.applied.added.join(", ") || "\u2014"}`);
|
|
14327
14478
|
lines.push(` modificados: ${result.fold.applied.modified.join(", ") || "\u2014"}`);
|
|
14328
14479
|
lines.push(` eliminados: ${result.fold.applied.removed.join(", ") || "\u2014"}`);
|
|
14329
14480
|
lines.push(` renombrados: ${result.fold.applied.renamed.join(", ") || "\u2014"}`);
|
|
14330
|
-
|
|
14481
|
+
} else if (result.livingFix) {
|
|
14482
|
+
lines.push(`${dryRun ? "[dry-run] " : ""}Fix archivado y conservado como fix vivo.`);
|
|
14483
|
+
lines.push(` fix vivo: ${result.livingFix}`);
|
|
14331
14484
|
} else {
|
|
14332
14485
|
lines.push("El delta no contiene operaciones (ADDED/MODIFIED/REMOVED/RENAMED).");
|
|
14333
14486
|
}
|
|
14487
|
+
if (result.archivedTo) lines.push(` archivado en: ${path30.relative(ctx.cwd, result.archivedTo)}`);
|
|
14334
14488
|
const hasErrors2 = result.diagnostics.some((d) => d.severity === "error");
|
|
14335
14489
|
return {
|
|
14336
14490
|
exitCode: hasErrors2 ? 1 : 0,
|
|
14337
14491
|
diagnostics: result.diagnostics,
|
|
14338
|
-
data: { slug: result.slug, domain: result.domain, archivedTo: result.archivedTo, applied: result.fold.applied, dryRun: result.dryRun },
|
|
14492
|
+
data: { slug: result.slug, domain: result.domain, archivedTo: result.archivedTo, livingFix: result.livingFix, applied: result.fold.applied, dryRun: result.dryRun },
|
|
14339
14493
|
text: lines
|
|
14340
14494
|
};
|
|
14341
14495
|
}
|
|
14342
14496
|
|
|
14343
14497
|
// src/commands/ci.ts
|
|
14344
|
-
import
|
|
14498
|
+
import path31 from "path";
|
|
14345
14499
|
async function runCi(ctx) {
|
|
14346
14500
|
const { root, config } = await requireWorkspace(ctx);
|
|
14347
14501
|
const strict = flagBool(ctx.flags, "strict");
|
|
@@ -14367,10 +14521,10 @@ async function runCi(ctx) {
|
|
|
14367
14521
|
const lines = ["CI de SpecAtlas", ""];
|
|
14368
14522
|
let sarifWritten;
|
|
14369
14523
|
if (sarifPath) {
|
|
14370
|
-
const target =
|
|
14524
|
+
const target = path31.resolve(ctx.cwd, sarifPath);
|
|
14371
14525
|
try {
|
|
14372
14526
|
await writeText(target, toSarifText({ diagnostics: gate.diagnostics, root, version: cliVersion(), failed: gate.failed }));
|
|
14373
|
-
sarifWritten =
|
|
14527
|
+
sarifWritten = path31.relative(ctx.cwd, target);
|
|
14374
14528
|
} catch (err) {
|
|
14375
14529
|
diagnostics.push({
|
|
14376
14530
|
code: "ATLAS-CI-SARIF-001",
|
|
@@ -14405,12 +14559,12 @@ async function runCi(ctx) {
|
|
|
14405
14559
|
}
|
|
14406
14560
|
|
|
14407
14561
|
// src/commands/profile.ts
|
|
14408
|
-
import
|
|
14562
|
+
import path32 from "path";
|
|
14409
14563
|
async function runProfile(ctx) {
|
|
14410
14564
|
const action = ctx.positionals[0] ?? "detect";
|
|
14411
14565
|
const { root, workspace } = await requireWorkspace(ctx);
|
|
14412
14566
|
const officialDir = await resolveProfilesDir();
|
|
14413
|
-
const customDir =
|
|
14567
|
+
const customDir = path32.join(workspace.sddDir, "profiles", "custom");
|
|
14414
14568
|
const profiles = [...officialDir ? await loadProfilesFromDir(officialDir) : [], ...await loadProfilesFromDir(customDir)];
|
|
14415
14569
|
if (action === "detect") {
|
|
14416
14570
|
const result = await detectProfiles(root, profiles);
|
|
@@ -14439,7 +14593,7 @@ async function runProfile(ctx) {
|
|
|
14439
14593
|
if (!name) {
|
|
14440
14594
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-001", severity: "error", message: "Falta el nombre del perfil: satlas profile create <nombre>" }] };
|
|
14441
14595
|
}
|
|
14442
|
-
const file =
|
|
14596
|
+
const file = path32.join(customDir, `${name}.yaml`);
|
|
14443
14597
|
if (await exists(file)) {
|
|
14444
14598
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-002", severity: "error", message: `El perfil ya existe: ${file}` }] };
|
|
14445
14599
|
}
|
|
@@ -14448,7 +14602,7 @@ async function runProfile(ctx) {
|
|
|
14448
14602
|
exitCode: 0,
|
|
14449
14603
|
diagnostics: [],
|
|
14450
14604
|
data: { file },
|
|
14451
|
-
text: [`Perfil creado: ${
|
|
14605
|
+
text: [`Perfil creado: ${path32.relative(ctx.cwd, file)}`, "", "Ed\xEDtalo con los comandos reales de tu stack (build/lint/test) y tus anti-patrones."]
|
|
14452
14606
|
};
|
|
14453
14607
|
}
|
|
14454
14608
|
return {
|
|
@@ -14457,7 +14611,7 @@ async function runProfile(ctx) {
|
|
|
14457
14611
|
};
|
|
14458
14612
|
}
|
|
14459
14613
|
async function isInside(dir, name) {
|
|
14460
|
-
return exists(
|
|
14614
|
+
return exists(path32.join(dir, `${name}.yaml`));
|
|
14461
14615
|
}
|
|
14462
14616
|
function profileTemplate(name) {
|
|
14463
14617
|
return `name: ${name}
|
|
@@ -14485,7 +14639,7 @@ assumptions: []
|
|
|
14485
14639
|
}
|
|
14486
14640
|
|
|
14487
14641
|
// src/commands/doctor.ts
|
|
14488
|
-
import
|
|
14642
|
+
import path33 from "path";
|
|
14489
14643
|
async function runDoctorCommand(ctx) {
|
|
14490
14644
|
const { root, config } = await requireWorkspace(ctx);
|
|
14491
14645
|
const report = await runDoctor(root);
|
|
@@ -14510,7 +14664,7 @@ async function runDoctorCommand(ctx) {
|
|
|
14510
14664
|
lines.push("Sin hallazgos. El workspace est\xE1 sano.");
|
|
14511
14665
|
} else {
|
|
14512
14666
|
for (const finding of report.findings) {
|
|
14513
|
-
const location = finding.path ? ` ${
|
|
14667
|
+
const location = finding.path ? ` ${path33.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
|
|
14514
14668
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${location} \u2014 ${finding.message}`);
|
|
14515
14669
|
if (finding.suggestion) lines.push(` \u21B3 ${finding.suggestion}`);
|
|
14516
14670
|
}
|
|
@@ -14584,7 +14738,7 @@ async function runHelp(ctx, commandName) {
|
|
|
14584
14738
|
}
|
|
14585
14739
|
|
|
14586
14740
|
// src/commands/init.ts
|
|
14587
|
-
import
|
|
14741
|
+
import path34 from "path";
|
|
14588
14742
|
async function runInit(ctx) {
|
|
14589
14743
|
const languageFlag = flagString(ctx.flags, "language");
|
|
14590
14744
|
const language = languageFlag === "en" ? "en" : languageFlag === "es" ? "es" : void 0;
|
|
@@ -14631,11 +14785,11 @@ async function runInit(ctx) {
|
|
|
14631
14785
|
const lines = [msg("init.title", ctx.language), ""];
|
|
14632
14786
|
if (result.created.length > 0) {
|
|
14633
14787
|
lines.push(msg("init.done", ctx.language));
|
|
14634
|
-
for (const file of result.created) lines.push(` + ${
|
|
14788
|
+
for (const file of result.created) lines.push(` + ${path34.relative(ctx.cwd, file)}`);
|
|
14635
14789
|
if (compiled.length > 0) {
|
|
14636
14790
|
lines.push("");
|
|
14637
14791
|
lines.push("Adaptadores compilados:");
|
|
14638
|
-
for (const file of compiled) lines.push(` + ${
|
|
14792
|
+
for (const file of compiled) lines.push(` + ${path34.relative(ctx.cwd, path34.join(ctx.cwd, file))}`);
|
|
14639
14793
|
}
|
|
14640
14794
|
lines.push("");
|
|
14641
14795
|
lines.push("Siguiente: crea un cambio con `satlas new <slug>` y escribe la spec funcional.");
|
|
@@ -14645,8 +14799,8 @@ async function runInit(ctx) {
|
|
|
14645
14799
|
exitCode: hasErrors2 ? 1 : 0,
|
|
14646
14800
|
diagnostics: result.diagnostics,
|
|
14647
14801
|
data: {
|
|
14648
|
-
sddDir:
|
|
14649
|
-
created: result.created.map((f) =>
|
|
14802
|
+
sddDir: path34.relative(ctx.cwd, result.sddDir),
|
|
14803
|
+
created: result.created.map((f) => path34.relative(ctx.cwd, f)),
|
|
14650
14804
|
detected: result.detected?.matches.map((m) => ({ name: m.name, score: m.score })),
|
|
14651
14805
|
best: result.detected?.best?.name
|
|
14652
14806
|
},
|
|
@@ -14687,7 +14841,7 @@ async function runMetrics(ctx) {
|
|
|
14687
14841
|
}
|
|
14688
14842
|
|
|
14689
14843
|
// src/commands/mockup.ts
|
|
14690
|
-
import
|
|
14844
|
+
import path35 from "path";
|
|
14691
14845
|
async function runMockup(ctx) {
|
|
14692
14846
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
14693
14847
|
const slug = ctx.positionals[0];
|
|
@@ -14736,17 +14890,17 @@ async function runMockup(ctx) {
|
|
|
14736
14890
|
` pantallas: ${plan.screens.length}`,
|
|
14737
14891
|
...plan.screens.map((s) => ` - ${s.id}: ${s.title} (${s.illustrates.length} escenario(s))`),
|
|
14738
14892
|
"",
|
|
14739
|
-
` plan: ${
|
|
14740
|
-
` manifiesto: ${
|
|
14893
|
+
` plan: ${path35.relative(ctx.cwd, planFile)}`,
|
|
14894
|
+
` manifiesto: ${path35.relative(ctx.cwd, manifestFile)}`,
|
|
14741
14895
|
"",
|
|
14742
14896
|
"Siguiente: genera el HTML de cada pantalla (fase /satlas-mockup) y valida con `satlas mockup " + slug + " --check`."
|
|
14743
14897
|
];
|
|
14744
|
-
return { exitCode: 0, diagnostics: [], data: { plan, planFile:
|
|
14898
|
+
return { exitCode: 0, diagnostics: [], data: { plan, planFile: path35.relative(ctx.cwd, planFile), manifestFile: path35.relative(ctx.cwd, manifestFile) }, text: lines };
|
|
14745
14899
|
}
|
|
14746
14900
|
|
|
14747
14901
|
// src/mcp/server.ts
|
|
14748
14902
|
import { promises as fs3 } from "fs";
|
|
14749
|
-
import
|
|
14903
|
+
import path38 from "path";
|
|
14750
14904
|
import { createInterface } from "readline/promises";
|
|
14751
14905
|
|
|
14752
14906
|
// src/mcp/protocol.ts
|
|
@@ -14807,13 +14961,39 @@ function stringArg(args, key) {
|
|
|
14807
14961
|
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
14808
14962
|
}
|
|
14809
14963
|
|
|
14964
|
+
// src/mcp/tools/fixes.ts
|
|
14965
|
+
async function runAtlasFixes(_args, host) {
|
|
14966
|
+
const resolved = await host.getWorkspace();
|
|
14967
|
+
if (!resolved) return noWorkspaceResult();
|
|
14968
|
+
const fixes = await loadLivingFixes(resolved.root);
|
|
14969
|
+
if (fixes.length === 0) {
|
|
14970
|
+
const activeFix = resolved.workspace.changes.find((change) => change.meta?.lane === "fix");
|
|
14971
|
+
return jsonResult({
|
|
14972
|
+
fixes: [],
|
|
14973
|
+
message: "No hay fixes vivos registrados.",
|
|
14974
|
+
action: activeFix ? `satlas archive ${activeFix.slug}` : "satlas new <slug> --lane fix"
|
|
14975
|
+
});
|
|
14976
|
+
}
|
|
14977
|
+
return jsonResult({
|
|
14978
|
+
fixes: fixes.map((fix) => ({
|
|
14979
|
+
slug: fix.slug,
|
|
14980
|
+
date: fix.date,
|
|
14981
|
+
result: fix.result,
|
|
14982
|
+
domain: fix.domain,
|
|
14983
|
+
title: fix.title,
|
|
14984
|
+
covers: fix.covers,
|
|
14985
|
+
content: fix.content
|
|
14986
|
+
}))
|
|
14987
|
+
});
|
|
14988
|
+
}
|
|
14989
|
+
|
|
14810
14990
|
// src/mcp/tools/glossary.ts
|
|
14811
|
-
import
|
|
14991
|
+
import path36 from "path";
|
|
14812
14992
|
async function runAtlasGlossary(_args, host) {
|
|
14813
14993
|
const resolved = await host.getWorkspace();
|
|
14814
14994
|
if (!resolved) return noWorkspaceResult();
|
|
14815
14995
|
const { root, config } = resolved;
|
|
14816
|
-
const glossaryPath =
|
|
14996
|
+
const glossaryPath = path36.resolve(root, config.spec.glossary);
|
|
14817
14997
|
const raw = await readTextIfExists(glossaryPath);
|
|
14818
14998
|
if (raw === void 0) {
|
|
14819
14999
|
return jsonResult({
|
|
@@ -14859,7 +15039,7 @@ async function runAtlasImpact(args, host) {
|
|
|
14859
15039
|
}
|
|
14860
15040
|
|
|
14861
15041
|
// src/evaluate.ts
|
|
14862
|
-
import
|
|
15042
|
+
import path37 from "path";
|
|
14863
15043
|
function livingRequirementsMap2(specs) {
|
|
14864
15044
|
const map = /* @__PURE__ */ new Map();
|
|
14865
15045
|
for (const spec of specs) {
|
|
@@ -14868,7 +15048,7 @@ function livingRequirementsMap2(specs) {
|
|
|
14868
15048
|
return map;
|
|
14869
15049
|
}
|
|
14870
15050
|
async function evaluateChange(workspace, config, change, approvals) {
|
|
14871
|
-
const deltaPath =
|
|
15051
|
+
const deltaPath = path37.join(change.dir, "spec.md");
|
|
14872
15052
|
const deltaContent = await readTextIfExists(deltaPath);
|
|
14873
15053
|
const approval = verifyApproval(change, approvals, config, deltaContent ?? void 0);
|
|
14874
15054
|
const living = livingRequirementsMap2(workspace.specs);
|
|
@@ -15044,7 +15224,8 @@ var HANDLERS = {
|
|
|
15044
15224
|
atlas_validate: runAtlasValidate,
|
|
15045
15225
|
atlas_trace: runAtlasTrace,
|
|
15046
15226
|
atlas_impact: runAtlasImpact,
|
|
15047
|
-
atlas_glossary: runAtlasGlossary
|
|
15227
|
+
atlas_glossary: runAtlasGlossary,
|
|
15228
|
+
atlas_fixes: runAtlasFixes
|
|
15048
15229
|
};
|
|
15049
15230
|
async function runToolByName(name, args, host) {
|
|
15050
15231
|
const handler = HANDLERS[name];
|
|
@@ -15113,6 +15294,15 @@ function listTools() {
|
|
|
15113
15294
|
properties: {},
|
|
15114
15295
|
additionalProperties: false
|
|
15115
15296
|
}
|
|
15297
|
+
},
|
|
15298
|
+
{
|
|
15299
|
+
name: "atlas_fixes",
|
|
15300
|
+
description: "Fixes vivos (carril express ya archivado): identidad, resultado de su evidencia, contenido y requisitos que declaran. Solo lectura.",
|
|
15301
|
+
inputSchema: {
|
|
15302
|
+
type: "object",
|
|
15303
|
+
properties: {},
|
|
15304
|
+
additionalProperties: false
|
|
15305
|
+
}
|
|
15116
15306
|
}
|
|
15117
15307
|
];
|
|
15118
15308
|
}
|
|
@@ -15130,8 +15320,8 @@ var WorkspaceCache = class {
|
|
|
15130
15320
|
}
|
|
15131
15321
|
};
|
|
15132
15322
|
async function workspaceStamp(root) {
|
|
15133
|
-
const sddDir =
|
|
15134
|
-
const markers = [sddDir,
|
|
15323
|
+
const sddDir = path38.join(root, ".sdd");
|
|
15324
|
+
const markers = [sddDir, path38.join(sddDir, "config.yaml"), path38.join(sddDir, "changes"), path38.join(sddDir, "specs"), path38.join(sddDir, "approvals.yaml")];
|
|
15135
15325
|
let stamp = 0;
|
|
15136
15326
|
for (const marker of markers) {
|
|
15137
15327
|
try {
|
|
@@ -15149,7 +15339,7 @@ async function resolveMcpWorkspace(cwd, cache) {
|
|
|
15149
15339
|
const cached2 = cache.get(root);
|
|
15150
15340
|
if (cached2 && cached2.mtimeMs === stamp) return cached2.value;
|
|
15151
15341
|
const { workspace, config } = await loadWorkspace(root);
|
|
15152
|
-
const approvals = await loadApprovals(
|
|
15342
|
+
const approvals = await loadApprovals(path38.join(root, ".sdd"));
|
|
15153
15343
|
const value = { root, workspace, config, approvals: approvals.byArtifact };
|
|
15154
15344
|
cache.set(root, stamp, value);
|
|
15155
15345
|
return value;
|
|
@@ -15241,7 +15431,7 @@ async function runMcp(ctx) {
|
|
|
15241
15431
|
}
|
|
15242
15432
|
|
|
15243
15433
|
// src/commands/packs.ts
|
|
15244
|
-
import
|
|
15434
|
+
import path39 from "path";
|
|
15245
15435
|
async function runPacks(ctx) {
|
|
15246
15436
|
const { root, config, workspace } = await requireWorkspace(ctx);
|
|
15247
15437
|
const slug = flagString(ctx.flags, "check");
|
|
@@ -15299,14 +15489,14 @@ async function runPacks(ctx) {
|
|
|
15299
15489
|
data: {
|
|
15300
15490
|
slug,
|
|
15301
15491
|
packs: evaluations.map((evaluation) => ({ id: evaluation.pack.id, status: evaluation.status, passed: evaluation.passed, failed: evaluation.failed, results: evaluation.results })),
|
|
15302
|
-
analyzePath:
|
|
15492
|
+
analyzePath: path39.posix.join(".sdd", "changes", slug, "analyze.md")
|
|
15303
15493
|
},
|
|
15304
15494
|
text: lines
|
|
15305
15495
|
};
|
|
15306
15496
|
}
|
|
15307
15497
|
|
|
15308
15498
|
// src/commands/new.ts
|
|
15309
|
-
import
|
|
15499
|
+
import path40 from "path";
|
|
15310
15500
|
async function runNew(ctx) {
|
|
15311
15501
|
const slug = ctx.positionals[0];
|
|
15312
15502
|
if (!slug) {
|
|
@@ -15332,14 +15522,14 @@ async function runNew(ctx) {
|
|
|
15332
15522
|
msg("new.title", ctx.language),
|
|
15333
15523
|
"",
|
|
15334
15524
|
msg("new.done", ctx.language),
|
|
15335
|
-
...result.files.map((f) => ` + ${
|
|
15525
|
+
...result.files.map((f) => ` + ${path40.relative(ctx.cwd, f)}`),
|
|
15336
15526
|
"",
|
|
15337
15527
|
`Siguiente: /satlas.specify ${result.slug} \u2014 escribe la especificaci\xF3n 100% funcional y de negocio.`
|
|
15338
15528
|
];
|
|
15339
15529
|
return {
|
|
15340
15530
|
exitCode: hasErrors2 ? 1 : 0,
|
|
15341
15531
|
diagnostics: result.diagnostics,
|
|
15342
|
-
data: { slug: result.slug, files: result.files.map((f) =>
|
|
15532
|
+
data: { slug: result.slug, files: result.files.map((f) => path40.relative(ctx.cwd, f)) },
|
|
15343
15533
|
text: lines
|
|
15344
15534
|
};
|
|
15345
15535
|
}
|
|
@@ -15374,7 +15564,7 @@ async function runNext(ctx) {
|
|
|
15374
15564
|
}
|
|
15375
15565
|
|
|
15376
15566
|
// src/commands/present.ts
|
|
15377
|
-
import
|
|
15567
|
+
import path41 from "path";
|
|
15378
15568
|
async function runPresentCommand(ctx) {
|
|
15379
15569
|
const { root } = await requireWorkspace(ctx);
|
|
15380
15570
|
const slug = ctx.positionals[0];
|
|
@@ -15386,7 +15576,7 @@ async function runPresentCommand(ctx) {
|
|
|
15386
15576
|
if (result.path) {
|
|
15387
15577
|
lines.push("Propuesta generada");
|
|
15388
15578
|
lines.push("");
|
|
15389
|
-
lines.push(` archivo: ${
|
|
15579
|
+
lines.push(` archivo: ${path41.relative(ctx.cwd, result.path)}`);
|
|
15390
15580
|
if (result.hash) lines.push(` hash de la spec: ${shortHash(result.hash)}`);
|
|
15391
15581
|
lines.push("");
|
|
15392
15582
|
lines.push("\xC1brela en el navegador y comp\xE1rtela con el stakeholder.");
|
|
@@ -15396,7 +15586,7 @@ async function runPresentCommand(ctx) {
|
|
|
15396
15586
|
return {
|
|
15397
15587
|
exitCode: hasErrors2 ? 1 : 0,
|
|
15398
15588
|
diagnostics: result.diagnostics,
|
|
15399
|
-
data: result.path ? { path:
|
|
15589
|
+
data: result.path ? { path: path41.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
|
|
15400
15590
|
text: lines
|
|
15401
15591
|
};
|
|
15402
15592
|
}
|
|
@@ -15505,6 +15695,16 @@ async function runStatus(ctx) {
|
|
|
15505
15695
|
for (const spec of workspace.specs) {
|
|
15506
15696
|
lines.push(` ${spec.domain} \u2014 ${spec.spec.requirements.length} requisitos`);
|
|
15507
15697
|
}
|
|
15698
|
+
const fixes = await loadLivingFixes(root);
|
|
15699
|
+
lines.push("");
|
|
15700
|
+
lines.push(`${msg("status.fixes", ctx.language)}: ${fixes.length}`);
|
|
15701
|
+
if (fixes.length === 0) {
|
|
15702
|
+
lines.push(" (se llenan al archivar un fix)");
|
|
15703
|
+
} else {
|
|
15704
|
+
for (const fix of fixes) {
|
|
15705
|
+
lines.push(` ${fix.date || "\u2014"} ${(fix.domain ?? "\u2014").padEnd(10)} ${fix.slug} \u2014 ${fix.result}`);
|
|
15706
|
+
}
|
|
15707
|
+
}
|
|
15508
15708
|
const diagnostics = [...workspace.diagnostics, ...advisory.diagnostics];
|
|
15509
15709
|
const errors = diagnostics.filter((d) => d.severity === "error").length;
|
|
15510
15710
|
return {
|
|
@@ -15513,6 +15713,13 @@ async function runStatus(ctx) {
|
|
|
15513
15713
|
data: {
|
|
15514
15714
|
changes,
|
|
15515
15715
|
specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length })),
|
|
15716
|
+
fixes: fixes.map((fix) => ({
|
|
15717
|
+
slug: fix.slug,
|
|
15718
|
+
date: fix.date,
|
|
15719
|
+
result: fix.result,
|
|
15720
|
+
...fix.domain !== void 0 ? { domain: fix.domain } : {},
|
|
15721
|
+
covers: fix.covers
|
|
15722
|
+
})),
|
|
15516
15723
|
upgrade: {
|
|
15517
15724
|
currentVersion: advisory.plan.currentVersion,
|
|
15518
15725
|
pending: advisory.plan.pending.length,
|
|
@@ -15525,7 +15732,7 @@ async function runStatus(ctx) {
|
|
|
15525
15732
|
}
|
|
15526
15733
|
|
|
15527
15734
|
// src/commands/trace.ts
|
|
15528
|
-
import
|
|
15735
|
+
import path42 from "path";
|
|
15529
15736
|
async function runTrace(ctx) {
|
|
15530
15737
|
const { workspace, config } = await requireWorkspace(ctx);
|
|
15531
15738
|
const slug = flagString(ctx.flags, "change");
|
|
@@ -15545,7 +15752,7 @@ async function runTrace(ctx) {
|
|
|
15545
15752
|
data.push({ slug: change.slug, nodes: result.graph.nodes.length, edges: result.graph.edges.length, summary: result.summary, findings: result.findings });
|
|
15546
15753
|
lines.push(` ${change.slug}: ${result.graph.nodes.length} nodos, ${result.graph.edges.length} aristas, ${errors} errores, ${result.summary.warnings} avisos`);
|
|
15547
15754
|
for (const finding of result.findings) {
|
|
15548
|
-
const location = finding.path ? ` (${
|
|
15755
|
+
const location = finding.path ? ` (${path42.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
|
|
15549
15756
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}${location}`);
|
|
15550
15757
|
}
|
|
15551
15758
|
}
|
|
@@ -15555,7 +15762,7 @@ async function runTrace(ctx) {
|
|
|
15555
15762
|
}
|
|
15556
15763
|
|
|
15557
15764
|
// src/commands/upgrade.ts
|
|
15558
|
-
import
|
|
15765
|
+
import path43 from "path";
|
|
15559
15766
|
async function runUpgrade(ctx) {
|
|
15560
15767
|
const { root } = await requireWorkspace(ctx);
|
|
15561
15768
|
const apply = flagBool(ctx.flags, "apply");
|
|
@@ -15609,7 +15816,7 @@ function renderPreview(plan, root, ctx) {
|
|
|
15609
15816
|
}
|
|
15610
15817
|
for (const item of plan.unreadable) {
|
|
15611
15818
|
lines.push(` Ilegible: "${item.artifact}" \u2014 ${item.reason}`);
|
|
15612
|
-
lines.push(` \u21B3 ${
|
|
15819
|
+
lines.push(` \u21B3 ${path43.relative(ctx.cwd, item.path)}`);
|
|
15613
15820
|
}
|
|
15614
15821
|
return { exitCode: 0, diagnostics: [], data: summarizable(plan), text: lines };
|
|
15615
15822
|
}
|
|
@@ -15672,7 +15879,7 @@ function renderRollback(report, ctx) {
|
|
|
15672
15879
|
}
|
|
15673
15880
|
|
|
15674
15881
|
// src/commands/validate.ts
|
|
15675
|
-
import
|
|
15882
|
+
import path44 from "path";
|
|
15676
15883
|
async function runValidate(ctx) {
|
|
15677
15884
|
const { root, workspace, config, approvals } = await requireWorkspace(ctx);
|
|
15678
15885
|
const slug = flagString(ctx.flags, "change");
|
|
@@ -15699,7 +15906,7 @@ async function runValidate(ctx) {
|
|
|
15699
15906
|
for (const finding of evaluation.lintFindings) {
|
|
15700
15907
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${finding.line ? `:${finding.line}` : ""} \u2014 ${finding.message}`);
|
|
15701
15908
|
}
|
|
15702
|
-
lines.push(` archivo: ${
|
|
15909
|
+
lines.push(` archivo: ${path44.relative(ctx.cwd, path44.join(change.dir, "spec.md"))}`);
|
|
15703
15910
|
}
|
|
15704
15911
|
if (targets.length === 0) lines.push("Sin cambios activos.");
|
|
15705
15912
|
if (advisory && advisory.plan.pending.length > 0) {
|
|
@@ -15727,7 +15934,7 @@ async function runValidate(ctx) {
|
|
|
15727
15934
|
}
|
|
15728
15935
|
|
|
15729
15936
|
// src/commands/verify.ts
|
|
15730
|
-
import
|
|
15937
|
+
import path45 from "path";
|
|
15731
15938
|
async function runVerify(ctx) {
|
|
15732
15939
|
const { root, workspace } = await requireWorkspace(ctx);
|
|
15733
15940
|
const slug = ctx.positionals[0];
|
|
@@ -15770,7 +15977,7 @@ async function runVerify(ctx) {
|
|
|
15770
15977
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-VERIFY-001", severity: "error", message: "Falta --by <nombre> (o define meta.owner)" }] };
|
|
15771
15978
|
}
|
|
15772
15979
|
const profilesDir = await resolveProfilesDir();
|
|
15773
|
-
const profile = await loadActiveProfile(
|
|
15980
|
+
const profile = await loadActiveProfile(path45.join(root, ".sdd"), [profilesDir ?? "", path45.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
|
|
15774
15981
|
const record = await recordEvidence({
|
|
15775
15982
|
root,
|
|
15776
15983
|
slug,
|
|
@@ -15792,7 +15999,7 @@ async function runVerify(ctx) {
|
|
|
15792
15999
|
if (record.evidence.command) lines.push(` comando: ${record.evidence.command}`);
|
|
15793
16000
|
lines.push(` resultado: ${record.evidence.result}`);
|
|
15794
16001
|
if (record.evidence.outputHash) lines.push(` hash: ${record.evidence.outputHash}`);
|
|
15795
|
-
lines.push(` archivo: ${
|
|
16002
|
+
lines.push(` archivo: ${path45.relative(ctx.cwd, record.path)}`);
|
|
15796
16003
|
if (record.stdout) {
|
|
15797
16004
|
lines.push("");
|
|
15798
16005
|
lines.push(" salida (\xFAltimas l\xEDneas):");
|
|
@@ -15807,7 +16014,7 @@ async function runVerify(ctx) {
|
|
|
15807
16014
|
return {
|
|
15808
16015
|
exitCode: record.exitCode === 0 ? 0 : 1,
|
|
15809
16016
|
diagnostics: record.diagnostics,
|
|
15810
|
-
data: { evidence: record.evidence, path:
|
|
16017
|
+
data: { evidence: record.evidence, path: path45.relative(ctx.cwd, record.path), exitCode: record.exitCode },
|
|
15811
16018
|
text: lines
|
|
15812
16019
|
};
|
|
15813
16020
|
}
|
|
@@ -15907,7 +16114,7 @@ async function requireWorkspace(ctx) {
|
|
|
15907
16114
|
throw new CliError("ATLAS-WS-001", msg("cli.noWorkspace", ctx.language), 2);
|
|
15908
16115
|
}
|
|
15909
16116
|
const { workspace, config } = await loadWorkspace(root);
|
|
15910
|
-
const approvals = await loadApprovals(
|
|
16117
|
+
const approvals = await loadApprovals(path46.join(root, ".sdd"));
|
|
15911
16118
|
return { root, workspace, config, approvals: approvals.byArtifact };
|
|
15912
16119
|
}
|
|
15913
16120
|
var CliError = class extends Error {
|
|
@@ -15974,7 +16181,7 @@ function printResult(command, result, json2, language) {
|
|
|
15974
16181
|
}
|
|
15975
16182
|
const lines = [...result.text ?? []];
|
|
15976
16183
|
for (const d of result.diagnostics) {
|
|
15977
|
-
const location = d.path ? ` ${
|
|
16184
|
+
const location = d.path ? ` ${path46.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
|
|
15978
16185
|
lines.push(`${d.severity === "error" ? "ERROR" : d.severity === "warning" ? "AVISO" : "NOTA"} ${d.code}${location} \u2014 ${d.message}`);
|
|
15979
16186
|
if (d.suggestion) lines.push(` \u21B3 ${d.suggestion}`);
|
|
15980
16187
|
}
|