specatlas 0.1.29 → 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 +433 -109
- 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
|
};
|
|
@@ -10596,6 +10664,19 @@ async function loadChange(root, slug, relDir) {
|
|
|
10596
10664
|
diagnostics.push(...change.fix.diagnostics);
|
|
10597
10665
|
change.fixCovers = parseFixCovers(fixRaw);
|
|
10598
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;
|
|
10599
10680
|
const mockupManifest = path5.join(dir, "mockups", "manifest.yaml");
|
|
10600
10681
|
if (await exists(mockupManifest)) change.mockupManifestPath = mockupManifest;
|
|
10601
10682
|
return change;
|
|
@@ -10809,6 +10890,44 @@ satlas verify <slug> --file fix --scenario REQ-DOMINIO-001-S1 --command "<comand
|
|
|
10809
10890
|
o, si es manual:
|
|
10810
10891
|
satlas verify <slug> --file fix --scenario REQ-DOMINIO-001-S1 --method manual --result pass --by "<nombre>" --notes "<c\xF3mo se comprob\xF3>"
|
|
10811
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}}
|
|
10812
10931
|
`
|
|
10813
10932
|
};
|
|
10814
10933
|
var EN = {
|
|
@@ -10934,6 +11053,44 @@ Cubre: REQ-DOMAIN-001
|
|
|
10934
11053
|
<!-- Register real evidence with:
|
|
10935
11054
|
satlas verify <slug> --file fix --scenario REQ-DOMAIN-001-S1 --command "<command>" --by "<name>"
|
|
10936
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}}
|
|
10937
11094
|
`
|
|
10938
11095
|
};
|
|
10939
11096
|
function templatesFor(language) {
|
|
@@ -13433,6 +13590,84 @@ async function upgradeAdvisory(root) {
|
|
|
13433
13590
|
}
|
|
13434
13591
|
return { plan, diagnostics };
|
|
13435
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
|
+
}
|
|
13436
13671
|
var SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
|
|
13437
13672
|
var SARIF_VERSION = "2.1.0";
|
|
13438
13673
|
var TOOL_NAME = "SpecAtlas";
|
|
@@ -13470,7 +13705,7 @@ function resultOf(diagnostic, root) {
|
|
|
13470
13705
|
message: { text: diagnostic.message }
|
|
13471
13706
|
};
|
|
13472
13707
|
if (diagnostic.path) {
|
|
13473
|
-
const rel =
|
|
13708
|
+
const rel = path20.relative(root, diagnostic.path);
|
|
13474
13709
|
if (rel !== "") {
|
|
13475
13710
|
const physicalLocation = {
|
|
13476
13711
|
artifactLocation: { uri: toPosix(rel) },
|
|
@@ -13525,7 +13760,7 @@ async function runDoctor(root) {
|
|
|
13525
13760
|
const approvals = await loadApprovals(workspace.sddDir);
|
|
13526
13761
|
findings.push(...approvals.diagnostics);
|
|
13527
13762
|
for (const change of workspace.changes) {
|
|
13528
|
-
const deltaPath =
|
|
13763
|
+
const deltaPath = path21.join(change.dir, "spec.md");
|
|
13529
13764
|
const deltaContent = await readTextIfExists(deltaPath);
|
|
13530
13765
|
const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
|
|
13531
13766
|
if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
|
|
@@ -13547,7 +13782,7 @@ async function runDoctor(root) {
|
|
|
13547
13782
|
}
|
|
13548
13783
|
for (const override of change.meta?.overrides ?? []) {
|
|
13549
13784
|
if (!override.reason.trim() || !override.by.trim()) {
|
|
13550
|
-
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") }));
|
|
13551
13786
|
}
|
|
13552
13787
|
}
|
|
13553
13788
|
}
|
|
@@ -13579,7 +13814,7 @@ function livingRequirementsMap(specs) {
|
|
|
13579
13814
|
return map;
|
|
13580
13815
|
}
|
|
13581
13816
|
async function runCiGate(opts) {
|
|
13582
|
-
const root =
|
|
13817
|
+
const root = path22.resolve(opts.root);
|
|
13583
13818
|
const { workspace, config } = await loadWorkspace(root);
|
|
13584
13819
|
const diagnostics = [];
|
|
13585
13820
|
const checks = [];
|
|
@@ -13590,7 +13825,7 @@ async function runCiGate(opts) {
|
|
|
13590
13825
|
let changesErrors = 0;
|
|
13591
13826
|
let changesWarnings = 0;
|
|
13592
13827
|
for (const change of workspace.changes) {
|
|
13593
|
-
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 }) : [];
|
|
13594
13829
|
const trace = checkTrace({
|
|
13595
13830
|
specs: workspace.specs,
|
|
13596
13831
|
change,
|
|
@@ -13699,6 +13934,8 @@ var CATALOG = [
|
|
|
13699
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]" },
|
|
13700
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]" },
|
|
13701
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]" },
|
|
13702
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]" },
|
|
13703
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' },
|
|
13704
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>" },
|
|
@@ -13750,6 +13987,13 @@ var ES2 = {
|
|
|
13750
13987
|
"new.done": "Cambio creado. Siguiente: escribe la spec funcional.",
|
|
13751
13988
|
"upgrade.title": "Actualizar el estado del proyecto",
|
|
13752
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",
|
|
13753
13997
|
"summary.findings": "hallazgos",
|
|
13754
13998
|
"label.errors": "errores",
|
|
13755
13999
|
"label.warnings": "avisos",
|
|
@@ -13785,6 +14029,13 @@ var EN2 = {
|
|
|
13785
14029
|
"new.done": "Change created. Next: write the business spec.",
|
|
13786
14030
|
"upgrade.title": "Upgrade project state",
|
|
13787
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",
|
|
13788
14039
|
"summary.findings": "findings",
|
|
13789
14040
|
"label.errors": "errors",
|
|
13790
14041
|
"label.warnings": "warnings",
|
|
@@ -13812,26 +14063,26 @@ function cliVersion() {
|
|
|
13812
14063
|
}
|
|
13813
14064
|
|
|
13814
14065
|
// src/commands/adapters.ts
|
|
13815
|
-
import
|
|
14066
|
+
import path26 from "path";
|
|
13816
14067
|
|
|
13817
14068
|
// ../adapters/dist/index.js
|
|
13818
|
-
import path22 from "path";
|
|
13819
|
-
import { stringify as stringifyYaml7 } from "yaml";
|
|
13820
14069
|
import path23 from "path";
|
|
14070
|
+
import { stringify as stringifyYaml7 } from "yaml";
|
|
14071
|
+
import path24 from "path";
|
|
13821
14072
|
async function loadWorkflow(workflowDir) {
|
|
13822
|
-
const phasesDir =
|
|
13823
|
-
const snippetsDir =
|
|
14073
|
+
const phasesDir = path23.join(workflowDir, "phases");
|
|
14074
|
+
const snippetsDir = path23.join(workflowDir, "snippets");
|
|
13824
14075
|
const phases = [];
|
|
13825
14076
|
const snippets = /* @__PURE__ */ new Map();
|
|
13826
14077
|
const hashParts = [];
|
|
13827
14078
|
for (const entry of (await listDir(phasesDir)).sort()) {
|
|
13828
14079
|
if (!entry.endsWith(".md")) continue;
|
|
13829
|
-
const filePath =
|
|
14080
|
+
const filePath = path23.join(phasesDir, entry);
|
|
13830
14081
|
const raw = await readText(filePath);
|
|
13831
14082
|
hashParts.push(raw);
|
|
13832
14083
|
const fm = parseFrontmatter(raw, filePath);
|
|
13833
14084
|
const data = fm.data;
|
|
13834
|
-
const id = typeof data["id"] === "string" ? data["id"] :
|
|
14085
|
+
const id = typeof data["id"] === "string" ? data["id"] : path23.basename(entry, ".md");
|
|
13835
14086
|
phases.push({
|
|
13836
14087
|
id,
|
|
13837
14088
|
title: typeof data["title"] === "string" ? data["title"] : id,
|
|
@@ -13845,9 +14096,9 @@ async function loadWorkflow(workflowDir) {
|
|
|
13845
14096
|
}
|
|
13846
14097
|
for (const entry of (await listDir(snippetsDir)).sort()) {
|
|
13847
14098
|
if (!entry.endsWith(".md")) continue;
|
|
13848
|
-
const raw = await readTextIfExists(
|
|
14099
|
+
const raw = await readTextIfExists(path23.join(snippetsDir, entry)) ?? "";
|
|
13849
14100
|
hashParts.push(raw);
|
|
13850
|
-
snippets.set(
|
|
14101
|
+
snippets.set(path23.basename(entry, ".md"), raw.trim());
|
|
13851
14102
|
}
|
|
13852
14103
|
return { phases: phases.sort((a, b) => a.id.localeCompare(b.id)), snippets, sourceHash: sha256(hashParts.join("\n---\n")) };
|
|
13853
14104
|
}
|
|
@@ -14029,11 +14280,11 @@ Reglas: la spec es funcional y de negocio (sin tecnolog\xEDa); la trazabilidad e
|
|
|
14029
14280
|
function tomlString(value) {
|
|
14030
14281
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
14031
14282
|
}
|
|
14032
|
-
var MANIFEST_REL =
|
|
14283
|
+
var MANIFEST_REL = path24.join(".sdd", ".generated", "manifest.json");
|
|
14033
14284
|
var BEGIN = "<!-- BEGIN specatlas -->";
|
|
14034
14285
|
var END = "<!-- END specatlas -->";
|
|
14035
14286
|
async function compileTargets(opts) {
|
|
14036
|
-
const root =
|
|
14287
|
+
const root = path24.resolve(opts.root);
|
|
14037
14288
|
const language = opts.language ?? "es";
|
|
14038
14289
|
const diagnostics = [];
|
|
14039
14290
|
const sources = await loadWorkflow(opts.workflowDir);
|
|
@@ -14041,7 +14292,7 @@ async function compileTargets(opts) {
|
|
|
14041
14292
|
diagnostics.push({
|
|
14042
14293
|
code: "ATLAS-ADAPTERS-002",
|
|
14043
14294
|
severity: "error",
|
|
14044
|
-
message: `No hay fases en ${
|
|
14295
|
+
message: `No hay fases en ${path24.join(opts.workflowDir, "phases")}`,
|
|
14045
14296
|
suggestion: "Revisa la carpeta workflow/phases del proyecto"
|
|
14046
14297
|
});
|
|
14047
14298
|
}
|
|
@@ -14053,7 +14304,7 @@ async function compileTargets(opts) {
|
|
|
14053
14304
|
}
|
|
14054
14305
|
}
|
|
14055
14306
|
const compiled = [...deduped.values()];
|
|
14056
|
-
const manifestPath =
|
|
14307
|
+
const manifestPath = path24.join(root, MANIFEST_REL);
|
|
14057
14308
|
const previous = await readManifest(manifestPath);
|
|
14058
14309
|
const previousHashes = /* @__PURE__ */ new Map();
|
|
14059
14310
|
for (const files2 of Object.values(previous?.targets ?? {})) {
|
|
@@ -14064,7 +14315,7 @@ async function compileTargets(opts) {
|
|
|
14064
14315
|
const stale = [];
|
|
14065
14316
|
const missing = [];
|
|
14066
14317
|
for (const file of compiled) {
|
|
14067
|
-
const abs =
|
|
14318
|
+
const abs = path24.join(root, file.path);
|
|
14068
14319
|
const finalContent = file.path === "AGENTS.md" ? mergeAgentsBlock(await readTextIfExists(abs) ?? "", file.content) : file.content;
|
|
14069
14320
|
const hash = artifactHash(finalContent);
|
|
14070
14321
|
const existing = await readTextIfExists(abs);
|
|
@@ -14080,7 +14331,7 @@ async function compileTargets(opts) {
|
|
|
14080
14331
|
stale.push(file.path);
|
|
14081
14332
|
}
|
|
14082
14333
|
if (!opts.check && status !== "unchanged") {
|
|
14083
|
-
await ensureDir(
|
|
14334
|
+
await ensureDir(path24.dirname(abs));
|
|
14084
14335
|
await writeText(abs, finalContent);
|
|
14085
14336
|
written.push(file.path);
|
|
14086
14337
|
}
|
|
@@ -14134,7 +14385,7 @@ async function readManifest(manifestPath) {
|
|
|
14134
14385
|
}
|
|
14135
14386
|
}
|
|
14136
14387
|
async function checkAdapters(opts) {
|
|
14137
|
-
const manifestPath =
|
|
14388
|
+
const manifestPath = path24.join(path24.resolve(opts.root), MANIFEST_REL);
|
|
14138
14389
|
const manifestRaw = await readTextIfExists(manifestPath);
|
|
14139
14390
|
if (manifestRaw === void 0) return { ok: false, manifest: false, stale: [], missing: [], orphaned: [] };
|
|
14140
14391
|
const manifest = await readManifest(manifestPath);
|
|
@@ -14151,7 +14402,7 @@ async function checkAdapters(opts) {
|
|
|
14151
14402
|
}
|
|
14152
14403
|
|
|
14153
14404
|
// src/paths.ts
|
|
14154
|
-
import
|
|
14405
|
+
import path25 from "path";
|
|
14155
14406
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14156
14407
|
async function resolveProfilesDir() {
|
|
14157
14408
|
const env = process.env["SPECATLAS_PROFILES_DIR"];
|
|
@@ -14161,16 +14412,16 @@ async function resolveProfilesDir() {
|
|
|
14161
14412
|
async function resolveWorkflowDir() {
|
|
14162
14413
|
const env = process.env["SPECATLAS_WORKFLOW_DIR"];
|
|
14163
14414
|
if (env && await exists(env)) return env;
|
|
14164
|
-
return walkUpFor(
|
|
14415
|
+
return walkUpFor(path25.join("workflow", "phases"));
|
|
14165
14416
|
}
|
|
14166
14417
|
async function walkUpFor(relative) {
|
|
14167
|
-
let dir =
|
|
14418
|
+
let dir = path25.dirname(fileURLToPath2(import.meta.url));
|
|
14168
14419
|
for (let i = 0; i < 8; i += 1) {
|
|
14169
|
-
const candidate =
|
|
14420
|
+
const candidate = path25.join(dir, relative);
|
|
14170
14421
|
if (await exists(candidate)) {
|
|
14171
|
-
return relative.includes(
|
|
14422
|
+
return relative.includes(path25.sep) ? path25.dirname(candidate) : candidate;
|
|
14172
14423
|
}
|
|
14173
|
-
const parent =
|
|
14424
|
+
const parent = path25.dirname(dir);
|
|
14174
14425
|
if (parent === dir) break;
|
|
14175
14426
|
dir = parent;
|
|
14176
14427
|
}
|
|
@@ -14248,7 +14499,7 @@ async function runAdapters(ctx) {
|
|
|
14248
14499
|
for (const [target, count2] of byTarget) lines.push(` ${target}: ${count2} archivo(s)`);
|
|
14249
14500
|
if (report.written.length > 0) {
|
|
14250
14501
|
lines.push("");
|
|
14251
|
-
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))}`);
|
|
14252
14503
|
} else {
|
|
14253
14504
|
lines.push("");
|
|
14254
14505
|
lines.push("Sin cambios: los adaptadores ya estaban al d\xEDa.");
|
|
@@ -14259,7 +14510,7 @@ async function runAdapters(ctx) {
|
|
|
14259
14510
|
}
|
|
14260
14511
|
|
|
14261
14512
|
// src/commands/adopt.ts
|
|
14262
|
-
import
|
|
14513
|
+
import path27 from "path";
|
|
14263
14514
|
async function runAdopt(ctx) {
|
|
14264
14515
|
const { root } = await requireWorkspace(ctx);
|
|
14265
14516
|
const domainsFlag = flagString(ctx.flags, "domains") ?? flagString(ctx.flags, "domain");
|
|
@@ -14279,11 +14530,11 @@ async function runAdopt(ctx) {
|
|
|
14279
14530
|
}
|
|
14280
14531
|
if (result.createdSpecs.length > 0) {
|
|
14281
14532
|
lines.push("");
|
|
14282
|
-
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)}`);
|
|
14283
14534
|
}
|
|
14284
14535
|
if (result.reportPath && !result.dryRun) {
|
|
14285
14536
|
lines.push("");
|
|
14286
|
-
lines.push(` Informe: ${
|
|
14537
|
+
lines.push(` Informe: ${path27.relative(ctx.cwd, result.reportPath)}`);
|
|
14287
14538
|
}
|
|
14288
14539
|
lines.push("");
|
|
14289
14540
|
lines.push("Siguiente: fase /satlas-adopt por dominio (requisitos AS-IS) y luego:");
|
|
@@ -14296,8 +14547,8 @@ async function runAdopt(ctx) {
|
|
|
14296
14547
|
diagnostics: result.diagnostics,
|
|
14297
14548
|
data: {
|
|
14298
14549
|
domains: result.domains.map((d) => ({ name: d.name, files: d.files, existingSpec: d.existingSpec })),
|
|
14299
|
-
createdSpecs: result.createdSpecs.map((s) =>
|
|
14300
|
-
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,
|
|
14301
14552
|
stack: result.stack,
|
|
14302
14553
|
dryRun: result.dryRun
|
|
14303
14554
|
},
|
|
@@ -14306,7 +14557,7 @@ async function runAdopt(ctx) {
|
|
|
14306
14557
|
}
|
|
14307
14558
|
|
|
14308
14559
|
// src/commands/analyze.ts
|
|
14309
|
-
import
|
|
14560
|
+
import path28 from "path";
|
|
14310
14561
|
async function runAnalyzeCommand(ctx) {
|
|
14311
14562
|
const { root } = await requireWorkspace(ctx);
|
|
14312
14563
|
const slug = ctx.positionals[0];
|
|
@@ -14317,23 +14568,23 @@ async function runAnalyzeCommand(ctx) {
|
|
|
14317
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`];
|
|
14318
14569
|
if (result.waves) lines.push(` olas: ${result.waves.blocks} bloque(s), ${result.waves.waves} ola(s), ${result.waves.tasks} tarea(s)`);
|
|
14319
14570
|
if (result.mockups) lines.push(` mockups: ${result.mockups.screens} pantalla(s)${result.mockups.stale ? " (desactualizados)" : ""}`);
|
|
14320
|
-
if (result.path) lines.push("", ` informe: ${
|
|
14571
|
+
if (result.path) lines.push("", ` informe: ${path28.relative(ctx.cwd, result.path)}`);
|
|
14321
14572
|
for (const finding of result.findings) {
|
|
14322
14573
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}`);
|
|
14323
14574
|
}
|
|
14324
14575
|
return {
|
|
14325
14576
|
exitCode: result.status === "blocked" ? 1 : 0,
|
|
14326
14577
|
diagnostics: result.findings,
|
|
14327
|
-
data: result.path ? { ...result, path:
|
|
14578
|
+
data: result.path ? { ...result, path: path28.relative(ctx.cwd, result.path) } : result,
|
|
14328
14579
|
text: lines
|
|
14329
14580
|
};
|
|
14330
14581
|
}
|
|
14331
14582
|
|
|
14332
14583
|
// src/commands/approve.ts
|
|
14333
|
-
import
|
|
14584
|
+
import path30 from "path";
|
|
14334
14585
|
|
|
14335
14586
|
// src/commands/issue.ts
|
|
14336
|
-
import
|
|
14587
|
+
import path29 from "path";
|
|
14337
14588
|
async function runIssue(ctx) {
|
|
14338
14589
|
const action = ctx.positionals[0] ?? "status";
|
|
14339
14590
|
const slug = ctx.positionals[1];
|
|
@@ -14361,7 +14612,7 @@ async function runIssue(ctx) {
|
|
|
14361
14612
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-ISSUE-000", severity: "error", message: "Falta el slug: satlas issue status <slug>" }] };
|
|
14362
14613
|
}
|
|
14363
14614
|
const change = await loadChange(root, slug);
|
|
14364
|
-
const { config } = await loadConfig(
|
|
14615
|
+
const { config } = await loadConfig(path29.join(root, ".sdd"));
|
|
14365
14616
|
const tracker = change.meta?.tracker;
|
|
14366
14617
|
const lines = [`Issue de ${slug}`, ""];
|
|
14367
14618
|
if (!tracker || tracker.provider !== "github") {
|
|
@@ -14432,7 +14683,7 @@ async function runApprove(ctx) {
|
|
|
14432
14683
|
};
|
|
14433
14684
|
}
|
|
14434
14685
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
14435
|
-
const artifact = target.includes("/") || target.includes("\\") ? target :
|
|
14686
|
+
const artifact = target.includes("/") || target.includes("\\") ? target : path30.join("changes", target, "spec.md");
|
|
14436
14687
|
const channelFlag = flagString(ctx.flags, "channel");
|
|
14437
14688
|
const channel = channelFlag === "presentation" || channelFlag === "editor" || channelFlag === "pr" || channelFlag === "tracker" ? channelFlag : "cli";
|
|
14438
14689
|
const change = workspace.changes.find(
|
|
@@ -14473,13 +14724,13 @@ async function runApprove(ctx) {
|
|
|
14473
14724
|
return {
|
|
14474
14725
|
exitCode: hasErrors2 ? 1 : 0,
|
|
14475
14726
|
diagnostics: result.diagnostics,
|
|
14476
|
-
data: result.approval ? { ...result.approval, file:
|
|
14727
|
+
data: result.approval ? { ...result.approval, file: path30.relative(ctx.cwd, result.file) } : void 0,
|
|
14477
14728
|
text: lines
|
|
14478
14729
|
};
|
|
14479
14730
|
}
|
|
14480
14731
|
|
|
14481
14732
|
// src/commands/archive.ts
|
|
14482
|
-
import
|
|
14733
|
+
import path31 from "path";
|
|
14483
14734
|
async function runArchive(ctx) {
|
|
14484
14735
|
const slug = ctx.positionals[0];
|
|
14485
14736
|
if (!slug) {
|
|
@@ -14516,7 +14767,7 @@ async function runArchive(ctx) {
|
|
|
14516
14767
|
} else {
|
|
14517
14768
|
lines.push("El delta no contiene operaciones (ADDED/MODIFIED/REMOVED/RENAMED).");
|
|
14518
14769
|
}
|
|
14519
|
-
if (result.archivedTo) lines.push(` archivado en: ${
|
|
14770
|
+
if (result.archivedTo) lines.push(` archivado en: ${path31.relative(ctx.cwd, result.archivedTo)}`);
|
|
14520
14771
|
const hasErrors2 = result.diagnostics.some((d) => d.severity === "error");
|
|
14521
14772
|
return {
|
|
14522
14773
|
exitCode: hasErrors2 ? 1 : 0,
|
|
@@ -14527,7 +14778,7 @@ async function runArchive(ctx) {
|
|
|
14527
14778
|
}
|
|
14528
14779
|
|
|
14529
14780
|
// src/commands/ci.ts
|
|
14530
|
-
import
|
|
14781
|
+
import path32 from "path";
|
|
14531
14782
|
async function runCi(ctx) {
|
|
14532
14783
|
const { root, config } = await requireWorkspace(ctx);
|
|
14533
14784
|
const strict = flagBool(ctx.flags, "strict");
|
|
@@ -14553,10 +14804,10 @@ async function runCi(ctx) {
|
|
|
14553
14804
|
const lines = ["CI de SpecAtlas", ""];
|
|
14554
14805
|
let sarifWritten;
|
|
14555
14806
|
if (sarifPath) {
|
|
14556
|
-
const target =
|
|
14807
|
+
const target = path32.resolve(ctx.cwd, sarifPath);
|
|
14557
14808
|
try {
|
|
14558
14809
|
await writeText(target, toSarifText({ diagnostics: gate.diagnostics, root, version: cliVersion(), failed: gate.failed }));
|
|
14559
|
-
sarifWritten =
|
|
14810
|
+
sarifWritten = path32.relative(ctx.cwd, target);
|
|
14560
14811
|
} catch (err) {
|
|
14561
14812
|
diagnostics.push({
|
|
14562
14813
|
code: "ATLAS-CI-SARIF-001",
|
|
@@ -14590,13 +14841,83 @@ async function runCi(ctx) {
|
|
|
14590
14841
|
};
|
|
14591
14842
|
}
|
|
14592
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
|
+
|
|
14593
14914
|
// src/commands/profile.ts
|
|
14594
|
-
import
|
|
14915
|
+
import path34 from "path";
|
|
14595
14916
|
async function runProfile(ctx) {
|
|
14596
14917
|
const action = ctx.positionals[0] ?? "detect";
|
|
14597
14918
|
const { root, workspace } = await requireWorkspace(ctx);
|
|
14598
14919
|
const officialDir = await resolveProfilesDir();
|
|
14599
|
-
const customDir =
|
|
14920
|
+
const customDir = path34.join(workspace.sddDir, "profiles", "custom");
|
|
14600
14921
|
const profiles = [...officialDir ? await loadProfilesFromDir(officialDir) : [], ...await loadProfilesFromDir(customDir)];
|
|
14601
14922
|
if (action === "detect") {
|
|
14602
14923
|
const result = await detectProfiles(root, profiles);
|
|
@@ -14625,7 +14946,7 @@ async function runProfile(ctx) {
|
|
|
14625
14946
|
if (!name) {
|
|
14626
14947
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-001", severity: "error", message: "Falta el nombre del perfil: satlas profile create <nombre>" }] };
|
|
14627
14948
|
}
|
|
14628
|
-
const file =
|
|
14949
|
+
const file = path34.join(customDir, `${name}.yaml`);
|
|
14629
14950
|
if (await exists(file)) {
|
|
14630
14951
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-PROFILE-002", severity: "error", message: `El perfil ya existe: ${file}` }] };
|
|
14631
14952
|
}
|
|
@@ -14634,7 +14955,7 @@ async function runProfile(ctx) {
|
|
|
14634
14955
|
exitCode: 0,
|
|
14635
14956
|
diagnostics: [],
|
|
14636
14957
|
data: { file },
|
|
14637
|
-
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."]
|
|
14638
14959
|
};
|
|
14639
14960
|
}
|
|
14640
14961
|
return {
|
|
@@ -14643,7 +14964,7 @@ async function runProfile(ctx) {
|
|
|
14643
14964
|
};
|
|
14644
14965
|
}
|
|
14645
14966
|
async function isInside(dir, name) {
|
|
14646
|
-
return exists(
|
|
14967
|
+
return exists(path34.join(dir, `${name}.yaml`));
|
|
14647
14968
|
}
|
|
14648
14969
|
function profileTemplate(name) {
|
|
14649
14970
|
return `name: ${name}
|
|
@@ -14671,7 +14992,7 @@ assumptions: []
|
|
|
14671
14992
|
}
|
|
14672
14993
|
|
|
14673
14994
|
// src/commands/doctor.ts
|
|
14674
|
-
import
|
|
14995
|
+
import path35 from "path";
|
|
14675
14996
|
async function runDoctorCommand(ctx) {
|
|
14676
14997
|
const { root, config } = await requireWorkspace(ctx);
|
|
14677
14998
|
const report = await runDoctor(root);
|
|
@@ -14696,7 +15017,7 @@ async function runDoctorCommand(ctx) {
|
|
|
14696
15017
|
lines.push("Sin hallazgos. El workspace est\xE1 sano.");
|
|
14697
15018
|
} else {
|
|
14698
15019
|
for (const finding of report.findings) {
|
|
14699
|
-
const location = finding.path ? ` ${
|
|
15020
|
+
const location = finding.path ? ` ${path35.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""}` : "";
|
|
14700
15021
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${location} \u2014 ${finding.message}`);
|
|
14701
15022
|
if (finding.suggestion) lines.push(` \u21B3 ${finding.suggestion}`);
|
|
14702
15023
|
}
|
|
@@ -14770,7 +15091,7 @@ async function runHelp(ctx, commandName) {
|
|
|
14770
15091
|
}
|
|
14771
15092
|
|
|
14772
15093
|
// src/commands/init.ts
|
|
14773
|
-
import
|
|
15094
|
+
import path36 from "path";
|
|
14774
15095
|
async function runInit(ctx) {
|
|
14775
15096
|
const languageFlag = flagString(ctx.flags, "language");
|
|
14776
15097
|
const language = languageFlag === "en" ? "en" : languageFlag === "es" ? "es" : void 0;
|
|
@@ -14817,11 +15138,11 @@ async function runInit(ctx) {
|
|
|
14817
15138
|
const lines = [msg("init.title", ctx.language), ""];
|
|
14818
15139
|
if (result.created.length > 0) {
|
|
14819
15140
|
lines.push(msg("init.done", ctx.language));
|
|
14820
|
-
for (const file of result.created) lines.push(` + ${
|
|
15141
|
+
for (const file of result.created) lines.push(` + ${path36.relative(ctx.cwd, file)}`);
|
|
14821
15142
|
if (compiled.length > 0) {
|
|
14822
15143
|
lines.push("");
|
|
14823
15144
|
lines.push("Adaptadores compilados:");
|
|
14824
|
-
for (const file of compiled) lines.push(` + ${
|
|
15145
|
+
for (const file of compiled) lines.push(` + ${path36.relative(ctx.cwd, path36.join(ctx.cwd, file))}`);
|
|
14825
15146
|
}
|
|
14826
15147
|
lines.push("");
|
|
14827
15148
|
lines.push("Siguiente: crea un cambio con `satlas new <slug>` y escribe la spec funcional.");
|
|
@@ -14831,8 +15152,8 @@ async function runInit(ctx) {
|
|
|
14831
15152
|
exitCode: hasErrors2 ? 1 : 0,
|
|
14832
15153
|
diagnostics: result.diagnostics,
|
|
14833
15154
|
data: {
|
|
14834
|
-
sddDir:
|
|
14835
|
-
created: result.created.map((f) =>
|
|
15155
|
+
sddDir: path36.relative(ctx.cwd, result.sddDir),
|
|
15156
|
+
created: result.created.map((f) => path36.relative(ctx.cwd, f)),
|
|
14836
15157
|
detected: result.detected?.matches.map((m) => ({ name: m.name, score: m.score })),
|
|
14837
15158
|
best: result.detected?.best?.name
|
|
14838
15159
|
},
|
|
@@ -14873,7 +15194,7 @@ async function runMetrics(ctx) {
|
|
|
14873
15194
|
}
|
|
14874
15195
|
|
|
14875
15196
|
// src/commands/mockup.ts
|
|
14876
|
-
import
|
|
15197
|
+
import path37 from "path";
|
|
14877
15198
|
async function runMockup(ctx) {
|
|
14878
15199
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
14879
15200
|
const slug = ctx.positionals[0];
|
|
@@ -14922,17 +15243,17 @@ async function runMockup(ctx) {
|
|
|
14922
15243
|
` pantallas: ${plan.screens.length}`,
|
|
14923
15244
|
...plan.screens.map((s) => ` - ${s.id}: ${s.title} (${s.illustrates.length} escenario(s))`),
|
|
14924
15245
|
"",
|
|
14925
|
-
` plan: ${
|
|
14926
|
-
` manifiesto: ${
|
|
15246
|
+
` plan: ${path37.relative(ctx.cwd, planFile)}`,
|
|
15247
|
+
` manifiesto: ${path37.relative(ctx.cwd, manifestFile)}`,
|
|
14927
15248
|
"",
|
|
14928
15249
|
"Siguiente: genera el HTML de cada pantalla (fase /satlas-mockup) y valida con `satlas mockup " + slug + " --check`."
|
|
14929
15250
|
];
|
|
14930
|
-
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 };
|
|
14931
15252
|
}
|
|
14932
15253
|
|
|
14933
15254
|
// src/mcp/server.ts
|
|
14934
15255
|
import { promises as fs3 } from "fs";
|
|
14935
|
-
import
|
|
15256
|
+
import path40 from "path";
|
|
14936
15257
|
import { createInterface } from "readline/promises";
|
|
14937
15258
|
|
|
14938
15259
|
// src/mcp/protocol.ts
|
|
@@ -15021,12 +15342,12 @@ async function runAtlasFixes(_args, host) {
|
|
|
15021
15342
|
}
|
|
15022
15343
|
|
|
15023
15344
|
// src/mcp/tools/glossary.ts
|
|
15024
|
-
import
|
|
15345
|
+
import path38 from "path";
|
|
15025
15346
|
async function runAtlasGlossary(_args, host) {
|
|
15026
15347
|
const resolved = await host.getWorkspace();
|
|
15027
15348
|
if (!resolved) return noWorkspaceResult();
|
|
15028
15349
|
const { root, config } = resolved;
|
|
15029
|
-
const glossaryPath =
|
|
15350
|
+
const glossaryPath = path38.resolve(root, config.spec.glossary);
|
|
15030
15351
|
const raw = await readTextIfExists(glossaryPath);
|
|
15031
15352
|
if (raw === void 0) {
|
|
15032
15353
|
return jsonResult({
|
|
@@ -15072,7 +15393,7 @@ async function runAtlasImpact(args, host) {
|
|
|
15072
15393
|
}
|
|
15073
15394
|
|
|
15074
15395
|
// src/evaluate.ts
|
|
15075
|
-
import
|
|
15396
|
+
import path39 from "path";
|
|
15076
15397
|
function livingRequirementsMap2(specs) {
|
|
15077
15398
|
const map = /* @__PURE__ */ new Map();
|
|
15078
15399
|
for (const spec of specs) {
|
|
@@ -15081,7 +15402,7 @@ function livingRequirementsMap2(specs) {
|
|
|
15081
15402
|
return map;
|
|
15082
15403
|
}
|
|
15083
15404
|
async function evaluateChange(workspace, config, change, approvals) {
|
|
15084
|
-
const deltaPath =
|
|
15405
|
+
const deltaPath = path39.join(change.dir, "spec.md");
|
|
15085
15406
|
const deltaContent = await readTextIfExists(deltaPath);
|
|
15086
15407
|
const approval = verifyApproval(change, approvals, config, deltaContent ?? void 0);
|
|
15087
15408
|
const living = livingRequirementsMap2(workspace.specs);
|
|
@@ -15100,7 +15421,8 @@ async function evaluateChange(workspace, config, change, approvals) {
|
|
|
15100
15421
|
blockingFindings: change.delta ? blocking : 0,
|
|
15101
15422
|
...mockupsAreReady !== void 0 ? { mockupsReady: mockupsAreReady } : {}
|
|
15102
15423
|
});
|
|
15103
|
-
|
|
15424
|
+
const phaseAdvisories = [...clarifyAdvisory(change, config), ...docsAdvisory(change, config)];
|
|
15425
|
+
return { change, approval, lintFindings: [...lintFindings, ...phaseAdvisories], trace, blocking, state };
|
|
15104
15426
|
}
|
|
15105
15427
|
|
|
15106
15428
|
// src/mcp/tools/next.ts
|
|
@@ -15353,8 +15675,8 @@ var WorkspaceCache = class {
|
|
|
15353
15675
|
}
|
|
15354
15676
|
};
|
|
15355
15677
|
async function workspaceStamp(root) {
|
|
15356
|
-
const sddDir =
|
|
15357
|
-
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")];
|
|
15358
15680
|
let stamp = 0;
|
|
15359
15681
|
for (const marker of markers) {
|
|
15360
15682
|
try {
|
|
@@ -15372,7 +15694,7 @@ async function resolveMcpWorkspace(cwd, cache) {
|
|
|
15372
15694
|
const cached2 = cache.get(root);
|
|
15373
15695
|
if (cached2 && cached2.mtimeMs === stamp) return cached2.value;
|
|
15374
15696
|
const { workspace, config } = await loadWorkspace(root);
|
|
15375
|
-
const approvals = await loadApprovals(
|
|
15697
|
+
const approvals = await loadApprovals(path40.join(root, ".sdd"));
|
|
15376
15698
|
const value = { root, workspace, config, approvals: approvals.byArtifact };
|
|
15377
15699
|
cache.set(root, stamp, value);
|
|
15378
15700
|
return value;
|
|
@@ -15464,7 +15786,7 @@ async function runMcp(ctx) {
|
|
|
15464
15786
|
}
|
|
15465
15787
|
|
|
15466
15788
|
// src/commands/packs.ts
|
|
15467
|
-
import
|
|
15789
|
+
import path41 from "path";
|
|
15468
15790
|
async function runPacks(ctx) {
|
|
15469
15791
|
const { root, config, workspace } = await requireWorkspace(ctx);
|
|
15470
15792
|
const slug = flagString(ctx.flags, "check");
|
|
@@ -15522,14 +15844,14 @@ async function runPacks(ctx) {
|
|
|
15522
15844
|
data: {
|
|
15523
15845
|
slug,
|
|
15524
15846
|
packs: evaluations.map((evaluation) => ({ id: evaluation.pack.id, status: evaluation.status, passed: evaluation.passed, failed: evaluation.failed, results: evaluation.results })),
|
|
15525
|
-
analyzePath:
|
|
15847
|
+
analyzePath: path41.posix.join(".sdd", "changes", slug, "analyze.md")
|
|
15526
15848
|
},
|
|
15527
15849
|
text: lines
|
|
15528
15850
|
};
|
|
15529
15851
|
}
|
|
15530
15852
|
|
|
15531
15853
|
// src/commands/new.ts
|
|
15532
|
-
import
|
|
15854
|
+
import path42 from "path";
|
|
15533
15855
|
async function runNew(ctx) {
|
|
15534
15856
|
const slug = ctx.positionals[0];
|
|
15535
15857
|
if (!slug) {
|
|
@@ -15555,14 +15877,14 @@ async function runNew(ctx) {
|
|
|
15555
15877
|
msg("new.title", ctx.language),
|
|
15556
15878
|
"",
|
|
15557
15879
|
msg("new.done", ctx.language),
|
|
15558
|
-
...result.files.map((f) => ` + ${
|
|
15880
|
+
...result.files.map((f) => ` + ${path42.relative(ctx.cwd, f)}`),
|
|
15559
15881
|
"",
|
|
15560
15882
|
`Siguiente: /satlas.specify ${result.slug} \u2014 escribe la especificaci\xF3n 100% funcional y de negocio.`
|
|
15561
15883
|
];
|
|
15562
15884
|
return {
|
|
15563
15885
|
exitCode: hasErrors2 ? 1 : 0,
|
|
15564
15886
|
diagnostics: result.diagnostics,
|
|
15565
|
-
data: { slug: result.slug, files: result.files.map((f) =>
|
|
15887
|
+
data: { slug: result.slug, files: result.files.map((f) => path42.relative(ctx.cwd, f)) },
|
|
15566
15888
|
text: lines
|
|
15567
15889
|
};
|
|
15568
15890
|
}
|
|
@@ -15597,7 +15919,7 @@ async function runNext(ctx) {
|
|
|
15597
15919
|
}
|
|
15598
15920
|
|
|
15599
15921
|
// src/commands/present.ts
|
|
15600
|
-
import
|
|
15922
|
+
import path43 from "path";
|
|
15601
15923
|
async function runPresentCommand(ctx) {
|
|
15602
15924
|
const { root } = await requireWorkspace(ctx);
|
|
15603
15925
|
const slug = ctx.positionals[0];
|
|
@@ -15609,7 +15931,7 @@ async function runPresentCommand(ctx) {
|
|
|
15609
15931
|
if (result.path) {
|
|
15610
15932
|
lines.push("Propuesta generada");
|
|
15611
15933
|
lines.push("");
|
|
15612
|
-
lines.push(` archivo: ${
|
|
15934
|
+
lines.push(` archivo: ${path43.relative(ctx.cwd, result.path)}`);
|
|
15613
15935
|
if (result.hash) lines.push(` hash de la spec: ${shortHash(result.hash)}`);
|
|
15614
15936
|
lines.push("");
|
|
15615
15937
|
lines.push("\xC1brela en el navegador y comp\xE1rtela con el stakeholder.");
|
|
@@ -15619,7 +15941,7 @@ async function runPresentCommand(ctx) {
|
|
|
15619
15941
|
return {
|
|
15620
15942
|
exitCode: hasErrors2 ? 1 : 0,
|
|
15621
15943
|
diagnostics: result.diagnostics,
|
|
15622
|
-
data: result.path ? { path:
|
|
15944
|
+
data: result.path ? { path: path43.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
|
|
15623
15945
|
text: lines
|
|
15624
15946
|
};
|
|
15625
15947
|
}
|
|
@@ -15767,7 +16089,7 @@ async function runStatus(ctx) {
|
|
|
15767
16089
|
}
|
|
15768
16090
|
|
|
15769
16091
|
// src/commands/trace.ts
|
|
15770
|
-
import
|
|
16092
|
+
import path44 from "path";
|
|
15771
16093
|
async function runTrace(ctx) {
|
|
15772
16094
|
const { workspace, config } = await requireWorkspace(ctx);
|
|
15773
16095
|
const slug = flagString(ctx.flags, "change");
|
|
@@ -15787,7 +16109,7 @@ async function runTrace(ctx) {
|
|
|
15787
16109
|
data.push({ slug: change.slug, nodes: result.graph.nodes.length, edges: result.graph.edges.length, summary: result.summary, findings: result.findings });
|
|
15788
16110
|
lines.push(` ${change.slug}: ${result.graph.nodes.length} nodos, ${result.graph.edges.length} aristas, ${errors} errores, ${result.summary.warnings} avisos`);
|
|
15789
16111
|
for (const finding of result.findings) {
|
|
15790
|
-
const location = finding.path ? ` (${
|
|
16112
|
+
const location = finding.path ? ` (${path44.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
|
|
15791
16113
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}${location}`);
|
|
15792
16114
|
}
|
|
15793
16115
|
}
|
|
@@ -15797,7 +16119,7 @@ async function runTrace(ctx) {
|
|
|
15797
16119
|
}
|
|
15798
16120
|
|
|
15799
16121
|
// src/commands/upgrade.ts
|
|
15800
|
-
import
|
|
16122
|
+
import path45 from "path";
|
|
15801
16123
|
async function runUpgrade(ctx) {
|
|
15802
16124
|
const { root } = await requireWorkspace(ctx);
|
|
15803
16125
|
const apply = flagBool(ctx.flags, "apply");
|
|
@@ -15851,7 +16173,7 @@ function renderPreview(plan, root, ctx) {
|
|
|
15851
16173
|
}
|
|
15852
16174
|
for (const item of plan.unreadable) {
|
|
15853
16175
|
lines.push(` Ilegible: "${item.artifact}" \u2014 ${item.reason}`);
|
|
15854
|
-
lines.push(` \u21B3 ${
|
|
16176
|
+
lines.push(` \u21B3 ${path45.relative(ctx.cwd, item.path)}`);
|
|
15855
16177
|
}
|
|
15856
16178
|
return { exitCode: 0, diagnostics: [], data: summarizable(plan), text: lines };
|
|
15857
16179
|
}
|
|
@@ -15914,7 +16236,7 @@ function renderRollback(report, ctx) {
|
|
|
15914
16236
|
}
|
|
15915
16237
|
|
|
15916
16238
|
// src/commands/validate.ts
|
|
15917
|
-
import
|
|
16239
|
+
import path46 from "path";
|
|
15918
16240
|
async function runValidate(ctx) {
|
|
15919
16241
|
const { root, workspace, config, approvals } = await requireWorkspace(ctx);
|
|
15920
16242
|
const slug = flagString(ctx.flags, "change");
|
|
@@ -15941,7 +16263,7 @@ async function runValidate(ctx) {
|
|
|
15941
16263
|
for (const finding of evaluation.lintFindings) {
|
|
15942
16264
|
lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${finding.line ? `:${finding.line}` : ""} \u2014 ${finding.message}`);
|
|
15943
16265
|
}
|
|
15944
|
-
lines.push(` archivo: ${
|
|
16266
|
+
lines.push(` archivo: ${path46.relative(ctx.cwd, path46.join(change.dir, "spec.md"))}`);
|
|
15945
16267
|
}
|
|
15946
16268
|
if (targets.length === 0) lines.push("Sin cambios activos.");
|
|
15947
16269
|
if (advisory && advisory.plan.pending.length > 0) {
|
|
@@ -15969,7 +16291,7 @@ async function runValidate(ctx) {
|
|
|
15969
16291
|
}
|
|
15970
16292
|
|
|
15971
16293
|
// src/commands/verify.ts
|
|
15972
|
-
import
|
|
16294
|
+
import path47 from "path";
|
|
15973
16295
|
async function runVerify(ctx) {
|
|
15974
16296
|
const { root, workspace } = await requireWorkspace(ctx);
|
|
15975
16297
|
const slug = ctx.positionals[0];
|
|
@@ -16012,7 +16334,7 @@ async function runVerify(ctx) {
|
|
|
16012
16334
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-VERIFY-001", severity: "error", message: "Falta --by <nombre> (o define meta.owner)" }] };
|
|
16013
16335
|
}
|
|
16014
16336
|
const profilesDir = await resolveProfilesDir();
|
|
16015
|
-
const profile = await loadActiveProfile(
|
|
16337
|
+
const profile = await loadActiveProfile(path47.join(root, ".sdd"), [profilesDir ?? "", path47.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
|
|
16016
16338
|
const record = await recordEvidence({
|
|
16017
16339
|
root,
|
|
16018
16340
|
slug,
|
|
@@ -16034,7 +16356,7 @@ async function runVerify(ctx) {
|
|
|
16034
16356
|
if (record.evidence.command) lines.push(` comando: ${record.evidence.command}`);
|
|
16035
16357
|
lines.push(` resultado: ${record.evidence.result}`);
|
|
16036
16358
|
if (record.evidence.outputHash) lines.push(` hash: ${record.evidence.outputHash}`);
|
|
16037
|
-
lines.push(` archivo: ${
|
|
16359
|
+
lines.push(` archivo: ${path47.relative(ctx.cwd, record.path)}`);
|
|
16038
16360
|
if (record.stdout) {
|
|
16039
16361
|
lines.push("");
|
|
16040
16362
|
lines.push(" salida (\xFAltimas l\xEDneas):");
|
|
@@ -16049,7 +16371,7 @@ async function runVerify(ctx) {
|
|
|
16049
16371
|
return {
|
|
16050
16372
|
exitCode: record.exitCode === 0 ? 0 : 1,
|
|
16051
16373
|
diagnostics: record.diagnostics,
|
|
16052
|
-
data: { evidence: record.evidence, path:
|
|
16374
|
+
data: { evidence: record.evidence, path: path47.relative(ctx.cwd, record.path), exitCode: record.exitCode },
|
|
16053
16375
|
text: lines
|
|
16054
16376
|
};
|
|
16055
16377
|
}
|
|
@@ -16104,6 +16426,8 @@ var HANDLERS2 = {
|
|
|
16104
16426
|
trace: runTrace,
|
|
16105
16427
|
waves: runWaves,
|
|
16106
16428
|
doctor: runDoctorCommand,
|
|
16429
|
+
clarify: runClarify,
|
|
16430
|
+
docs: runDocs,
|
|
16107
16431
|
upgrade: runUpgrade,
|
|
16108
16432
|
approve: runApprove,
|
|
16109
16433
|
issue: runIssue,
|
|
@@ -16149,7 +16473,7 @@ async function requireWorkspace(ctx) {
|
|
|
16149
16473
|
throw new CliError("ATLAS-WS-001", msg("cli.noWorkspace", ctx.language), 2);
|
|
16150
16474
|
}
|
|
16151
16475
|
const { workspace, config } = await loadWorkspace(root);
|
|
16152
|
-
const approvals = await loadApprovals(
|
|
16476
|
+
const approvals = await loadApprovals(path48.join(root, ".sdd"));
|
|
16153
16477
|
return { root, workspace, config, approvals: approvals.byArtifact };
|
|
16154
16478
|
}
|
|
16155
16479
|
var CliError = class extends Error {
|
|
@@ -16216,7 +16540,7 @@ function printResult(command, result, json2, language) {
|
|
|
16216
16540
|
}
|
|
16217
16541
|
const lines = [...result.text ?? []];
|
|
16218
16542
|
for (const d of result.diagnostics) {
|
|
16219
|
-
const location = d.path ? ` ${
|
|
16543
|
+
const location = d.path ? ` ${path48.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
|
|
16220
16544
|
lines.push(`${d.severity === "error" ? "ERROR" : d.severity === "warning" ? "AVISO" : "NOTA"} ${d.code}${location} \u2014 ${d.message}`);
|
|
16221
16545
|
if (d.suggestion) lines.push(` \u21B3 ${d.suggestion}`);
|
|
16222
16546
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specatlas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"description": "SpecAtlas: kernel determinista de Spec-Driven Development (CLI)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@specatlas/adapters": "0.1.1",
|
|
43
|
-
"@specatlas/core": "0.1.
|
|
43
|
+
"@specatlas/core": "0.1.27"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"typecheck": "tsc --noEmit",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: clarify
|
|
3
|
+
title: Aclarar
|
|
4
|
+
description: >-
|
|
5
|
+
Resuelve supuestos, dependencias y preguntas abiertas de un cambio ya especificado
|
|
6
|
+
antes de aprobar y planificar. Usar cuando el usuario pide aclarar, resolver dudas
|
|
7
|
+
o cerrar preguntas abiertas de una especificación.
|
|
8
|
+
requires: []
|
|
9
|
+
produces:
|
|
10
|
+
- changes/<slug>/clarify.md
|
|
11
|
+
arguments: true
|
|
12
|
+
agent:
|
|
13
|
+
mode: primary
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Fase: Aclarar (vaciar las preguntas abiertas)
|
|
17
|
+
|
|
18
|
+
## Objetivo
|
|
19
|
+
|
|
20
|
+
Resolver los supuestos, dependencias y preguntas abiertas del cambio `{{SLUG}}` y dejarlos registrados en `.sdd/changes/{{SLUG}}/clarify.md`.
|
|
21
|
+
El contenido se escribe en **{{LANGUAGE_NAME}}**.
|
|
22
|
+
|
|
23
|
+
## Pasos
|
|
24
|
+
|
|
25
|
+
1. Lee `.sdd/changes/{{SLUG}}/spec.md`, `proposal.md` y, si existe, `clarify.md`.
|
|
26
|
+
2. Detecta supuestos, dependencias y preguntas abiertas **reales** de la especificación; no inventes preguntas.
|
|
27
|
+
3. Pregunta una a una (máximo 5, las que más cambien el resultado), con opciones concretas cuando ayuden a decidir. No des nada por respondido sin respuesta del humano.
|
|
28
|
+
4. Escribe o actualiza `clarify.md` con la gramática canónica:
|
|
29
|
+
- `- [ ] pregunta` para lo que sigue abierto.
|
|
30
|
+
- `- [x] pregunta — respuesta` para lo aclarado.
|
|
31
|
+
5. Refleja el resumen de lo decidido en la propuesta (`proposal.md`), en su sección correspondiente.
|
|
32
|
+
6. Si una respuesta cambia lo especificado: corrige `spec.md` y avisa de que la firma quedó obsoleta (hay que volver a aprobar con `satlas approve`).
|
|
33
|
+
7. Cierra con `satlas clarify {{SLUG}}` (estado de la aclaración) y `satlas next {{SLUG}}`.
|
|
34
|
+
|
|
35
|
+
## Prohibido
|
|
36
|
+
|
|
37
|
+
- Inventar preguntas o respuestas.
|
|
38
|
+
- Cambiar la especificación sin avisar de la re-firma.
|
|
39
|
+
- Marcar como aclarada una pregunta que el humano no respondió.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: docs
|
|
3
|
+
title: Documentar
|
|
4
|
+
description: >-
|
|
5
|
+
Genera la documentación técnica y manual del cambio (carril completo) desde la
|
|
6
|
+
evidencia registrada. Usar cuando el usuario pide documentar, generar el manual
|
|
7
|
+
o cerrar la documentación antes de archivar.
|
|
8
|
+
requires: []
|
|
9
|
+
produces:
|
|
10
|
+
- changes/<slug>/docs/tecnica.md
|
|
11
|
+
- changes/<slug>/docs/manual.md
|
|
12
|
+
arguments: true
|
|
13
|
+
agent:
|
|
14
|
+
mode: primary
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Fase: Documentar (técnica y manual del cambio)
|
|
18
|
+
|
|
19
|
+
## Objetivo
|
|
20
|
+
|
|
21
|
+
Dejar la documentación técnica y manual del cambio `{{SLUG}}` en `.sdd/changes/{{SLUG}}/docs/`, construida desde lo real (especificación, tareas y evidencia).
|
|
22
|
+
El contenido se escribe en **{{LANGUAGE_NAME}}**.
|
|
23
|
+
|
|
24
|
+
## Pasos
|
|
25
|
+
|
|
26
|
+
1. Ejecuta `satlas docs {{SLUG}}` para generar el esqueleto de los dos documentos desde las plantillas y la evidencia.
|
|
27
|
+
2. Revisa lo generado: si un dato falta o es incorrecto, corrige **la fuente** (spec, tareas o evidencia) y vuelve a ejecutar el comando; no retoques los datos generados.
|
|
28
|
+
3. Completa lo que no se puede generar — decisiones, límites, ejemplos de uso y notas — **fuera del bloque gestionado** (lo que está entre los marcadores se reemplaza al regenerar; lo de fuera se conserva).
|
|
29
|
+
4. Señala expresamente lo que quedó sin evidencia; nunca lo presentes como hecho.
|
|
30
|
+
5. Cierra con `satlas docs {{SLUG}}` (regenera el bloque gestionado) y `satlas next {{SLUG}}`.
|
|
31
|
+
|
|
32
|
+
## Prohibido
|
|
33
|
+
|
|
34
|
+
- Documentar como hecho algo sin evidencia.
|
|
35
|
+
- Escribir dentro del bloque gestionado (se reemplaza al regenerar).
|
|
36
|
+
- Tocar la especificación, el plan o las tareas desde esta fase.
|