specatlas 0.1.36 → 0.1.38
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 +452 -161
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -5810,7 +5810,7 @@ var require_pako = __commonJS({
|
|
|
5810
5810
|
});
|
|
5811
5811
|
|
|
5812
5812
|
// src/cli.ts
|
|
5813
|
-
import
|
|
5813
|
+
import path62 from "path";
|
|
5814
5814
|
|
|
5815
5815
|
// ../core/dist/index.js
|
|
5816
5816
|
import path from "path";
|
|
@@ -23473,14 +23473,14 @@ var parameters = /* @__PURE__ */ new Map([
|
|
|
23473
23473
|
["Z", 0],
|
|
23474
23474
|
["z", 0]
|
|
23475
23475
|
]);
|
|
23476
|
-
var parse = function(
|
|
23476
|
+
var parse = function(path63) {
|
|
23477
23477
|
var cmd;
|
|
23478
23478
|
var ret = [];
|
|
23479
23479
|
var args = [];
|
|
23480
23480
|
var curArg = "";
|
|
23481
23481
|
var foundDecimal = false;
|
|
23482
23482
|
var params = 0;
|
|
23483
|
-
for (var _i = 0, path_1 =
|
|
23483
|
+
for (var _i = 0, path_1 = path63; _i < path_1.length; _i++) {
|
|
23484
23484
|
var c = path_1[_i];
|
|
23485
23485
|
if (parameters.has(c)) {
|
|
23486
23486
|
params = parameters.get(c);
|
|
@@ -23794,8 +23794,8 @@ var segmentToBezier = function(cx1, cy1, th0, th1, rx, ry, sinTh, cosTh) {
|
|
|
23794
23794
|
];
|
|
23795
23795
|
return result;
|
|
23796
23796
|
};
|
|
23797
|
-
var svgPathToOperators = function(
|
|
23798
|
-
return apply(parse(
|
|
23797
|
+
var svgPathToOperators = function(path63) {
|
|
23798
|
+
return apply(parse(path63));
|
|
23799
23799
|
};
|
|
23800
23800
|
|
|
23801
23801
|
// ../../node_modules/.pnpm/pdf-lib@1.17.1/node_modules/pdf-lib/es/api/operations.js
|
|
@@ -23952,7 +23952,7 @@ var drawEllipse = function(options) {
|
|
|
23952
23952
|
popGraphicsState()
|
|
23953
23953
|
]).filter(Boolean);
|
|
23954
23954
|
};
|
|
23955
|
-
var drawSvgPath = function(
|
|
23955
|
+
var drawSvgPath = function(path63, options) {
|
|
23956
23956
|
var _a, _b, _c;
|
|
23957
23957
|
return __spreadArrays([
|
|
23958
23958
|
pushGraphicsState(),
|
|
@@ -23966,7 +23966,7 @@ var drawSvgPath = function(path62, options) {
|
|
|
23966
23966
|
options.borderWidth && setLineWidth(options.borderWidth),
|
|
23967
23967
|
options.borderLineCap && setLineCap(options.borderLineCap),
|
|
23968
23968
|
setDashPattern((_b = options.borderDashArray) !== null && _b !== void 0 ? _b : [], (_c = options.borderDashPhase) !== null && _c !== void 0 ? _c : 0)
|
|
23969
|
-
], svgPathToOperators(
|
|
23969
|
+
], svgPathToOperators(path63), [
|
|
23970
23970
|
// prettier-ignore
|
|
23971
23971
|
options.color && options.borderWidth ? fillAndStroke() : options.color ? fill() : options.borderColor ? stroke() : closePath(),
|
|
23972
23972
|
popGraphicsState()
|
|
@@ -27948,12 +27948,12 @@ var PDFPage = (
|
|
|
27948
27948
|
graphicsState: graphicsStateKey
|
|
27949
27949
|
}));
|
|
27950
27950
|
};
|
|
27951
|
-
PDFPage2.prototype.drawSvgPath = function(
|
|
27951
|
+
PDFPage2.prototype.drawSvgPath = function(path63, options) {
|
|
27952
27952
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
27953
27953
|
if (options === void 0) {
|
|
27954
27954
|
options = {};
|
|
27955
27955
|
}
|
|
27956
|
-
assertIs(
|
|
27956
|
+
assertIs(path63, "path", ["string"]);
|
|
27957
27957
|
assertOrUndefined(options.x, "options.x", ["number"]);
|
|
27958
27958
|
assertOrUndefined(options.y, "options.y", ["number"]);
|
|
27959
27959
|
assertOrUndefined(options.scale, "options.scale", ["number"]);
|
|
@@ -27982,7 +27982,7 @@ var PDFPage = (
|
|
|
27982
27982
|
options.borderColor = rgb(0, 0, 0);
|
|
27983
27983
|
}
|
|
27984
27984
|
var contentStream = this.getContentStream();
|
|
27985
|
-
contentStream.push.apply(contentStream, drawSvgPath(
|
|
27985
|
+
contentStream.push.apply(contentStream, drawSvgPath(path63, {
|
|
27986
27986
|
x: (_a = options.x) !== null && _a !== void 0 ? _a : this.x,
|
|
27987
27987
|
y: (_b = options.y) !== null && _b !== void 0 ? _b : this.y,
|
|
27988
27988
|
scale: options.scale,
|
|
@@ -29375,10 +29375,12 @@ import path23 from "path";
|
|
|
29375
29375
|
import { promises as fs2 } from "fs";
|
|
29376
29376
|
import path24 from "path";
|
|
29377
29377
|
import { parse as parseYaml11, stringify as stringifyYaml8 } from "yaml";
|
|
29378
|
+
import os from "os";
|
|
29378
29379
|
import path25 from "path";
|
|
29379
29380
|
import path26 from "path";
|
|
29380
29381
|
import path27 from "path";
|
|
29381
29382
|
import path28 from "path";
|
|
29383
|
+
import path29 from "path";
|
|
29382
29384
|
var CORE_VERSION = "0.0.1";
|
|
29383
29385
|
var DIAGNOSTIC_FAMILIES = [
|
|
29384
29386
|
{ prefix: "LINT-STR", title: "Estructura", description: "La forma del documento: ids, encabezados y bloques donde corresponde." },
|
|
@@ -30710,13 +30712,13 @@ var FIELD_LABEL = {
|
|
|
30710
30712
|
when: "un CUANDO",
|
|
30711
30713
|
then: "un ENTONCES"
|
|
30712
30714
|
};
|
|
30713
|
-
function lintPlaceholders(req,
|
|
30715
|
+
function lintPlaceholders(req, path302) {
|
|
30714
30716
|
const hits = requirementPlaceholders(req);
|
|
30715
30717
|
if (hits.length === 0) return [];
|
|
30716
30718
|
if (isSkeletonRequirement(req)) {
|
|
30717
30719
|
return [
|
|
30718
30720
|
diag("LINT-BIZ-003", "error", `El requisito ${req.id} sigue siendo la plantilla de \`satlas new\`: no especifica nada todav\xEDa`, {
|
|
30719
|
-
path:
|
|
30721
|
+
path: path302,
|
|
30720
30722
|
line: req.line,
|
|
30721
30723
|
suggestion: "Escribe la necesidad de negocio, sus reglas y el CUANDO/ENTONCES de cada escenario"
|
|
30722
30724
|
})
|
|
@@ -30725,7 +30727,7 @@ function lintPlaceholders(req, path292) {
|
|
|
30725
30727
|
return hits.map((hit) => {
|
|
30726
30728
|
const where = hit.scenario ? `${hit.scenario}` : req.id;
|
|
30727
30729
|
return diag("LINT-BIZ-003", "error", `Texto de plantilla sin completar en ${where}: ${FIELD_LABEL[hit.field]} sigue siendo el hueco de \`satlas new\``, {
|
|
30728
|
-
path:
|
|
30730
|
+
path: path302,
|
|
30729
30731
|
line: hit.line,
|
|
30730
30732
|
suggestion: "Escribe el comportamiento real de negocio en lugar del texto entre par\xE9ntesis de la plantilla"
|
|
30731
30733
|
});
|
|
@@ -30755,14 +30757,14 @@ function wordBoundary(text, term) {
|
|
|
30755
30757
|
const re = new RegExp(`(?<![\\p{L}\\p{N}])${escaped}(?![\\p{L}\\p{N}])`, "iu");
|
|
30756
30758
|
return re.test(text);
|
|
30757
30759
|
}
|
|
30758
|
-
function lintText(text, code, terms, label,
|
|
30760
|
+
function lintText(text, code, terms, label, path302, line) {
|
|
30759
30761
|
const out = [];
|
|
30760
30762
|
const lower = text.toLowerCase();
|
|
30761
30763
|
for (const term of terms) {
|
|
30762
30764
|
if (wordBoundary(lower, term)) {
|
|
30763
30765
|
out.push(
|
|
30764
30766
|
diag(code, "error", `${label}: "${term}"`, {
|
|
30765
|
-
path:
|
|
30767
|
+
path: path302,
|
|
30766
30768
|
line,
|
|
30767
30769
|
suggestion: "La especificaci\xF3n es funcional y de negocio: describe comportamiento, no tecnolog\xEDa ni adjetivos vagos"
|
|
30768
30770
|
})
|
|
@@ -30771,7 +30773,7 @@ function lintText(text, code, terms, label, path292, line) {
|
|
|
30771
30773
|
}
|
|
30772
30774
|
return out;
|
|
30773
30775
|
}
|
|
30774
|
-
function lintRequirement(req,
|
|
30776
|
+
function lintRequirement(req, path302, opts = {}) {
|
|
30775
30777
|
const out = [];
|
|
30776
30778
|
const vague = opts.language === "en" ? VAGUE_EN : VAGUE_ES;
|
|
30777
30779
|
const tech = opts.language === "en" ? TECH_EN : TECH_ES;
|
|
@@ -30782,33 +30784,33 @@ function lintRequirement(req, path292, opts = {}) {
|
|
|
30782
30784
|
...req.scenarios.flatMap((s) => [...s.when.map((w) => ({ text: w, line: s.line })), ...s.then.map((t) => ({ text: t, line: s.line }))])
|
|
30783
30785
|
];
|
|
30784
30786
|
for (const part of parts) {
|
|
30785
|
-
out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n",
|
|
30787
|
+
out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path302, part.line));
|
|
30786
30788
|
if (opts.businessOnly !== false) {
|
|
30787
|
-
out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio",
|
|
30789
|
+
out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path302, part.line));
|
|
30788
30790
|
}
|
|
30789
30791
|
}
|
|
30790
|
-
out.push(...lintPlaceholders(req,
|
|
30792
|
+
out.push(...lintPlaceholders(req, path302));
|
|
30791
30793
|
if (req.scenarios.length === 0) {
|
|
30792
|
-
out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path:
|
|
30794
|
+
out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path302, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
|
|
30793
30795
|
}
|
|
30794
30796
|
return out;
|
|
30795
30797
|
}
|
|
30796
|
-
function lintDelta(delta, livingRequirements,
|
|
30798
|
+
function lintDelta(delta, livingRequirements, path302, opts = {}) {
|
|
30797
30799
|
const out = [...delta.diagnostics];
|
|
30798
30800
|
for (const req of [...delta.added, ...delta.modified]) {
|
|
30799
|
-
out.push(...lintRequirement(req,
|
|
30801
|
+
out.push(...lintRequirement(req, path302, opts));
|
|
30800
30802
|
}
|
|
30801
30803
|
for (const req of delta.modified) {
|
|
30802
30804
|
const living = livingRequirements.get(req.id);
|
|
30803
30805
|
if (!living) {
|
|
30804
|
-
out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path:
|
|
30806
|
+
out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path302, line: req.line }));
|
|
30805
30807
|
continue;
|
|
30806
30808
|
}
|
|
30807
30809
|
for (const existing of living.scenarios) {
|
|
30808
30810
|
if (!req.scenarios.some((s) => s.id === existing.id)) {
|
|
30809
30811
|
out.push(
|
|
30810
30812
|
diag("TRACE-007", "error", `MODIFIED ${req.id} pierde el escenario ${existing.id}: copia el bloque completo`, {
|
|
30811
|
-
path:
|
|
30813
|
+
path: path302,
|
|
30812
30814
|
line: req.line,
|
|
30813
30815
|
suggestion: "Copia el bloque completo de la spec viva y ed\xEDtalo; para quitarlo, decl\xE1ralo en REMOVED"
|
|
30814
30816
|
})
|
|
@@ -30819,15 +30821,15 @@ function lintDelta(delta, livingRequirements, path292, opts = {}) {
|
|
|
30819
30821
|
for (const req of delta.removed) {
|
|
30820
30822
|
const living = livingRequirements.get(req.id);
|
|
30821
30823
|
if (!living) {
|
|
30822
|
-
out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path:
|
|
30824
|
+
out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path302, line: req.line }));
|
|
30823
30825
|
}
|
|
30824
30826
|
}
|
|
30825
30827
|
for (const rename2 of delta.renamed) {
|
|
30826
30828
|
if (rename2.from.id !== rename2.to.id) {
|
|
30827
|
-
out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path:
|
|
30829
|
+
out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path302, line: rename2.line }));
|
|
30828
30830
|
}
|
|
30829
30831
|
if (!livingRequirements.has(rename2.from.id)) {
|
|
30830
|
-
out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path:
|
|
30832
|
+
out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path302, line: rename2.line }));
|
|
30831
30833
|
}
|
|
30832
30834
|
}
|
|
30833
30835
|
return out;
|
|
@@ -30851,7 +30853,7 @@ var MERMAID_KEYWORDS = [
|
|
|
30851
30853
|
"architecture-beta"
|
|
30852
30854
|
];
|
|
30853
30855
|
var MERMAID_BLOCKS = /^\s*(alt|loop|opt|par|rect|critical|break|subgraph)\b/;
|
|
30854
|
-
function lintPlan(planText,
|
|
30856
|
+
function lintPlan(planText, path302) {
|
|
30855
30857
|
const out = [];
|
|
30856
30858
|
const blocks = [...planText.matchAll(/```mermaid\r?\n([\s\S]*?)```/g)];
|
|
30857
30859
|
for (const [index, block] of blocks.entries()) {
|
|
@@ -30861,7 +30863,7 @@ function lintPlan(planText, path292) {
|
|
|
30861
30863
|
if (!MERMAID_KEYWORDS.some((keyword) => first.startsWith(keyword))) {
|
|
30862
30864
|
out.push(
|
|
30863
30865
|
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)}"`, {
|
|
30864
|
-
path:
|
|
30866
|
+
path: path302,
|
|
30865
30867
|
suggestion: "Corrige el tipo del diagrama o elimina el bloque"
|
|
30866
30868
|
})
|
|
30867
30869
|
);
|
|
@@ -30875,7 +30877,7 @@ function lintPlan(planText, path292) {
|
|
|
30875
30877
|
if (open !== 0) {
|
|
30876
30878
|
out.push(
|
|
30877
30879
|
diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: faltan ${Math.abs(open)} \`end\` (bloques alt/loop/subgraph sin cerrar)`, {
|
|
30878
|
-
path:
|
|
30880
|
+
path: path302,
|
|
30879
30881
|
suggestion: "Cierra cada bloque alt/loop/opt/par/rect/subgraph con `end`"
|
|
30880
30882
|
})
|
|
30881
30883
|
);
|
|
@@ -30887,7 +30889,7 @@ function lintPlan(planText, path292) {
|
|
|
30887
30889
|
if (message.includes(";")) {
|
|
30888
30890
|
out.push(
|
|
30889
30891
|
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`, {
|
|
30890
|
-
path:
|
|
30892
|
+
path: path302,
|
|
30891
30893
|
suggestion: "Sustituye `;` por `\xB7` o `,` en los mensajes de sequenceDiagram"
|
|
30892
30894
|
})
|
|
30893
30895
|
);
|
|
@@ -34463,6 +34465,11 @@ function labelsFor(language) {
|
|
|
34463
34465
|
signatureName: "Approver name",
|
|
34464
34466
|
signatureDate: "Signature date",
|
|
34465
34467
|
signatureNote: "The signature is recorded with the specification hash and the date.",
|
|
34468
|
+
signedBy: "Approved by",
|
|
34469
|
+
signedAt: "Date",
|
|
34470
|
+
signedHash: "Signed hash",
|
|
34471
|
+
signedChannel: "Channel",
|
|
34472
|
+
signHere: "Sign here to approve this proposal.",
|
|
34466
34473
|
openMockup: "Open mockup",
|
|
34467
34474
|
mockupHint: "Each screen opens full size in its own tab. In a browser they are also previewed inline.",
|
|
34468
34475
|
illustrates: "Illustrates"
|
|
@@ -34494,6 +34501,11 @@ function labelsFor(language) {
|
|
|
34494
34501
|
missingMockups: "Mockups declarados que faltan",
|
|
34495
34502
|
screenshots: "Capturas",
|
|
34496
34503
|
print: "Imprimir / Guardar PDF",
|
|
34504
|
+
signedBy: "Aprobada por",
|
|
34505
|
+
signedAt: "Fecha",
|
|
34506
|
+
signedHash: "Huella firmada",
|
|
34507
|
+
signedChannel: "Canal",
|
|
34508
|
+
signHere: "Firma aqu\xED para aprobar esta propuesta.",
|
|
34497
34509
|
signatureName: "Nombre de quien aprueba",
|
|
34498
34510
|
signatureDate: "Fecha de la firma",
|
|
34499
34511
|
signatureNote: "La firma se registra con la huella de la especificaci\xF3n y la fecha.",
|
|
@@ -34576,6 +34588,8 @@ th { background:var(--elev); font-size:11px; text-transform:uppercase; letter-sp
|
|
|
34576
34588
|
.grid-shots { display:grid; grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); gap:12px; margin-top:12px; }
|
|
34577
34589
|
.grid-shots img { width:100%; border:1px solid var(--line); border-radius:10px; }
|
|
34578
34590
|
.signature { display:grid; grid-template-columns:1.2fr .8fr; gap:18px; }
|
|
34591
|
+
.sign-line.signed { border-style:solid; border-color:var(--ok); background:color-mix(in srgb, var(--ok) 8%, transparent); }
|
|
34592
|
+
.sign-line .mono { font-family:ui-monospace, Consolas, monospace; font-size:11.5px; word-break:break-all; }
|
|
34579
34593
|
.sign-line { border:1px dashed var(--accent); border-radius:8px; padding:16px; background:var(--accent-soft); }
|
|
34580
34594
|
.sign-line .sl-row { display:flex; justify-content:space-between; gap:12px; border-bottom:1px solid var(--line); padding:0 0 6px; margin-bottom:18px; color:var(--muted); font-size:12.5px; }
|
|
34581
34595
|
.sign-line .sl-row:last-child { border-bottom:0; margin-bottom:0; padding-top:14px; }
|
|
@@ -34622,6 +34636,15 @@ function page(input) {
|
|
|
34622
34636
|
return `<tr><td><code>${esc(s.id)}</code></td><td>${esc(s.title)}</td><td><span class="pill ${cls}">${esc(label)}</span></td></tr>`;
|
|
34623
34637
|
}).join("");
|
|
34624
34638
|
const signatureState = input.approvalStatus === "valid" && input.approval ? `<div class="sign-state ok"><span class="ss-label">${esc(l.approve)}</span><b>${esc(l.approveDone.replace("{by}", input.approval.by).replace("{at}", input.approval.at))}</b></div>` : input.approvalStatus === "stale" && input.approval ? `<div class="sign-state stale"><span class="ss-label">${esc(l.approve)}</span><b>${esc(l.approveStale)}</b><p>${esc(l.approveDone.replace("{by}", input.approval.by).replace("{at}", input.approval.at))}</p></div>` : `<div class="sign-state"><span class="ss-label">${esc(l.approve)}</span><b>${esc(l.approvePending)}</b><p>${esc(l.approveText)}</p></div>`;
|
|
34639
|
+
const signatureBlock = input.approvalStatus === "valid" && input.approval ? `<div class="sign-line signed">
|
|
34640
|
+
<div class="sl-row"><span>${esc(l.signedBy)}</span><b>${esc(input.approval.by)}</b></div>
|
|
34641
|
+
<div class="sl-row"><span>${esc(l.signedAt)}</span><b>${esc(input.approval.at)}</b></div>
|
|
34642
|
+
<div class="sl-row"><span>${esc(l.signedHash)}</span><span class="mono">${esc(input.hash)}</span></div>
|
|
34643
|
+
</div>` : `<div class="sign-line">
|
|
34644
|
+
<p class="sign-help" style="margin:0 0 12px">${esc(l.signHere)}</p>
|
|
34645
|
+
<div class="sl-row"><span>${esc(l.signatureName)}</span><span style="font-family:ui-monospace,Consolas,monospace">____________________</span></div>
|
|
34646
|
+
<div class="sl-row"><span>${esc(l.signatureDate)}</span><span style="font-family:ui-monospace,Consolas,monospace">____ / ____ / ________</span></div>
|
|
34647
|
+
</div>`;
|
|
34625
34648
|
const mockupHtml = input.mockups.length === 0 ? `<div class="callout">${esc(l.noMockups)}</div>` : `<p class="sign-help" style="margin:0 0 12px">${esc(l.mockupHint)}</p><div class="mockups">${input.mockups.map(
|
|
34626
34649
|
(m) => `<div class="mockup ${m.platform === "mobile" ? "phone" : ""}" data-mockup="${esc(m.file)}" data-title="${esc(m.title)}"><header><span>${esc(m.title)}</span><a class="btn" href="${esc(m.file)}" target="_blank" rel="noopener">${esc(l.openMockup)}</a></header><div class="mockup-body"><span class="mockup-file">${esc(m.file)}</span>${m.illustrates.length > 0 ? `<span class="mockup-scn">${esc(l.illustrates)}: ${m.illustrates.map((id) => esc(id)).join(", ")}</span>` : ""}</div></div>`
|
|
34627
34650
|
).join("")}</div>`;
|
|
@@ -34716,14 +34739,11 @@ function page(input) {
|
|
|
34716
34739
|
<div class="card-head"><span class="num">05</span><h2>${esc(l.approve)}</h2><span class="sub">acto humano \xB7 queda auditado</span></div>
|
|
34717
34740
|
<div class="card-body">
|
|
34718
34741
|
<div class="signature">
|
|
34719
|
-
|
|
34720
|
-
<div class="sl-row"><span>${esc(l.signatureName)}</span><span style="font-family:ui-monospace,Consolas,monospace">____________________</span></div>
|
|
34721
|
-
<div class="sl-row"><span>${esc(l.signatureDate)}</span><span style="font-family:ui-monospace,Consolas,monospace">____ / ____ / ________</span></div>
|
|
34722
|
-
</div>
|
|
34742
|
+
${signatureBlock}
|
|
34723
34743
|
<div>
|
|
34724
34744
|
<p class="sign-help">${esc(l.signatureNote)}</p>
|
|
34725
|
-
|
|
34726
|
-
|
|
34745
|
+
${input.approvalStatus === "valid" ? "" : `<p class="sign-help" style="margin-top:8px"><code>${esc(input.approveHint)}</code></p>`}
|
|
34746
|
+
${input.approvalStatus === "stale" ? `<p class="sign-help" style="margin-top:10px">${esc(l.approveStale)}</p>` : ""}
|
|
34727
34747
|
</div>
|
|
34728
34748
|
</div>
|
|
34729
34749
|
</div>
|
|
@@ -35712,6 +35732,140 @@ async function upgradeAdvisory(root) {
|
|
|
35712
35732
|
}
|
|
35713
35733
|
return { plan, diagnostics };
|
|
35714
35734
|
}
|
|
35735
|
+
var PACKAGE_NAME = "specatlas";
|
|
35736
|
+
var REGISTRY = "https://registry.npmjs.org";
|
|
35737
|
+
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
35738
|
+
var TIMEOUT_MS = 2e3;
|
|
35739
|
+
function detectarInstalacion(rutaEjecutable2, plataforma = process.platform) {
|
|
35740
|
+
const ruta = toPosix(rutaEjecutable2).toLowerCase();
|
|
35741
|
+
if (/[/](_npx|\.npm[/]_npx)[/]/.test(ruta) || ruta.includes("/.pnpm-store/") || /[/]dlx-[^/]*[/]/.test(ruta)) {
|
|
35742
|
+
return {
|
|
35743
|
+
gestor: "npx",
|
|
35744
|
+
motivo: "la herramienta se est\xE1 ejecutando sin instalar: cada ejecuci\xF3n ya usa la versi\xF3n publicada"
|
|
35745
|
+
};
|
|
35746
|
+
}
|
|
35747
|
+
if (ruta.includes("/pnpm/global/") || ruta.includes("/.pnpm/global/") || /[/]pnpm[/][^/]*[/]global[/]/.test(ruta)) {
|
|
35748
|
+
return { gestor: "pnpm", comando: `pnpm add -g ${PACKAGE_NAME}@latest` };
|
|
35749
|
+
}
|
|
35750
|
+
if (ruta.includes("/.bun/")) {
|
|
35751
|
+
return { gestor: "bun", comando: `bun add -g ${PACKAGE_NAME}@latest` };
|
|
35752
|
+
}
|
|
35753
|
+
if (ruta.includes("/.yarn/") || ruta.includes("/yarn/global/")) {
|
|
35754
|
+
return { gestor: "yarn", comando: `yarn global add ${PACKAGE_NAME}@latest` };
|
|
35755
|
+
}
|
|
35756
|
+
if (ruta.includes("/npm/node_modules/") || ruta.includes("/roaming/npm/") || ruta.includes("/lib/node_modules/") || ruta.includes("/npm-global/")) {
|
|
35757
|
+
return { gestor: "npm", comando: `npm install -g ${PACKAGE_NAME}@latest` };
|
|
35758
|
+
}
|
|
35759
|
+
return { gestor: "desconocido", comando: plataforma === "win32" ? `npm install -g ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest` };
|
|
35760
|
+
}
|
|
35761
|
+
function compararVersiones(a, b) {
|
|
35762
|
+
const partes = (v) => v.trim().replace(/^v/, "").split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
35763
|
+
const ap = partes(a);
|
|
35764
|
+
const bp = partes(b);
|
|
35765
|
+
for (let i = 0; i < 3; i += 1) {
|
|
35766
|
+
const diff = (ap[i] ?? 0) - (bp[i] ?? 0);
|
|
35767
|
+
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
35768
|
+
}
|
|
35769
|
+
return 0;
|
|
35770
|
+
}
|
|
35771
|
+
function hayVersionNueva(instalada, publicada) {
|
|
35772
|
+
if (!publicada) return false;
|
|
35773
|
+
return compararVersiones(publicada, instalada) > 0;
|
|
35774
|
+
}
|
|
35775
|
+
var PREFERENCIAS_POR_DEFECTO = { comprobar: true, automatica: false };
|
|
35776
|
+
function directorioPersonal(home = os.homedir()) {
|
|
35777
|
+
return path25.join(home, ".specatlas");
|
|
35778
|
+
}
|
|
35779
|
+
function rutaPreferencias(home) {
|
|
35780
|
+
return path25.join(directorioPersonal(home), "config.json");
|
|
35781
|
+
}
|
|
35782
|
+
function rutaEstado(home) {
|
|
35783
|
+
return path25.join(directorioPersonal(home), "update-check.json");
|
|
35784
|
+
}
|
|
35785
|
+
async function leerPreferencias(home) {
|
|
35786
|
+
const raw = await readTextIfExists(rutaPreferencias(home));
|
|
35787
|
+
if (raw === void 0) return { ...PREFERENCIAS_POR_DEFECTO };
|
|
35788
|
+
try {
|
|
35789
|
+
const data = JSON.parse(raw);
|
|
35790
|
+
return {
|
|
35791
|
+
comprobar: data.update?.comprobar ?? PREFERENCIAS_POR_DEFECTO.comprobar,
|
|
35792
|
+
automatica: data.update?.automatica ?? PREFERENCIAS_POR_DEFECTO.automatica
|
|
35793
|
+
};
|
|
35794
|
+
} catch {
|
|
35795
|
+
return { ...PREFERENCIAS_POR_DEFECTO };
|
|
35796
|
+
}
|
|
35797
|
+
}
|
|
35798
|
+
async function escribirPreferencias(preferencias, home) {
|
|
35799
|
+
const file = rutaPreferencias(home);
|
|
35800
|
+
await ensureDir(path25.dirname(file));
|
|
35801
|
+
await writeText(file, `${JSON.stringify({ update: preferencias }, null, 2)}
|
|
35802
|
+
`);
|
|
35803
|
+
return file;
|
|
35804
|
+
}
|
|
35805
|
+
async function leerEstado(home) {
|
|
35806
|
+
const raw = await readTextIfExists(rutaEstado(home));
|
|
35807
|
+
if (raw === void 0) return {};
|
|
35808
|
+
try {
|
|
35809
|
+
return JSON.parse(raw);
|
|
35810
|
+
} catch {
|
|
35811
|
+
return {};
|
|
35812
|
+
}
|
|
35813
|
+
}
|
|
35814
|
+
async function escribirEstado(estado, home) {
|
|
35815
|
+
const file = rutaEstado(home);
|
|
35816
|
+
await ensureDir(path25.dirname(file));
|
|
35817
|
+
await writeText(file, `${JSON.stringify(estado, null, 2)}
|
|
35818
|
+
`);
|
|
35819
|
+
}
|
|
35820
|
+
function tocaConsultar(estado, ahora = /* @__PURE__ */ new Date(), ttlMs = TTL_MS) {
|
|
35821
|
+
if (!estado.ultimaConsulta) return true;
|
|
35822
|
+
const ultima = Date.parse(estado.ultimaConsulta);
|
|
35823
|
+
if (Number.isNaN(ultima)) return true;
|
|
35824
|
+
return ahora.getTime() - ultima >= ttlMs;
|
|
35825
|
+
}
|
|
35826
|
+
function esIntegracionContinua(env = process.env) {
|
|
35827
|
+
return Boolean(env["CI"] || env["CONTINUOUS_INTEGRATION"] || env["GITHUB_ACTIONS"] || env["GITLAB_CI"] || env["BUILDKITE"] || env["TEAMCITY_VERSION"]);
|
|
35828
|
+
}
|
|
35829
|
+
function comprobacionDesactivada(env = process.env) {
|
|
35830
|
+
const flag3 = env["SPECATLAS_NO_UPDATE_CHECK"];
|
|
35831
|
+
return flag3 === "1" || flag3 === "true";
|
|
35832
|
+
}
|
|
35833
|
+
async function consultarVersionPublicada(timeoutMs = TIMEOUT_MS, fetchImpl = fetch) {
|
|
35834
|
+
const control = new AbortController();
|
|
35835
|
+
const timer = setTimeout(() => control.abort(), timeoutMs);
|
|
35836
|
+
try {
|
|
35837
|
+
const respuesta = await fetchImpl(`${REGISTRY}/-/package/${PACKAGE_NAME}/dist-tags`, { signal: control.signal });
|
|
35838
|
+
if (!respuesta.ok) return void 0;
|
|
35839
|
+
const data = await respuesta.json();
|
|
35840
|
+
return typeof data.latest === "string" ? data.latest : void 0;
|
|
35841
|
+
} catch {
|
|
35842
|
+
return void 0;
|
|
35843
|
+
} finally {
|
|
35844
|
+
clearTimeout(timer);
|
|
35845
|
+
}
|
|
35846
|
+
}
|
|
35847
|
+
async function comprobarActualizacion(ctx) {
|
|
35848
|
+
const env = ctx.env ?? process.env;
|
|
35849
|
+
if (ctx.json || esIntegracionContinua(env) || comprobacionDesactivada(env)) return void 0;
|
|
35850
|
+
const preferencias = await leerPreferencias(ctx.home);
|
|
35851
|
+
if (!preferencias.comprobar) return void 0;
|
|
35852
|
+
const ahora = ctx.ahora ?? /* @__PURE__ */ new Date();
|
|
35853
|
+
const estado = await leerEstado(ctx.home);
|
|
35854
|
+
let publicada = estado.ultimaVersion;
|
|
35855
|
+
if (tocaConsultar(estado, ahora)) {
|
|
35856
|
+
publicada = await consultarVersionPublicada(TIMEOUT_MS, ctx.fetchImpl ?? fetch);
|
|
35857
|
+
if (publicada === void 0) return void 0;
|
|
35858
|
+
await escribirEstado({ ultimaConsulta: ahora.toISOString(), ultimaVersion: publicada }, ctx.home);
|
|
35859
|
+
}
|
|
35860
|
+
if (!hayVersionNueva(ctx.instalada, publicada)) return void 0;
|
|
35861
|
+
const instalacion = detectarInstalacion(ctx.rutaEjecutable);
|
|
35862
|
+
return {
|
|
35863
|
+
instalada: ctx.instalada,
|
|
35864
|
+
publicada,
|
|
35865
|
+
comando: instalacion.comando ?? `npm install -g ${PACKAGE_NAME}@latest`,
|
|
35866
|
+
gestor: instalacion.gestor
|
|
35867
|
+
};
|
|
35868
|
+
}
|
|
35715
35869
|
var DOCS_MARKER_START = "<!-- specatlas:generado:inicio -->";
|
|
35716
35870
|
var DOCS_MARKER_END = "<!-- specatlas:generado:fin -->";
|
|
35717
35871
|
var ES_WORDING = {
|
|
@@ -36197,7 +36351,7 @@ ${notes}
|
|
|
36197
36351
|
${existing}`;
|
|
36198
36352
|
}
|
|
36199
36353
|
async function generateDocs(opts) {
|
|
36200
|
-
const root =
|
|
36354
|
+
const root = path26.resolve(opts.root);
|
|
36201
36355
|
const { config } = await loadWorkspace(root);
|
|
36202
36356
|
const change = await loadChange(root, opts.slug);
|
|
36203
36357
|
if (!change.meta) {
|
|
@@ -36213,10 +36367,10 @@ async function generateDocs(opts) {
|
|
|
36213
36367
|
const tipos = tipo === "all" ? ["tecnica", "manual"] : [tipo];
|
|
36214
36368
|
const templates = templatesFor(language);
|
|
36215
36369
|
const [proposalRaw, planRaw, glossaryRaw, analyzeRaw, mockupManifest] = await Promise.all([
|
|
36216
|
-
readTextIfExists(
|
|
36217
|
-
readTextIfExists(
|
|
36218
|
-
readTextIfExists(
|
|
36219
|
-
readTextIfExists(
|
|
36370
|
+
readTextIfExists(path26.join(change.dir, "proposal.md")),
|
|
36371
|
+
readTextIfExists(path26.join(change.dir, "plan.md")),
|
|
36372
|
+
readTextIfExists(path26.join(root, ".sdd", "glossary.md")),
|
|
36373
|
+
readTextIfExists(path26.join(change.dir, "analyze.md")),
|
|
36220
36374
|
readMockupManifest(root, change.slug)
|
|
36221
36375
|
]);
|
|
36222
36376
|
const mockups = (mockupManifest.manifest?.screens ?? []).map((screen) => ({
|
|
@@ -36230,7 +36384,7 @@ async function generateDocs(opts) {
|
|
|
36230
36384
|
const files = [];
|
|
36231
36385
|
const diagnostics = [];
|
|
36232
36386
|
for (const current of tipos) {
|
|
36233
|
-
const file =
|
|
36387
|
+
const file = path26.join(change.dir, "docs", `${current}.md`);
|
|
36234
36388
|
const block = renderTemplate(current === "tecnica" ? templates.docTecnica : templates.docManual, data);
|
|
36235
36389
|
const existing = await readTextIfExists(file);
|
|
36236
36390
|
const content = mergeManaged(existing, block, language);
|
|
@@ -36239,7 +36393,7 @@ async function generateDocs(opts) {
|
|
|
36239
36393
|
const label = current === "tecnica" ? "t\xE9cnico" : "manual";
|
|
36240
36394
|
const title = `${current === "tecnica" ? language === "en" ? "Technical document" : "Documento t\xE9cnico" : language === "en" ? "User manual" : "Manual de usuario"} \u2014 ${change.meta.title ?? change.slug}`;
|
|
36241
36395
|
try {
|
|
36242
|
-
const htmlPath =
|
|
36396
|
+
const htmlPath = path26.join(change.dir, "docs", `${current}.html`);
|
|
36243
36397
|
await writeText(htmlPath, renderDocument(content, { theme: "auto", title }));
|
|
36244
36398
|
doc.htmlPath = htmlPath;
|
|
36245
36399
|
} catch (error2) {
|
|
@@ -36251,7 +36405,7 @@ async function generateDocs(opts) {
|
|
|
36251
36405
|
);
|
|
36252
36406
|
}
|
|
36253
36407
|
try {
|
|
36254
|
-
const pdfPath =
|
|
36408
|
+
const pdfPath = path26.join(change.dir, "docs", `${current}.pdf`);
|
|
36255
36409
|
const pdf = await renderPdf(content, { title, project: config.project.name, generatedAt: localStamp(ahora) });
|
|
36256
36410
|
await writeBytes(pdfPath, pdf.bytes);
|
|
36257
36411
|
doc.pdfPath = pdfPath;
|
|
@@ -36304,7 +36458,7 @@ function resultOf(diagnostic, root) {
|
|
|
36304
36458
|
message: { text: diagnostic.message }
|
|
36305
36459
|
};
|
|
36306
36460
|
if (diagnostic.path) {
|
|
36307
|
-
const rel =
|
|
36461
|
+
const rel = path27.relative(root, diagnostic.path);
|
|
36308
36462
|
if (rel !== "") {
|
|
36309
36463
|
const physicalLocation = {
|
|
36310
36464
|
artifactLocation: { uri: toPosix(rel) },
|
|
@@ -36359,7 +36513,7 @@ async function runDoctor(root) {
|
|
|
36359
36513
|
const approvals = await loadApprovals(workspace.sddDir);
|
|
36360
36514
|
findings.push(...approvals.diagnostics);
|
|
36361
36515
|
for (const change of workspace.changes) {
|
|
36362
|
-
const deltaPath =
|
|
36516
|
+
const deltaPath = path28.join(change.dir, "spec.md");
|
|
36363
36517
|
const deltaContent = await readTextIfExists(deltaPath);
|
|
36364
36518
|
const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
|
|
36365
36519
|
if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
|
|
@@ -36381,7 +36535,7 @@ async function runDoctor(root) {
|
|
|
36381
36535
|
}
|
|
36382
36536
|
for (const override of change.meta?.overrides ?? []) {
|
|
36383
36537
|
if (!override.reason.trim() || !override.by.trim()) {
|
|
36384
|
-
findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path:
|
|
36538
|
+
findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path28.join(change.dir, "meta.yaml") }));
|
|
36385
36539
|
}
|
|
36386
36540
|
}
|
|
36387
36541
|
}
|
|
@@ -36413,7 +36567,7 @@ function livingRequirementsMap(specs) {
|
|
|
36413
36567
|
return map;
|
|
36414
36568
|
}
|
|
36415
36569
|
async function runCiGate(opts) {
|
|
36416
|
-
const root =
|
|
36570
|
+
const root = path29.resolve(opts.root);
|
|
36417
36571
|
const { workspace, config } = await loadWorkspace(root);
|
|
36418
36572
|
const diagnostics = [];
|
|
36419
36573
|
const checks = [];
|
|
@@ -36424,7 +36578,7 @@ async function runCiGate(opts) {
|
|
|
36424
36578
|
let changesErrors = 0;
|
|
36425
36579
|
let changesWarnings = 0;
|
|
36426
36580
|
for (const change of workspace.changes) {
|
|
36427
|
-
const lintFindings = change.delta ? lintDelta(change.delta, living,
|
|
36581
|
+
const lintFindings = change.delta ? lintDelta(change.delta, living, path29.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
|
|
36428
36582
|
const trace = checkTrace({
|
|
36429
36583
|
specs: workspace.specs,
|
|
36430
36584
|
change,
|
|
@@ -36565,6 +36719,7 @@ var CATALOG = [
|
|
|
36565
36719
|
{ name: "run", description: "Persiste runs y eventos de ejecuci\xF3n (auditor\xEDa y reanudaci\xF3n)", flags: ["phase", "inputs", "data", "slug", "status", "json"], usage: "satlas run start|event|status|show|list" },
|
|
36566
36720
|
{ name: "hash", description: "Calcula el sha256 de un archivo o texto (para evidencia)", flags: ["text", "json"], usage: 'satlas hash <archivo> | satlas hash --text "salida"' },
|
|
36567
36721
|
{ name: "explain", description: "Explica un c\xF3digo de diagn\xF3stico: qu\xE9 significa, por qu\xE9 importa y c\xF3mo se cierra", flags: ["json"], usage: "satlas explain [<c\xF3digo>]" },
|
|
36722
|
+
{ name: "self-update", description: "Actualiza la herramienta a la versi\xF3n publicada (o solo comprueba si la hay)", flags: ["check", "auto", "json"], usage: "satlas self-update [--check] [--auto on|off]" },
|
|
36568
36723
|
{ name: "version", description: "Muestra la versi\xF3n", flags: ["json"], usage: "satlas version" },
|
|
36569
36724
|
{ name: "help", description: "Muestra la ayuda", flags: ["json"], usage: "satlas help [comando]" }
|
|
36570
36725
|
];
|
|
@@ -36682,26 +36837,26 @@ function cliVersion() {
|
|
|
36682
36837
|
}
|
|
36683
36838
|
|
|
36684
36839
|
// src/commands/adapters.ts
|
|
36685
|
-
import
|
|
36840
|
+
import path32 from "path";
|
|
36686
36841
|
|
|
36687
36842
|
// ../adapters/dist/index.js
|
|
36688
|
-
import
|
|
36843
|
+
import path30 from "path";
|
|
36689
36844
|
import { stringify as stringifyYaml9 } from "yaml";
|
|
36690
36845
|
import path210 from "path";
|
|
36691
36846
|
async function loadWorkflow(workflowDir) {
|
|
36692
|
-
const phasesDir =
|
|
36693
|
-
const snippetsDir =
|
|
36847
|
+
const phasesDir = path30.join(workflowDir, "phases");
|
|
36848
|
+
const snippetsDir = path30.join(workflowDir, "snippets");
|
|
36694
36849
|
const phases = [];
|
|
36695
36850
|
const snippets = /* @__PURE__ */ new Map();
|
|
36696
36851
|
const hashParts = [];
|
|
36697
36852
|
for (const entry of (await listDir(phasesDir)).sort()) {
|
|
36698
36853
|
if (!entry.endsWith(".md")) continue;
|
|
36699
|
-
const filePath =
|
|
36854
|
+
const filePath = path30.join(phasesDir, entry);
|
|
36700
36855
|
const raw = await readText(filePath);
|
|
36701
36856
|
hashParts.push(raw);
|
|
36702
36857
|
const fm = parseFrontmatter(raw, filePath);
|
|
36703
36858
|
const data = fm.data;
|
|
36704
|
-
const id = typeof data["id"] === "string" ? data["id"] :
|
|
36859
|
+
const id = typeof data["id"] === "string" ? data["id"] : path30.basename(entry, ".md");
|
|
36705
36860
|
phases.push({
|
|
36706
36861
|
id,
|
|
36707
36862
|
title: typeof data["title"] === "string" ? data["title"] : id,
|
|
@@ -36715,9 +36870,9 @@ async function loadWorkflow(workflowDir) {
|
|
|
36715
36870
|
}
|
|
36716
36871
|
for (const entry of (await listDir(snippetsDir)).sort()) {
|
|
36717
36872
|
if (!entry.endsWith(".md")) continue;
|
|
36718
|
-
const raw = await readTextIfExists(
|
|
36873
|
+
const raw = await readTextIfExists(path30.join(snippetsDir, entry)) ?? "";
|
|
36719
36874
|
hashParts.push(raw);
|
|
36720
|
-
snippets.set(
|
|
36875
|
+
snippets.set(path30.basename(entry, ".md"), raw.trim());
|
|
36721
36876
|
}
|
|
36722
36877
|
return { phases: phases.sort((a, b) => a.id.localeCompare(b.id)), snippets, sourceHash: sha256(hashParts.join("\n---\n")) };
|
|
36723
36878
|
}
|
|
@@ -37032,11 +37187,11 @@ async function checkAdapters(opts) {
|
|
|
37032
37187
|
}
|
|
37033
37188
|
|
|
37034
37189
|
// src/paths.ts
|
|
37035
|
-
import
|
|
37190
|
+
import path31 from "path";
|
|
37036
37191
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
37037
37192
|
async function isSourceRepoRoot(dir) {
|
|
37038
|
-
if (!await exists(
|
|
37039
|
-
const raw = await readTextIfExists(
|
|
37193
|
+
if (!await exists(path31.join(dir, "pnpm-workspace.yaml"))) return false;
|
|
37194
|
+
const raw = await readTextIfExists(path31.join(dir, "packages", "cli", "package.json"));
|
|
37040
37195
|
if (raw === void 0) return false;
|
|
37041
37196
|
try {
|
|
37042
37197
|
return JSON.parse(raw).name === "specatlas";
|
|
@@ -37048,17 +37203,17 @@ async function sourceRepoRoot(startDir) {
|
|
|
37048
37203
|
let dir = startDir;
|
|
37049
37204
|
for (let i = 0; i < 5; i += 1) {
|
|
37050
37205
|
if (await isSourceRepoRoot(dir)) return dir;
|
|
37051
|
-
const parent =
|
|
37206
|
+
const parent = path31.dirname(dir);
|
|
37052
37207
|
if (parent === dir) break;
|
|
37053
37208
|
dir = parent;
|
|
37054
37209
|
}
|
|
37055
37210
|
return void 0;
|
|
37056
37211
|
}
|
|
37057
37212
|
async function resolveFromSource(relative) {
|
|
37058
|
-
const start =
|
|
37213
|
+
const start = path31.dirname(fileURLToPath2(import.meta.url));
|
|
37059
37214
|
const root = await sourceRepoRoot(start);
|
|
37060
37215
|
if (!root) return void 0;
|
|
37061
|
-
const candidate =
|
|
37216
|
+
const candidate = path31.join(root, relative);
|
|
37062
37217
|
return await exists(candidate) ? candidate : void 0;
|
|
37063
37218
|
}
|
|
37064
37219
|
async function resolveProfilesDir() {
|
|
@@ -37069,16 +37224,16 @@ async function resolveProfilesDir() {
|
|
|
37069
37224
|
async function resolveWorkflowDir() {
|
|
37070
37225
|
const env = process.env["SPECATLAS_WORKFLOW_DIR"];
|
|
37071
37226
|
if (env && await exists(env)) return env;
|
|
37072
|
-
return await resolveFromSource("workflow") ?? walkUpFor(
|
|
37227
|
+
return await resolveFromSource("workflow") ?? walkUpFor(path31.join("workflow", "phases"));
|
|
37073
37228
|
}
|
|
37074
37229
|
async function walkUpFor(relative) {
|
|
37075
|
-
let dir =
|
|
37230
|
+
let dir = path31.dirname(fileURLToPath2(import.meta.url));
|
|
37076
37231
|
for (let i = 0; i < 8; i += 1) {
|
|
37077
|
-
const candidate =
|
|
37232
|
+
const candidate = path31.join(dir, relative);
|
|
37078
37233
|
if (await exists(candidate)) {
|
|
37079
|
-
return relative.includes(
|
|
37234
|
+
return relative.includes(path31.sep) ? path31.dirname(candidate) : candidate;
|
|
37080
37235
|
}
|
|
37081
|
-
const parent =
|
|
37236
|
+
const parent = path31.dirname(dir);
|
|
37082
37237
|
if (parent === dir) break;
|
|
37083
37238
|
dir = parent;
|
|
37084
37239
|
}
|
|
@@ -37156,7 +37311,7 @@ async function runAdapters(ctx) {
|
|
|
37156
37311
|
for (const [target, count2] of byTarget) lines.push(` ${target}: ${count2} archivo(s)`);
|
|
37157
37312
|
if (report.written.length > 0) {
|
|
37158
37313
|
lines.push("");
|
|
37159
|
-
for (const p of report.written) lines.push(` + ${
|
|
37314
|
+
for (const p of report.written) lines.push(` + ${path32.relative(ctx.cwd, path32.join(root, p))}`);
|
|
37160
37315
|
} else {
|
|
37161
37316
|
lines.push("");
|
|
37162
37317
|
lines.push("Sin cambios: los adaptadores ya estaban al d\xEDa.");
|
|
@@ -37167,7 +37322,7 @@ async function runAdapters(ctx) {
|
|
|
37167
37322
|
}
|
|
37168
37323
|
|
|
37169
37324
|
// src/commands/adopt.ts
|
|
37170
|
-
import
|
|
37325
|
+
import path33 from "path";
|
|
37171
37326
|
async function runAdopt(ctx) {
|
|
37172
37327
|
const { root } = await requireWorkspace(ctx);
|
|
37173
37328
|
const domainsFlag = flagString(ctx.flags, "domains") ?? flagString(ctx.flags, "domain");
|
|
@@ -37187,11 +37342,11 @@ async function runAdopt(ctx) {
|
|
|
37187
37342
|
}
|
|
37188
37343
|
if (result.createdSpecs.length > 0) {
|
|
37189
37344
|
lines.push("");
|
|
37190
|
-
for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${
|
|
37345
|
+
for (const spec of result.createdSpecs) lines.push(` ${result.dryRun ? "[dry-run] " : "+ "}${path33.relative(ctx.cwd, spec)}`);
|
|
37191
37346
|
}
|
|
37192
37347
|
if (result.reportPath && !result.dryRun) {
|
|
37193
37348
|
lines.push("");
|
|
37194
|
-
lines.push(` Informe: ${
|
|
37349
|
+
lines.push(` Informe: ${path33.relative(ctx.cwd, result.reportPath)}`);
|
|
37195
37350
|
}
|
|
37196
37351
|
lines.push("");
|
|
37197
37352
|
lines.push("Siguiente: fase /satlas-adopt por dominio (requisitos AS-IS) y luego:");
|
|
@@ -37204,8 +37359,8 @@ async function runAdopt(ctx) {
|
|
|
37204
37359
|
diagnostics: result.diagnostics,
|
|
37205
37360
|
data: {
|
|
37206
37361
|
domains: result.domains.map((d) => ({ name: d.name, files: d.files, existingSpec: d.existingSpec })),
|
|
37207
|
-
createdSpecs: result.createdSpecs.map((s) =>
|
|
37208
|
-
reportPath: result.reportPath ?
|
|
37362
|
+
createdSpecs: result.createdSpecs.map((s) => path33.relative(ctx.cwd, s)),
|
|
37363
|
+
reportPath: result.reportPath ? path33.relative(ctx.cwd, result.reportPath) : void 0,
|
|
37209
37364
|
stack: result.stack,
|
|
37210
37365
|
dryRun: result.dryRun
|
|
37211
37366
|
},
|
|
@@ -37254,7 +37409,7 @@ async function runAmend(ctx) {
|
|
|
37254
37409
|
}
|
|
37255
37410
|
|
|
37256
37411
|
// src/commands/analyze.ts
|
|
37257
|
-
import
|
|
37412
|
+
import path34 from "path";
|
|
37258
37413
|
async function runAnalyzeCommand(ctx) {
|
|
37259
37414
|
const { root } = await requireWorkspace(ctx);
|
|
37260
37415
|
const slug = ctx.positionals[0];
|
|
@@ -37265,23 +37420,23 @@ async function runAnalyzeCommand(ctx) {
|
|
|
37265
37420
|
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`];
|
|
37266
37421
|
if (result.waves) lines.push(` olas: ${result.waves.blocks} bloque(s), ${result.waves.waves} ola(s), ${result.waves.tasks} tarea(s)`);
|
|
37267
37422
|
if (result.mockups) lines.push(` mockups: ${result.mockups.screens} pantalla(s)${result.mockups.stale ? " (desactualizados)" : ""}`);
|
|
37268
|
-
if (result.path) lines.push("", ` informe: ${
|
|
37423
|
+
if (result.path) lines.push("", ` informe: ${path34.relative(ctx.cwd, result.path)}`);
|
|
37269
37424
|
for (const finding of result.findings) {
|
|
37270
37425
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}`);
|
|
37271
37426
|
}
|
|
37272
37427
|
return {
|
|
37273
37428
|
exitCode: result.status === "blocked" ? 1 : 0,
|
|
37274
37429
|
diagnostics: result.findings,
|
|
37275
|
-
data: result.path ? { ...result, path:
|
|
37430
|
+
data: result.path ? { ...result, path: path34.relative(ctx.cwd, result.path) } : result,
|
|
37276
37431
|
text: lines
|
|
37277
37432
|
};
|
|
37278
37433
|
}
|
|
37279
37434
|
|
|
37280
37435
|
// src/commands/approve.ts
|
|
37281
|
-
import
|
|
37436
|
+
import path36 from "path";
|
|
37282
37437
|
|
|
37283
37438
|
// src/commands/issue.ts
|
|
37284
|
-
import
|
|
37439
|
+
import path35 from "path";
|
|
37285
37440
|
async function runIssue(ctx) {
|
|
37286
37441
|
const action = ctx.positionals[0] ?? "status";
|
|
37287
37442
|
const slug = ctx.positionals[1];
|
|
@@ -37309,7 +37464,7 @@ async function runIssue(ctx) {
|
|
|
37309
37464
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-ISSUE-000", severity: "error", message: "Falta el slug: satlas issue status <slug>" }] };
|
|
37310
37465
|
}
|
|
37311
37466
|
const change = await loadChange(root, slug);
|
|
37312
|
-
const { config } = await loadConfig(
|
|
37467
|
+
const { config } = await loadConfig(path35.join(root, ".sdd"));
|
|
37313
37468
|
const tracker = change.meta?.tracker;
|
|
37314
37469
|
const lines = [`Issue de ${slug}`, ""];
|
|
37315
37470
|
if (!tracker || tracker.provider !== "github") {
|
|
@@ -37380,7 +37535,7 @@ async function runApprove(ctx) {
|
|
|
37380
37535
|
};
|
|
37381
37536
|
}
|
|
37382
37537
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
37383
|
-
const artifact = target.includes("/") || target.includes("\\") ? target :
|
|
37538
|
+
const artifact = target.includes("/") || target.includes("\\") ? target : path36.join("changes", target, "spec.md");
|
|
37384
37539
|
const channelFlag = flagString(ctx.flags, "channel");
|
|
37385
37540
|
const channel = channelFlag === "presentation" || channelFlag === "editor" || channelFlag === "pr" || channelFlag === "tracker" ? channelFlag : "cli";
|
|
37386
37541
|
const change = workspace.changes.find(
|
|
@@ -37416,18 +37571,30 @@ async function runApprove(ctx) {
|
|
|
37416
37571
|
lines.push(` fecha: ${result.approval.approvedAt}`);
|
|
37417
37572
|
lines.push("");
|
|
37418
37573
|
lines.push("La firma desbloquea el plan. Si el artefacto cambia, la firma queda obsoleta autom\xE1ticamente.");
|
|
37574
|
+
const slug = change?.slug;
|
|
37575
|
+
if (slug && !flagBool(ctx.flags, "dry-run")) {
|
|
37576
|
+
const presentation = path36.join(root, ".sdd", "changes", slug, "presentation", "index.html");
|
|
37577
|
+
if (await exists(presentation)) {
|
|
37578
|
+
try {
|
|
37579
|
+
await generatePresentation({ root, slug });
|
|
37580
|
+
lines.push(`Propuesta actualizada con la firma: ${path36.relative(ctx.cwd, presentation)}`);
|
|
37581
|
+
} catch (error2) {
|
|
37582
|
+
lines.push(`La propuesta no se pudo rehacer (${error2.message}); vuelve a generarla con \`satlas present ${slug}\`.`);
|
|
37583
|
+
}
|
|
37584
|
+
}
|
|
37585
|
+
}
|
|
37419
37586
|
}
|
|
37420
37587
|
const hasErrors2 = result.diagnostics.some((d) => d.severity === "error");
|
|
37421
37588
|
return {
|
|
37422
37589
|
exitCode: hasErrors2 ? 1 : 0,
|
|
37423
37590
|
diagnostics: result.diagnostics,
|
|
37424
|
-
data: result.approval ? { ...result.approval, file:
|
|
37591
|
+
data: result.approval ? { ...result.approval, file: path36.relative(ctx.cwd, result.file) } : void 0,
|
|
37425
37592
|
text: lines
|
|
37426
37593
|
};
|
|
37427
37594
|
}
|
|
37428
37595
|
|
|
37429
37596
|
// src/commands/archive.ts
|
|
37430
|
-
import
|
|
37597
|
+
import path37 from "path";
|
|
37431
37598
|
async function runArchive(ctx) {
|
|
37432
37599
|
const slug = ctx.positionals[0];
|
|
37433
37600
|
if (!slug) {
|
|
@@ -37464,7 +37631,7 @@ async function runArchive(ctx) {
|
|
|
37464
37631
|
} else {
|
|
37465
37632
|
lines.push("El delta no contiene operaciones (ADDED/MODIFIED/REMOVED/RENAMED).");
|
|
37466
37633
|
}
|
|
37467
|
-
if (result.archivedTo) lines.push(` archivado en: ${
|
|
37634
|
+
if (result.archivedTo) lines.push(` archivado en: ${path37.relative(ctx.cwd, result.archivedTo)}`);
|
|
37468
37635
|
const hasErrors2 = result.diagnostics.some((d) => d.severity === "error");
|
|
37469
37636
|
return {
|
|
37470
37637
|
exitCode: hasErrors2 ? 1 : 0,
|
|
@@ -37475,7 +37642,7 @@ async function runArchive(ctx) {
|
|
|
37475
37642
|
}
|
|
37476
37643
|
|
|
37477
37644
|
// src/commands/ci.ts
|
|
37478
|
-
import
|
|
37645
|
+
import path38 from "path";
|
|
37479
37646
|
async function runCi(ctx) {
|
|
37480
37647
|
const { root, config } = await requireWorkspace(ctx);
|
|
37481
37648
|
const strict = flagBool(ctx.flags, "strict");
|
|
@@ -37501,10 +37668,10 @@ async function runCi(ctx) {
|
|
|
37501
37668
|
const lines = ["CI de SpecAtlas", ""];
|
|
37502
37669
|
let sarifWritten;
|
|
37503
37670
|
if (sarifPath) {
|
|
37504
|
-
const target =
|
|
37671
|
+
const target = path38.resolve(ctx.cwd, sarifPath);
|
|
37505
37672
|
try {
|
|
37506
37673
|
await writeText(target, toSarifText({ diagnostics: gate.diagnostics, root, version: cliVersion(), failed: gate.failed }));
|
|
37507
|
-
sarifWritten =
|
|
37674
|
+
sarifWritten = path38.relative(ctx.cwd, target);
|
|
37508
37675
|
} catch (err) {
|
|
37509
37676
|
diagnostics.push({
|
|
37510
37677
|
code: "ATLAS-CI-SARIF-001",
|
|
@@ -37581,7 +37748,7 @@ async function runClarify(ctx) {
|
|
|
37581
37748
|
}
|
|
37582
37749
|
|
|
37583
37750
|
// src/commands/contracts.ts
|
|
37584
|
-
import
|
|
37751
|
+
import path39 from "path";
|
|
37585
37752
|
async function runContracts(ctx) {
|
|
37586
37753
|
const slug = ctx.positionals[0];
|
|
37587
37754
|
if (!slug) {
|
|
@@ -37601,7 +37768,7 @@ async function runContracts(ctx) {
|
|
|
37601
37768
|
const coverage = contractCoverage(change, config.gates.contracts.mode);
|
|
37602
37769
|
const findings = [...state.findings, ...coverage];
|
|
37603
37770
|
for (const file of state.files) {
|
|
37604
|
-
lines.push(` ${
|
|
37771
|
+
lines.push(` ${path39.relative(ctx.cwd, file.path)} (${file.format}) \u2014 ${file.operations.length} operaci\xF3n(es)`);
|
|
37605
37772
|
for (const operation of file.operations) lines.push(` ${operation.id}`);
|
|
37606
37773
|
}
|
|
37607
37774
|
lines.push("");
|
|
@@ -37617,7 +37784,7 @@ async function runContracts(ctx) {
|
|
|
37617
37784
|
data: {
|
|
37618
37785
|
slug,
|
|
37619
37786
|
mode: config.gates.contracts.mode,
|
|
37620
|
-
contracts: state.files.map((file) => ({ path:
|
|
37787
|
+
contracts: state.files.map((file) => ({ path: path39.relative(ctx.cwd, file.path), format: file.format, operations: file.operations.map((operation) => operation.id) })),
|
|
37621
37788
|
findings
|
|
37622
37789
|
},
|
|
37623
37790
|
text: lines
|
|
@@ -37625,7 +37792,7 @@ async function runContracts(ctx) {
|
|
|
37625
37792
|
}
|
|
37626
37793
|
|
|
37627
37794
|
// src/commands/docs.ts
|
|
37628
|
-
import
|
|
37795
|
+
import path40 from "path";
|
|
37629
37796
|
async function runDocs(ctx) {
|
|
37630
37797
|
const slug = ctx.positionals[0];
|
|
37631
37798
|
if (!slug) {
|
|
@@ -37639,9 +37806,9 @@ async function runDocs(ctx) {
|
|
|
37639
37806
|
const warnings = result.diagnostics.filter((d) => d.severity === "warning");
|
|
37640
37807
|
const lines = [msg("docs.title", ctx.language), ""];
|
|
37641
37808
|
for (const file of result.files) {
|
|
37642
|
-
lines.push(` ${file.created ? msg("docs.created", ctx.language) : msg("docs.updated", ctx.language)}: ${
|
|
37643
|
-
if (file.htmlPath) lines.push(` html: ${
|
|
37644
|
-
if (file.pdfPath) lines.push(` pdf: ${
|
|
37809
|
+
lines.push(` ${file.created ? msg("docs.created", ctx.language) : msg("docs.updated", ctx.language)}: ${path40.relative(ctx.cwd, file.path)}`);
|
|
37810
|
+
if (file.htmlPath) lines.push(` html: ${path40.relative(ctx.cwd, file.htmlPath)}`);
|
|
37811
|
+
if (file.pdfPath) lines.push(` pdf: ${path40.relative(ctx.cwd, file.pdfPath)}`);
|
|
37645
37812
|
}
|
|
37646
37813
|
for (const warning of warnings) lines.push(` AVISO ${warning.code} \u2014 ${warning.message}`);
|
|
37647
37814
|
for (const error2 of errors) lines.push(` ERROR ${error2.code} \u2014 ${error2.message}`);
|
|
@@ -37653,10 +37820,10 @@ async function runDocs(ctx) {
|
|
|
37653
37820
|
tipo,
|
|
37654
37821
|
files: result.files.map((file) => ({
|
|
37655
37822
|
tipo: file.tipo,
|
|
37656
|
-
path:
|
|
37823
|
+
path: path40.relative(ctx.cwd, file.path),
|
|
37657
37824
|
created: file.created,
|
|
37658
|
-
html: file.htmlPath ?
|
|
37659
|
-
pdf: file.pdfPath ?
|
|
37825
|
+
html: file.htmlPath ? path40.relative(ctx.cwd, file.htmlPath) : void 0,
|
|
37826
|
+
pdf: file.pdfPath ? path40.relative(ctx.cwd, file.pdfPath) : void 0
|
|
37660
37827
|
}))
|
|
37661
37828
|
},
|
|
37662
37829
|
text: lines
|
|
@@ -37664,7 +37831,7 @@ async function runDocs(ctx) {
|
|
|
37664
37831
|
}
|
|
37665
37832
|
|
|
37666
37833
|
// src/commands/drift.ts
|
|
37667
|
-
import
|
|
37834
|
+
import path41 from "path";
|
|
37668
37835
|
async function runDrift(ctx) {
|
|
37669
37836
|
const { root, config } = await requireWorkspace(ctx);
|
|
37670
37837
|
const anchors = await loadAllAnchors(root);
|
|
@@ -37708,7 +37875,7 @@ async function runDrift(ctx) {
|
|
|
37708
37875
|
mode: report.mode,
|
|
37709
37876
|
domains: report.domains,
|
|
37710
37877
|
checked: report.checked,
|
|
37711
|
-
drifted: report.drifted.map((item) => ({ ...item, specPath:
|
|
37878
|
+
drifted: report.drifted.map((item) => ({ ...item, specPath: path41.relative(ctx.cwd, item.specPath) }))
|
|
37712
37879
|
},
|
|
37713
37880
|
text: lines
|
|
37714
37881
|
};
|
|
@@ -37759,7 +37926,7 @@ async function runImpact(ctx) {
|
|
|
37759
37926
|
}
|
|
37760
37927
|
|
|
37761
37928
|
// src/commands/link.ts
|
|
37762
|
-
import
|
|
37929
|
+
import path42 from "path";
|
|
37763
37930
|
async function runLink(ctx) {
|
|
37764
37931
|
const { root } = await requireWorkspace(ctx);
|
|
37765
37932
|
const action = ctx.positionals[0] ?? "list";
|
|
@@ -37771,7 +37938,7 @@ async function runLink(ctx) {
|
|
|
37771
37938
|
} else {
|
|
37772
37939
|
for (const entry of state.entries) {
|
|
37773
37940
|
const status = entry.available ? `${entry.requirements} requisito(s) \xB7 ${entry.domains.join(", ") || "sin dominios"}` : "NO DISPONIBLE";
|
|
37774
|
-
lines.push(` ${entry.name} ${
|
|
37941
|
+
lines.push(` ${entry.name} ${path42.relative(ctx.cwd, entry.path)} ${status}`);
|
|
37775
37942
|
}
|
|
37776
37943
|
}
|
|
37777
37944
|
return {
|
|
@@ -37780,7 +37947,7 @@ async function runLink(ctx) {
|
|
|
37780
37947
|
data: {
|
|
37781
37948
|
links: state.entries.map((entry) => ({
|
|
37782
37949
|
name: entry.name,
|
|
37783
|
-
path:
|
|
37950
|
+
path: path42.relative(ctx.cwd, entry.path),
|
|
37784
37951
|
available: entry.available,
|
|
37785
37952
|
requirements: entry.requirements,
|
|
37786
37953
|
domains: entry.domains
|
|
@@ -37825,12 +37992,12 @@ async function runLink(ctx) {
|
|
|
37825
37992
|
}
|
|
37826
37993
|
|
|
37827
37994
|
// src/commands/profile.ts
|
|
37828
|
-
import
|
|
37995
|
+
import path43 from "path";
|
|
37829
37996
|
async function runProfile(ctx) {
|
|
37830
37997
|
const action = ctx.positionals[0] ?? "detect";
|
|
37831
37998
|
const { root, workspace } = await requireWorkspace(ctx);
|
|
37832
37999
|
const officialDir = await resolveProfilesDir();
|
|
37833
|
-
const customDir =
|
|
38000
|
+
const customDir = path43.join(workspace.sddDir, "profiles", "custom");
|
|
37834
38001
|
const profiles = [...officialDir ? await loadProfilesFromDir(officialDir) : [], ...await loadProfilesFromDir(customDir)];
|
|
37835
38002
|
if (action === "detect") {
|
|
37836
38003
|
const result = await detectProfiles(root, profiles);
|
|
@@ -37859,7 +38026,7 @@ async function runProfile(ctx) {
|
|
|
37859
38026
|
if (!name) {
|
|
37860
38027
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-001", severity: "error", message: "Falta el nombre del perfil: satlas profile create <nombre>" }] };
|
|
37861
38028
|
}
|
|
37862
|
-
const file =
|
|
38029
|
+
const file = path43.join(customDir, `${name}.yaml`);
|
|
37863
38030
|
if (await exists(file)) {
|
|
37864
38031
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-002", severity: "error", message: `El perfil ya existe: ${file}` }] };
|
|
37865
38032
|
}
|
|
@@ -37868,7 +38035,7 @@ async function runProfile(ctx) {
|
|
|
37868
38035
|
exitCode: 0,
|
|
37869
38036
|
diagnostics: [],
|
|
37870
38037
|
data: { file },
|
|
37871
|
-
text: [`Perfil creado: ${
|
|
38038
|
+
text: [`Perfil creado: ${path43.relative(ctx.cwd, file)}`, "", "Ed\xEDtalo con los comandos reales de tu stack (build/lint/test) y tus anti-patrones."]
|
|
37872
38039
|
};
|
|
37873
38040
|
}
|
|
37874
38041
|
return {
|
|
@@ -37877,7 +38044,7 @@ async function runProfile(ctx) {
|
|
|
37877
38044
|
};
|
|
37878
38045
|
}
|
|
37879
38046
|
async function isInside(dir, name) {
|
|
37880
|
-
return exists(
|
|
38047
|
+
return exists(path43.join(dir, `${name}.yaml`));
|
|
37881
38048
|
}
|
|
37882
38049
|
function profileTemplate(name) {
|
|
37883
38050
|
return `name: ${name}
|
|
@@ -37905,7 +38072,7 @@ assumptions: []
|
|
|
37905
38072
|
}
|
|
37906
38073
|
|
|
37907
38074
|
// src/commands/doctor.ts
|
|
37908
|
-
import
|
|
38075
|
+
import path44 from "path";
|
|
37909
38076
|
async function runDoctorCommand(ctx) {
|
|
37910
38077
|
const { root, config } = await requireWorkspace(ctx);
|
|
37911
38078
|
const report = await runDoctor(root);
|
|
@@ -37930,7 +38097,7 @@ async function runDoctorCommand(ctx) {
|
|
|
37930
38097
|
lines.push("Sin hallazgos. El workspace est\xE1 sano.");
|
|
37931
38098
|
} else {
|
|
37932
38099
|
for (const finding of report.findings) {
|
|
37933
|
-
const location = finding.path ? ` ${
|
|
38100
|
+
const location = finding.path ? ` ${path44.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
|
|
37934
38101
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${location} \u2014 ${finding.message}`);
|
|
37935
38102
|
if (finding.suggestion) lines.push(` \u21B3 ${finding.suggestion}`);
|
|
37936
38103
|
}
|
|
@@ -38041,7 +38208,7 @@ async function runHelp(ctx, commandName) {
|
|
|
38041
38208
|
}
|
|
38042
38209
|
|
|
38043
38210
|
// src/commands/init.ts
|
|
38044
|
-
import
|
|
38211
|
+
import path45 from "path";
|
|
38045
38212
|
async function runInit(ctx) {
|
|
38046
38213
|
const languageFlag = flagString(ctx.flags, "language");
|
|
38047
38214
|
const language = languageFlag === "en" ? "en" : languageFlag === "es" ? "es" : void 0;
|
|
@@ -38091,11 +38258,11 @@ async function runInit(ctx) {
|
|
|
38091
38258
|
const lines = [msg("init.title", ctx.language), ""];
|
|
38092
38259
|
if (result.created.length > 0) {
|
|
38093
38260
|
lines.push(msg("init.done", ctx.language));
|
|
38094
|
-
for (const file of result.created) lines.push(` + ${
|
|
38261
|
+
for (const file of result.created) lines.push(` + ${path45.relative(ctx.cwd, file)}`);
|
|
38095
38262
|
if (compiled.length > 0) {
|
|
38096
38263
|
lines.push("");
|
|
38097
38264
|
lines.push("Adaptadores compilados:");
|
|
38098
|
-
for (const file of compiled) lines.push(` + ${
|
|
38265
|
+
for (const file of compiled) lines.push(` + ${path45.relative(ctx.cwd, path45.join(ctx.cwd, file))}`);
|
|
38099
38266
|
}
|
|
38100
38267
|
lines.push("");
|
|
38101
38268
|
lines.push("Siguiente: crea un cambio con `satlas new <slug>` y escribe la spec funcional.");
|
|
@@ -38105,8 +38272,8 @@ async function runInit(ctx) {
|
|
|
38105
38272
|
exitCode: hasErrors2 ? 1 : 0,
|
|
38106
38273
|
diagnostics: result.diagnostics,
|
|
38107
38274
|
data: {
|
|
38108
|
-
sddDir:
|
|
38109
|
-
created: result.created.map((f) =>
|
|
38275
|
+
sddDir: path45.relative(ctx.cwd, result.sddDir),
|
|
38276
|
+
created: result.created.map((f) => path45.relative(ctx.cwd, f)),
|
|
38110
38277
|
detected: result.detected?.matches.map((m) => ({ name: m.name, score: m.score })),
|
|
38111
38278
|
best: result.detected?.best?.name
|
|
38112
38279
|
},
|
|
@@ -38147,7 +38314,7 @@ async function runMetrics(ctx) {
|
|
|
38147
38314
|
}
|
|
38148
38315
|
|
|
38149
38316
|
// src/commands/mockup.ts
|
|
38150
|
-
import
|
|
38317
|
+
import path46 from "path";
|
|
38151
38318
|
async function runMockup(ctx) {
|
|
38152
38319
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
38153
38320
|
const slug = ctx.positionals[0];
|
|
@@ -38197,17 +38364,17 @@ async function runMockup(ctx) {
|
|
|
38197
38364
|
` pantallas: ${plan.screens.length}`,
|
|
38198
38365
|
...plan.screens.map((s) => ` - ${s.id}: ${s.title} (${s.illustrates.length} escenario(s))`),
|
|
38199
38366
|
"",
|
|
38200
|
-
` plan: ${
|
|
38201
|
-
` manifiesto: ${
|
|
38367
|
+
` plan: ${path46.relative(ctx.cwd, planFile)}`,
|
|
38368
|
+
` manifiesto: ${path46.relative(ctx.cwd, manifestFile)}`,
|
|
38202
38369
|
"",
|
|
38203
38370
|
`Siguiente: genera el HTML de cada pantalla (fase ${agentCommand("mockup", slug, config)}) y valida con \`satlas mockup ${slug} --check\`.`
|
|
38204
38371
|
];
|
|
38205
|
-
return { exitCode: 0, diagnostics: [], data: { plan, planFile:
|
|
38372
|
+
return { exitCode: 0, diagnostics: [], data: { plan, planFile: path46.relative(ctx.cwd, planFile), manifestFile: path46.relative(ctx.cwd, manifestFile) }, text: lines };
|
|
38206
38373
|
}
|
|
38207
38374
|
|
|
38208
38375
|
// src/mcp/server.ts
|
|
38209
38376
|
import { promises as fs3 } from "fs";
|
|
38210
|
-
import
|
|
38377
|
+
import path51 from "path";
|
|
38211
38378
|
import { createInterface } from "readline/promises";
|
|
38212
38379
|
|
|
38213
38380
|
// src/mcp/protocol.ts
|
|
@@ -38269,7 +38436,7 @@ function stringArg(args, key) {
|
|
|
38269
38436
|
}
|
|
38270
38437
|
|
|
38271
38438
|
// src/mcp/tools/contracts.ts
|
|
38272
|
-
import
|
|
38439
|
+
import path47 from "path";
|
|
38273
38440
|
async function runAtlasContracts(args, host) {
|
|
38274
38441
|
const resolved = await host.getWorkspace();
|
|
38275
38442
|
if (!resolved) return noWorkspaceResult();
|
|
@@ -38289,7 +38456,7 @@ async function runAtlasContracts(args, host) {
|
|
|
38289
38456
|
slug,
|
|
38290
38457
|
mode,
|
|
38291
38458
|
contracts: state.files.map((file) => ({
|
|
38292
|
-
path:
|
|
38459
|
+
path: path47.relative(resolved.root, file.path),
|
|
38293
38460
|
format: file.format,
|
|
38294
38461
|
operations: file.operations.map((operation) => operation.id)
|
|
38295
38462
|
})),
|
|
@@ -38325,12 +38492,12 @@ async function runAtlasFixes(_args, host) {
|
|
|
38325
38492
|
}
|
|
38326
38493
|
|
|
38327
38494
|
// src/mcp/tools/glossary.ts
|
|
38328
|
-
import
|
|
38495
|
+
import path48 from "path";
|
|
38329
38496
|
async function runAtlasGlossary(_args, host) {
|
|
38330
38497
|
const resolved = await host.getWorkspace();
|
|
38331
38498
|
if (!resolved) return noWorkspaceResult();
|
|
38332
38499
|
const { root, config } = resolved;
|
|
38333
|
-
const glossaryPath =
|
|
38500
|
+
const glossaryPath = path48.resolve(root, config.spec.glossary);
|
|
38334
38501
|
const raw = await readTextIfExists(glossaryPath);
|
|
38335
38502
|
if (raw === void 0) {
|
|
38336
38503
|
return jsonResult({
|
|
@@ -38351,7 +38518,7 @@ async function runAtlasGlossary(_args, host) {
|
|
|
38351
38518
|
}
|
|
38352
38519
|
|
|
38353
38520
|
// src/mcp/tools/links.ts
|
|
38354
|
-
import
|
|
38521
|
+
import path49 from "path";
|
|
38355
38522
|
async function runAtlasLinks(_args, host) {
|
|
38356
38523
|
const resolved = await host.getWorkspace();
|
|
38357
38524
|
if (!resolved) return noWorkspaceResult();
|
|
@@ -38362,7 +38529,7 @@ async function runAtlasLinks(_args, host) {
|
|
|
38362
38529
|
return jsonResult({
|
|
38363
38530
|
links: state.entries.map((entry) => ({
|
|
38364
38531
|
name: entry.name,
|
|
38365
|
-
path:
|
|
38532
|
+
path: path49.relative(resolved.root, entry.path),
|
|
38366
38533
|
available: entry.available,
|
|
38367
38534
|
requirements: entry.requirements,
|
|
38368
38535
|
domains: entry.domains
|
|
@@ -38399,7 +38566,7 @@ async function runAtlasImpact(args, host) {
|
|
|
38399
38566
|
}
|
|
38400
38567
|
|
|
38401
38568
|
// src/evaluate.ts
|
|
38402
|
-
import
|
|
38569
|
+
import path50 from "path";
|
|
38403
38570
|
function livingRequirementsMap2(specs) {
|
|
38404
38571
|
const map = /* @__PURE__ */ new Map();
|
|
38405
38572
|
for (const spec of specs) {
|
|
@@ -38408,7 +38575,7 @@ function livingRequirementsMap2(specs) {
|
|
|
38408
38575
|
return map;
|
|
38409
38576
|
}
|
|
38410
38577
|
async function evaluateChange(workspace, config, change, approvals) {
|
|
38411
|
-
const deltaPath =
|
|
38578
|
+
const deltaPath = path50.join(change.dir, "spec.md");
|
|
38412
38579
|
const deltaContent = await readTextIfExists(deltaPath);
|
|
38413
38580
|
const approval = verifyApproval(change, approvals, config, deltaContent ?? void 0);
|
|
38414
38581
|
const living = livingRequirementsMap2(workspace.specs);
|
|
@@ -38704,8 +38871,8 @@ var WorkspaceCache = class {
|
|
|
38704
38871
|
}
|
|
38705
38872
|
};
|
|
38706
38873
|
async function workspaceStamp(root) {
|
|
38707
|
-
const sddDir =
|
|
38708
|
-
const markers = [sddDir,
|
|
38874
|
+
const sddDir = path51.join(root, ".sdd");
|
|
38875
|
+
const markers = [sddDir, path51.join(sddDir, "config.yaml"), path51.join(sddDir, "changes"), path51.join(sddDir, "specs"), path51.join(sddDir, "approvals.yaml")];
|
|
38709
38876
|
let stamp2 = 0;
|
|
38710
38877
|
for (const marker of markers) {
|
|
38711
38878
|
try {
|
|
@@ -38723,7 +38890,7 @@ async function resolveMcpWorkspace(cwd, cache) {
|
|
|
38723
38890
|
const cached2 = cache.get(root);
|
|
38724
38891
|
if (cached2 && cached2.mtimeMs === stamp2) return cached2.value;
|
|
38725
38892
|
const { workspace, config } = await loadWorkspace(root);
|
|
38726
|
-
const approvals = await loadApprovals(
|
|
38893
|
+
const approvals = await loadApprovals(path51.join(root, ".sdd"));
|
|
38727
38894
|
const value = { root, workspace, config, approvals: approvals.byArtifact };
|
|
38728
38895
|
cache.set(root, stamp2, value);
|
|
38729
38896
|
return value;
|
|
@@ -38815,7 +38982,7 @@ async function runMcp(ctx) {
|
|
|
38815
38982
|
}
|
|
38816
38983
|
|
|
38817
38984
|
// src/commands/packs.ts
|
|
38818
|
-
import
|
|
38985
|
+
import path52 from "path";
|
|
38819
38986
|
async function runPacks(ctx) {
|
|
38820
38987
|
const { root, config, workspace } = await requireWorkspace(ctx);
|
|
38821
38988
|
const slug = flagString(ctx.flags, "check");
|
|
@@ -38873,14 +39040,14 @@ async function runPacks(ctx) {
|
|
|
38873
39040
|
data: {
|
|
38874
39041
|
slug,
|
|
38875
39042
|
packs: evaluations.map((evaluation) => ({ id: evaluation.pack.id, status: evaluation.status, passed: evaluation.passed, failed: evaluation.failed, results: evaluation.results })),
|
|
38876
|
-
analyzePath:
|
|
39043
|
+
analyzePath: path52.posix.join(".sdd", "changes", slug, "analyze.md")
|
|
38877
39044
|
},
|
|
38878
39045
|
text: lines
|
|
38879
39046
|
};
|
|
38880
39047
|
}
|
|
38881
39048
|
|
|
38882
39049
|
// src/commands/pause.ts
|
|
38883
|
-
import
|
|
39050
|
+
import path53 from "path";
|
|
38884
39051
|
async function runPause(ctx) {
|
|
38885
39052
|
const slug = ctx.positionals[0];
|
|
38886
39053
|
if (!slug) {
|
|
@@ -38910,7 +39077,7 @@ async function runPause(ctx) {
|
|
|
38910
39077
|
` motivo: ${result.paused?.reason ?? ""}`,
|
|
38911
39078
|
` por: ${result.paused?.by ?? ""}`,
|
|
38912
39079
|
` fecha: ${result.paused?.at ?? ""}`,
|
|
38913
|
-
` estado: ${
|
|
39080
|
+
` estado: ${path53.relative(ctx.cwd, result.path)}`,
|
|
38914
39081
|
"",
|
|
38915
39082
|
`Ret\xF3malo cuando quieras con \`satlas resume ${slug}\`.`
|
|
38916
39083
|
];
|
|
@@ -38953,7 +39120,7 @@ async function runResume(ctx) {
|
|
|
38953
39120
|
}
|
|
38954
39121
|
|
|
38955
39122
|
// src/commands/new.ts
|
|
38956
|
-
import
|
|
39123
|
+
import path54 from "path";
|
|
38957
39124
|
async function runNew(ctx) {
|
|
38958
39125
|
const slug = ctx.positionals[0];
|
|
38959
39126
|
if (!slug) {
|
|
@@ -38979,14 +39146,14 @@ async function runNew(ctx) {
|
|
|
38979
39146
|
msg("new.title", ctx.language),
|
|
38980
39147
|
"",
|
|
38981
39148
|
msg("new.done", ctx.language),
|
|
38982
|
-
...result.files.map((f) => ` + ${
|
|
39149
|
+
...result.files.map((f) => ` + ${path54.relative(ctx.cwd, f)}`),
|
|
38983
39150
|
"",
|
|
38984
39151
|
`Siguiente: ${agentCommand("specify", result.slug, config)} \u2014 escribe la especificaci\xF3n 100% funcional y de negocio.`
|
|
38985
39152
|
];
|
|
38986
39153
|
return {
|
|
38987
39154
|
exitCode: hasErrors2 ? 1 : 0,
|
|
38988
39155
|
diagnostics: result.diagnostics,
|
|
38989
|
-
data: { slug: result.slug, files: result.files.map((f) =>
|
|
39156
|
+
data: { slug: result.slug, files: result.files.map((f) => path54.relative(ctx.cwd, f)) },
|
|
38990
39157
|
text: lines
|
|
38991
39158
|
};
|
|
38992
39159
|
}
|
|
@@ -39119,7 +39286,7 @@ async function runNext(ctx) {
|
|
|
39119
39286
|
}
|
|
39120
39287
|
|
|
39121
39288
|
// src/commands/present.ts
|
|
39122
|
-
import
|
|
39289
|
+
import path55 from "path";
|
|
39123
39290
|
async function runPresentCommand(ctx) {
|
|
39124
39291
|
const { root } = await requireWorkspace(ctx);
|
|
39125
39292
|
const slug = ctx.positionals[0];
|
|
@@ -39131,7 +39298,7 @@ async function runPresentCommand(ctx) {
|
|
|
39131
39298
|
if (result.path) {
|
|
39132
39299
|
lines.push("Propuesta generada");
|
|
39133
39300
|
lines.push("");
|
|
39134
|
-
lines.push(` archivo: ${
|
|
39301
|
+
lines.push(` archivo: ${path55.relative(ctx.cwd, result.path)}`);
|
|
39135
39302
|
if (result.hash) lines.push(` hash de la spec: ${shortHash(result.hash)}`);
|
|
39136
39303
|
lines.push("");
|
|
39137
39304
|
lines.push("\xC1brela en el navegador y comp\xE1rtela con el stakeholder.");
|
|
@@ -39141,13 +39308,13 @@ async function runPresentCommand(ctx) {
|
|
|
39141
39308
|
return {
|
|
39142
39309
|
exitCode: hasErrors2 ? 1 : 0,
|
|
39143
39310
|
diagnostics: result.diagnostics,
|
|
39144
|
-
data: result.path ? { path:
|
|
39311
|
+
data: result.path ? { path: path55.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
|
|
39145
39312
|
text: lines
|
|
39146
39313
|
};
|
|
39147
39314
|
}
|
|
39148
39315
|
|
|
39149
39316
|
// src/commands/review.ts
|
|
39150
|
-
import
|
|
39317
|
+
import path56 from "path";
|
|
39151
39318
|
async function runReview(ctx) {
|
|
39152
39319
|
const slug = ctx.positionals[0];
|
|
39153
39320
|
if (!slug) {
|
|
@@ -39159,7 +39326,7 @@ async function runReview(ctx) {
|
|
|
39159
39326
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-REVIEW-000", severity: "error", message: `No existe el cambio "${slug}"` }] };
|
|
39160
39327
|
}
|
|
39161
39328
|
const mode = config.gates.review.mode;
|
|
39162
|
-
const reviewFile =
|
|
39329
|
+
const reviewFile = path56.join(change.dir, "review.md");
|
|
39163
39330
|
if (!await exists(reviewFile)) {
|
|
39164
39331
|
const templates = templatesFor(config.project.language);
|
|
39165
39332
|
await writeText(reviewFile, renderTemplate(templates.review, { TITLE: change.meta?.title ?? slug, SLUG: slug }));
|
|
@@ -39167,7 +39334,7 @@ async function runReview(ctx) {
|
|
|
39167
39334
|
"Revisi\xF3n del cambio",
|
|
39168
39335
|
"",
|
|
39169
39336
|
` modo: ${mode}`,
|
|
39170
|
-
` creado: ${
|
|
39337
|
+
` creado: ${path56.relative(ctx.cwd, reviewFile)}`,
|
|
39171
39338
|
"",
|
|
39172
39339
|
`Revisa el c\xF3digo con la fase del agente: ${agentCommand("review", slug, config)}`,
|
|
39173
39340
|
"Al cerrar, deja `- resultado: pass` en el veredicto y marca los hallazgos resueltos."
|
|
@@ -39180,7 +39347,7 @@ async function runReview(ctx) {
|
|
|
39180
39347
|
const passed = reviewPassed(review);
|
|
39181
39348
|
const lines = ["Revisi\xF3n del cambio", ""];
|
|
39182
39349
|
lines.push(` modo: ${mode}`);
|
|
39183
|
-
lines.push(` archivo: ${
|
|
39350
|
+
lines.push(` archivo: ${path56.relative(ctx.cwd, reviewFile)}`);
|
|
39184
39351
|
lines.push(` resultado: ${review?.verdict ?? "pendiente"}${review?.by ? ` \xB7 por ${review.by}` : ""}${review?.date ? ` \xB7 ${review.date}` : ""}`);
|
|
39185
39352
|
lines.push(` hallazgos: ${review?.findings.length ?? 0} (${open.length} sin resolver, ${blocking.length} bloqueante(s))`);
|
|
39186
39353
|
if (open.length > 0) {
|
|
@@ -39282,6 +39449,124 @@ function usage(usageText) {
|
|
|
39282
39449
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-RUN-000", severity: "error", message: `Uso: ${usageText}` }] };
|
|
39283
39450
|
}
|
|
39284
39451
|
|
|
39452
|
+
// src/commands/self-update.ts
|
|
39453
|
+
function rutaEjecutable() {
|
|
39454
|
+
return process.argv[1] ?? process.execPath;
|
|
39455
|
+
}
|
|
39456
|
+
async function instalarUltima(instalacion, cwd) {
|
|
39457
|
+
if (!instalacion.comando) return { ok: false, salida: instalacion.motivo ?? "no se puede actualizar esta instalaci\xF3n" };
|
|
39458
|
+
const resultado = await runProcess(instalacion.comando, { cwd, timeoutMs: 18e4 });
|
|
39459
|
+
return { ok: resultado.ok, salida: `${resultado.stdout}${resultado.stderr}`.trim() };
|
|
39460
|
+
}
|
|
39461
|
+
async function runSelfUpdate(ctx) {
|
|
39462
|
+
const instalada = cliVersion();
|
|
39463
|
+
const soloConsultar = flagBool(ctx.flags, "check");
|
|
39464
|
+
const auto = flagString(ctx.flags, "auto");
|
|
39465
|
+
if (auto !== void 0) {
|
|
39466
|
+
const valor = auto.toLowerCase();
|
|
39467
|
+
if (valor !== "on" && valor !== "off") {
|
|
39468
|
+
return {
|
|
39469
|
+
exitCode: 2,
|
|
39470
|
+
diagnostics: [{ code: "ATLAS-SELFUPDATE-000", severity: "error", message: "Valor inv\xE1lido para --auto", suggestion: "satlas self-update --auto on | off" }]
|
|
39471
|
+
};
|
|
39472
|
+
}
|
|
39473
|
+
const preferencias2 = await leerPreferencias();
|
|
39474
|
+
const nuevas = { ...preferencias2, automatica: valor === "on" };
|
|
39475
|
+
const file = await escribirPreferencias(nuevas);
|
|
39476
|
+
return {
|
|
39477
|
+
exitCode: 0,
|
|
39478
|
+
diagnostics: [],
|
|
39479
|
+
data: { automatica: nuevas.automatica, archivo: file },
|
|
39480
|
+
text: [
|
|
39481
|
+
"Actualizaci\xF3n desatendida",
|
|
39482
|
+
"",
|
|
39483
|
+
` ${nuevas.automatica ? "activada: la herramienta se actualizar\xE1 sola al detectar una versi\xF3n nueva" : "desactivada: solo se avisa cuando hay versi\xF3n nueva"}`,
|
|
39484
|
+
` preferencia: ${file}`
|
|
39485
|
+
]
|
|
39486
|
+
};
|
|
39487
|
+
}
|
|
39488
|
+
const publicada = await consultarVersionPublicada();
|
|
39489
|
+
const instalacion = detectarInstalacion(rutaEjecutable());
|
|
39490
|
+
const preferencias = await leerPreferencias();
|
|
39491
|
+
if (publicada === void 0) {
|
|
39492
|
+
return {
|
|
39493
|
+
exitCode: 1,
|
|
39494
|
+
diagnostics: [
|
|
39495
|
+
{
|
|
39496
|
+
code: "ATLAS-SELFUPDATE-001",
|
|
39497
|
+
severity: "error",
|
|
39498
|
+
message: "No se pudo consultar la versi\xF3n publicada",
|
|
39499
|
+
suggestion: `Comprueba tu conexi\xF3n o instala a mano: ${instalacion.comando ?? "npm install -g specatlas@latest"}`
|
|
39500
|
+
}
|
|
39501
|
+
]
|
|
39502
|
+
};
|
|
39503
|
+
}
|
|
39504
|
+
const base = [
|
|
39505
|
+
"Actualizaci\xF3n de SpecAtlas",
|
|
39506
|
+
"",
|
|
39507
|
+
` instalada: ${instalada}`,
|
|
39508
|
+
` publicada: ${publicada}`,
|
|
39509
|
+
` instalada con: ${instalacion.gestor}`,
|
|
39510
|
+
` desatendida: ${preferencias.automatica ? "activada" : "desactivada"}`
|
|
39511
|
+
];
|
|
39512
|
+
if (!hayVersionNueva(instalada, publicada)) {
|
|
39513
|
+
return { exitCode: 0, diagnostics: [], data: { instalada, publicada, actualizada: false, alDia: true }, text: [...base, "", " Ya est\xE1s en la \xFAltima versi\xF3n."] };
|
|
39514
|
+
}
|
|
39515
|
+
if (soloConsultar) {
|
|
39516
|
+
return {
|
|
39517
|
+
exitCode: 0,
|
|
39518
|
+
diagnostics: [],
|
|
39519
|
+
data: { instalada, publicada, actualizada: false, alDia: false, comando: instalacion.comando },
|
|
39520
|
+
text: [...base, "", ` Hay una versi\xF3n nueva. Actualiza con \`satlas self-update\`${instalacion.comando ? ` (o \`${instalacion.comando}\`)` : ""}.`]
|
|
39521
|
+
};
|
|
39522
|
+
}
|
|
39523
|
+
if (!instalacion.comando) {
|
|
39524
|
+
return {
|
|
39525
|
+
exitCode: 0,
|
|
39526
|
+
diagnostics: [{ code: "ATLAS-SELFUPDATE-002", severity: "info", message: instalacion.motivo ?? "esta instalaci\xF3n no se actualiza sola" }],
|
|
39527
|
+
data: { instalada, publicada, actualizada: false, motivo: instalacion.motivo },
|
|
39528
|
+
text: [...base, "", ` ${instalacion.motivo}`]
|
|
39529
|
+
};
|
|
39530
|
+
}
|
|
39531
|
+
const resultado = await instalarUltima(instalacion, ctx.cwd);
|
|
39532
|
+
if (!resultado.ok) {
|
|
39533
|
+
return {
|
|
39534
|
+
exitCode: 1,
|
|
39535
|
+
diagnostics: [
|
|
39536
|
+
{
|
|
39537
|
+
code: "ATLAS-SELFUPDATE-003",
|
|
39538
|
+
severity: "error",
|
|
39539
|
+
message: `No se pudo instalar ${publicada}: ${resultado.salida.split("\n")[0] ?? "el gestor fall\xF3"}`,
|
|
39540
|
+
suggestion: `Ejec\xFAtalo a mano: ${instalacion.comando}`
|
|
39541
|
+
}
|
|
39542
|
+
],
|
|
39543
|
+
data: { instalada, publicada, actualizada: false, comando: instalacion.comando },
|
|
39544
|
+
text: [...base, "", ` La instalaci\xF3n fall\xF3; sigues con ${instalada}.`]
|
|
39545
|
+
};
|
|
39546
|
+
}
|
|
39547
|
+
return {
|
|
39548
|
+
exitCode: 0,
|
|
39549
|
+
diagnostics: [],
|
|
39550
|
+
data: { instalada, publicada, actualizada: true, comando: instalacion.comando },
|
|
39551
|
+
text: [...base, "", ` Actualizada: ${instalada} \u2192 ${publicada}.`, " Abre una terminal nueva si el comando sigue apuntando a la versi\xF3n anterior."]
|
|
39552
|
+
};
|
|
39553
|
+
}
|
|
39554
|
+
async function avisoDeActualizacion(json2, cwd) {
|
|
39555
|
+
try {
|
|
39556
|
+
const aviso = await comprobarActualizacion({ instalada: cliVersion(), rutaEjecutable: rutaEjecutable(), json: json2 });
|
|
39557
|
+
if (!aviso) return [];
|
|
39558
|
+
const preferencias = await leerPreferencias();
|
|
39559
|
+
if (!preferencias.automatica) {
|
|
39560
|
+
return ["", `Hay una versi\xF3n nueva de SpecAtlas: ${aviso.instalada} \u2192 ${aviso.publicada}. Actualiza con \`satlas self-update\`.`];
|
|
39561
|
+
}
|
|
39562
|
+
const instalacion = detectarInstalacion(rutaEjecutable());
|
|
39563
|
+
const resultado = await instalarUltima(instalacion, cwd);
|
|
39564
|
+
return resultado.ok ? ["", `SpecAtlas se actualiz\xF3 solo: ${aviso.instalada} \u2192 ${aviso.publicada}.`] : ["", `Hay una versi\xF3n nueva (${aviso.publicada}) y la actualizaci\xF3n autom\xE1tica fall\xF3. Ejecuta \`${aviso.comando}\`.`];
|
|
39565
|
+
} catch {
|
|
39566
|
+
return [];
|
|
39567
|
+
}
|
|
39568
|
+
}
|
|
39569
|
+
|
|
39285
39570
|
// src/commands/status.ts
|
|
39286
39571
|
async function runStatus(ctx) {
|
|
39287
39572
|
const { root, workspace, config, approvals } = await requireWorkspace(ctx);
|
|
@@ -39356,7 +39641,7 @@ async function runStatus(ctx) {
|
|
|
39356
39641
|
}
|
|
39357
39642
|
|
|
39358
39643
|
// src/commands/trace.ts
|
|
39359
|
-
import
|
|
39644
|
+
import path57 from "path";
|
|
39360
39645
|
async function runTrace(ctx) {
|
|
39361
39646
|
const { workspace, config } = await requireWorkspace(ctx);
|
|
39362
39647
|
const slug = flagString(ctx.flags, "change");
|
|
@@ -39376,7 +39661,7 @@ async function runTrace(ctx) {
|
|
|
39376
39661
|
data.push({ slug: change.slug, nodes: result.graph.nodes.length, edges: result.graph.edges.length, summary: result.summary, findings: result.findings });
|
|
39377
39662
|
lines.push(` ${change.slug}: ${result.graph.nodes.length} nodos, ${result.graph.edges.length} aristas, ${errors} errores, ${result.summary.warnings} avisos`);
|
|
39378
39663
|
for (const finding of result.findings) {
|
|
39379
|
-
const location = finding.path ? ` (${
|
|
39664
|
+
const location = finding.path ? ` (${path57.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
|
|
39380
39665
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}${location}`);
|
|
39381
39666
|
}
|
|
39382
39667
|
}
|
|
@@ -39386,7 +39671,7 @@ async function runTrace(ctx) {
|
|
|
39386
39671
|
}
|
|
39387
39672
|
|
|
39388
39673
|
// src/commands/upgrade.ts
|
|
39389
|
-
import
|
|
39674
|
+
import path58 from "path";
|
|
39390
39675
|
async function runUpgrade(ctx) {
|
|
39391
39676
|
const { root } = await requireWorkspace(ctx);
|
|
39392
39677
|
const apply2 = flagBool(ctx.flags, "apply");
|
|
@@ -39440,7 +39725,7 @@ function renderPreview(plan, root, ctx) {
|
|
|
39440
39725
|
}
|
|
39441
39726
|
for (const item of plan.unreadable) {
|
|
39442
39727
|
lines.push(` Ilegible: "${item.artifact}" \u2014 ${item.reason}`);
|
|
39443
|
-
lines.push(` \u21B3 ${
|
|
39728
|
+
lines.push(` \u21B3 ${path58.relative(ctx.cwd, item.path)}`);
|
|
39444
39729
|
}
|
|
39445
39730
|
return { exitCode: 0, diagnostics: [], data: summarizable(plan), text: lines };
|
|
39446
39731
|
}
|
|
@@ -39503,7 +39788,7 @@ function renderRollback(report, ctx) {
|
|
|
39503
39788
|
}
|
|
39504
39789
|
|
|
39505
39790
|
// src/commands/validate.ts
|
|
39506
|
-
import
|
|
39791
|
+
import path59 from "path";
|
|
39507
39792
|
async function runValidate(ctx) {
|
|
39508
39793
|
const { root, workspace, config, approvals } = await requireWorkspace(ctx);
|
|
39509
39794
|
const slug = flagString(ctx.flags, "change");
|
|
@@ -39530,7 +39815,7 @@ async function runValidate(ctx) {
|
|
|
39530
39815
|
for (const finding of evaluation.lintFindings) {
|
|
39531
39816
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${finding.line ? `:${finding.line}` : ""} \u2014 ${finding.message}`);
|
|
39532
39817
|
}
|
|
39533
|
-
lines.push(` archivo: ${
|
|
39818
|
+
lines.push(` archivo: ${path59.relative(ctx.cwd, path59.join(change.dir, "spec.md"))}`);
|
|
39534
39819
|
}
|
|
39535
39820
|
if (targets.length === 0) lines.push("Sin cambios activos.");
|
|
39536
39821
|
if (advisory && advisory.plan.pending.length > 0) {
|
|
@@ -39558,7 +39843,7 @@ async function runValidate(ctx) {
|
|
|
39558
39843
|
}
|
|
39559
39844
|
|
|
39560
39845
|
// src/commands/verify.ts
|
|
39561
|
-
import
|
|
39846
|
+
import path60 from "path";
|
|
39562
39847
|
async function runVerify(ctx) {
|
|
39563
39848
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
39564
39849
|
const slug = ctx.positionals[0];
|
|
@@ -39601,7 +39886,7 @@ async function runVerify(ctx) {
|
|
|
39601
39886
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-VERIFY-001", severity: "error", message: "Falta --by <nombre> (o define meta.owner)" }] };
|
|
39602
39887
|
}
|
|
39603
39888
|
const profilesDir = await resolveProfilesDir();
|
|
39604
|
-
const profile = await loadActiveProfile(
|
|
39889
|
+
const profile = await loadActiveProfile(path60.join(root, ".sdd"), [profilesDir ?? "", path60.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
|
|
39605
39890
|
const record = await recordEvidence({
|
|
39606
39891
|
root,
|
|
39607
39892
|
slug,
|
|
@@ -39623,7 +39908,7 @@ async function runVerify(ctx) {
|
|
|
39623
39908
|
if (record.evidence.command) lines.push(` comando: ${record.evidence.command}`);
|
|
39624
39909
|
lines.push(` resultado: ${record.evidence.result}`);
|
|
39625
39910
|
if (record.evidence.outputHash) lines.push(` hash: ${record.evidence.outputHash}`);
|
|
39626
|
-
lines.push(` archivo: ${
|
|
39911
|
+
lines.push(` archivo: ${path60.relative(ctx.cwd, record.path)}`);
|
|
39627
39912
|
if (record.stdout) {
|
|
39628
39913
|
lines.push("");
|
|
39629
39914
|
lines.push(" salida (\xFAltimas l\xEDneas):");
|
|
@@ -39651,14 +39936,14 @@ async function runVerify(ctx) {
|
|
|
39651
39936
|
return {
|
|
39652
39937
|
exitCode: record.exitCode === 0 ? 0 : 1,
|
|
39653
39938
|
diagnostics: [...record.diagnostics, ...mockupDiagnostics],
|
|
39654
|
-
data: { evidence: record.evidence, path:
|
|
39939
|
+
data: { evidence: record.evidence, path: path60.relative(ctx.cwd, record.path), exitCode: record.exitCode },
|
|
39655
39940
|
text: lines
|
|
39656
39941
|
};
|
|
39657
39942
|
}
|
|
39658
39943
|
|
|
39659
39944
|
// src/commands/watch.ts
|
|
39660
39945
|
import { watch } from "fs";
|
|
39661
|
-
import
|
|
39946
|
+
import path61 from "path";
|
|
39662
39947
|
var DEBOUNCE_MS = 250;
|
|
39663
39948
|
function stamp(now) {
|
|
39664
39949
|
return now.toTimeString().slice(0, 8);
|
|
@@ -39672,7 +39957,7 @@ async function runWatch(ctx) {
|
|
|
39672
39957
|
diagnostics: [{ code: "ATLAS-WATCH-001", severity: "error", message: "`satlas watch` es interactivo y no admite --json", suggestion: "Usa `satlas ci --json` en una tuber\xEDa" }]
|
|
39673
39958
|
};
|
|
39674
39959
|
}
|
|
39675
|
-
const sddDir =
|
|
39960
|
+
const sddDir = path61.join(root, ".sdd");
|
|
39676
39961
|
const write = (line) => {
|
|
39677
39962
|
process.stdout.write(`${line}
|
|
39678
39963
|
`);
|
|
@@ -39690,7 +39975,7 @@ async function runWatch(ctx) {
|
|
|
39690
39975
|
} else {
|
|
39691
39976
|
write(` ${counts.errors} errores, ${counts.warnings} avisos`);
|
|
39692
39977
|
for (const finding of findings.slice(0, 8)) {
|
|
39693
|
-
const where = finding.path ? ` ${
|
|
39978
|
+
const where = finding.path ? ` ${path61.relative(root, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
|
|
39694
39979
|
write(` ${finding.severity === "error" ? "ERROR" : "AVISO"} ${finding.code}${where} \u2014 ${finding.message}`);
|
|
39695
39980
|
}
|
|
39696
39981
|
if (findings.length > 8) write(` \u2026 y ${findings.length - 8} m\xE1s (satlas validate)`);
|
|
@@ -39701,7 +39986,7 @@ async function runWatch(ctx) {
|
|
|
39701
39986
|
}
|
|
39702
39987
|
if (changes.length === 0) write(" sin cambios activos");
|
|
39703
39988
|
};
|
|
39704
|
-
write(`Vigilando ${
|
|
39989
|
+
write(`Vigilando ${path61.relative(ctx.cwd, sddDir) || ".sdd"} \u2014 Ctrl+C para salir`);
|
|
39705
39990
|
await pass();
|
|
39706
39991
|
await new Promise((resolve) => {
|
|
39707
39992
|
let timer;
|
|
@@ -39805,6 +40090,7 @@ var HANDLERS2 = {
|
|
|
39805
40090
|
pause: runPause,
|
|
39806
40091
|
resume: runResume,
|
|
39807
40092
|
archive: runArchive,
|
|
40093
|
+
"self-update": runSelfUpdate,
|
|
39808
40094
|
version: async () => ({ exitCode: 0, data: { version: cliVersion() }, diagnostics: [], text: [`specatlas ${cliVersion()}`] }),
|
|
39809
40095
|
help: (ctx) => runHelp(ctx)
|
|
39810
40096
|
};
|
|
@@ -39834,7 +40120,7 @@ async function requireWorkspace(ctx) {
|
|
|
39834
40120
|
throw new CliError("ATLAS-WS-001", msg("cli.noWorkspace", ctx.language), 2);
|
|
39835
40121
|
}
|
|
39836
40122
|
const { workspace, config } = await loadWorkspace(root);
|
|
39837
|
-
const approvals = await loadApprovals(
|
|
40123
|
+
const approvals = await loadApprovals(path62.join(root, ".sdd"));
|
|
39838
40124
|
return { root, workspace, config, approvals: approvals.byArtifact };
|
|
39839
40125
|
}
|
|
39840
40126
|
var CliError = class extends Error {
|
|
@@ -39885,6 +40171,11 @@ async function main(argv) {
|
|
|
39885
40171
|
try {
|
|
39886
40172
|
const result = await command.handler(ctx);
|
|
39887
40173
|
printResult(parsed.command, result, json2, ctx.language);
|
|
40174
|
+
if (parsed.command !== "self-update" && parsed.command !== "mcp") {
|
|
40175
|
+
const aviso = await avisoDeActualizacion(json2, ctx.cwd);
|
|
40176
|
+
if (aviso.length > 0) process.stdout.write(`${aviso.join("\n")}
|
|
40177
|
+
`);
|
|
40178
|
+
}
|
|
39888
40179
|
return result.exitCode;
|
|
39889
40180
|
} catch (err) {
|
|
39890
40181
|
if (err instanceof CliError) {
|
|
@@ -39903,7 +40194,7 @@ function printResult(command, result, json2, language) {
|
|
|
39903
40194
|
}
|
|
39904
40195
|
const lines = [...result.text ?? []];
|
|
39905
40196
|
for (const d of result.diagnostics) {
|
|
39906
|
-
const location = d.path ? ` ${
|
|
40197
|
+
const location = d.path ? ` ${path62.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
|
|
39907
40198
|
lines.push(`${d.severity === "error" ? "ERROR" : d.severity === "warning" ? "AVISO" : "NOTA"} ${d.code}${location} \u2014 ${d.message}`);
|
|
39908
40199
|
if (d.suggestion) lines.push(` \u21B3 ${d.suggestion}`);
|
|
39909
40200
|
}
|