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