specatlas 0.1.16 → 0.1.17
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 +112 -24
- package/package.json +2 -2
- package/workflow/phases/plan.md +1 -0
package/dist/bin.js
CHANGED
|
@@ -8871,34 +8871,49 @@ function renderMarkdown(markdown2, opts = {}) {
|
|
|
8871
8871
|
quote.push((lines[i] ?? "").replace(/^>\s?/, ""));
|
|
8872
8872
|
i += 1;
|
|
8873
8873
|
}
|
|
8874
|
-
const joined = quote.join(" ");
|
|
8875
|
-
const labelRe = /(?:^|\s)([\p{L}][\p{L}\s/()-]{1,32}):\s/gu;
|
|
8876
|
-
const hits = [];
|
|
8877
|
-
let hitMatch;
|
|
8878
|
-
while ((hitMatch = labelRe.exec(joined)) !== null) {
|
|
8879
|
-
if (hitMatch[1].trim().split(/\s+/).length > 4) continue;
|
|
8880
|
-
hits.push({ start: hitMatch.index, end: hitMatch.index + hitMatch[0].length, label: hitMatch[1] });
|
|
8881
|
-
}
|
|
8882
8874
|
const chips = [];
|
|
8883
8875
|
const prose = [];
|
|
8884
|
-
|
|
8885
|
-
for (
|
|
8886
|
-
const
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
const
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8876
|
+
const chipFor = (label, value) => `<span class="meta-chip"><b>${inlineMarkdown(label)}:</b> ${inlineMarkdown(value)}</span>`;
|
|
8877
|
+
for (const rawLine of quote) {
|
|
8878
|
+
const line2 = rawLine.trim();
|
|
8879
|
+
if (!line2) continue;
|
|
8880
|
+
const whole = /^(?:\*{0,2})([\p{L}][\p{L}\s/()-]{1,32}?)(?:\*{0,2})\s*:\s*(.+)$/u.exec(line2);
|
|
8881
|
+
const hasInlineLabels = / · \s*\*{0,2}[\p{L}][\p{L}\s/()-]{1,32}\*{0,2}:\s/u.test(line2);
|
|
8882
|
+
if (whole && !hasInlineLabels && whole[1].trim().split(/\s+/).length <= 4) {
|
|
8883
|
+
chips.push(chipFor(whole[1], whole[2]));
|
|
8884
|
+
continue;
|
|
8885
|
+
}
|
|
8886
|
+
const labelRe = /(?:^|\s|\*{0,2})([\p{L}][\p{L}\s/()-]{1,32}?)\*{0,2}:\s/gu;
|
|
8887
|
+
const hits = [];
|
|
8888
|
+
let hitMatch;
|
|
8889
|
+
while ((hitMatch = labelRe.exec(line2)) !== null) {
|
|
8890
|
+
if (hitMatch[1].trim().split(/\s+/).length > 4) continue;
|
|
8891
|
+
const prefix = /^\*+/.exec(hitMatch[0])?.[0].length ?? 0;
|
|
8892
|
+
hits.push({ start: hitMatch.index + prefix, end: hitMatch.index + hitMatch[0].length, label: hitMatch[1] });
|
|
8893
|
+
}
|
|
8894
|
+
if (hits.length === 0) {
|
|
8895
|
+
prose.push(line2);
|
|
8896
|
+
continue;
|
|
8897
|
+
}
|
|
8898
|
+
let cursor = 0;
|
|
8899
|
+
for (let h = 0; h < hits.length; h += 1) {
|
|
8900
|
+
const hit = hits[h];
|
|
8901
|
+
const before = line2.slice(cursor, hit.start).replace(/[\s·*]+$/u, "").trim();
|
|
8902
|
+
if (before) prose.push(before);
|
|
8903
|
+
const valueEnd = h + 1 < hits.length ? hits[h + 1].start : line2.length;
|
|
8904
|
+
let value = line2.slice(hit.end, valueEnd).replace(/[\s·*]+$/u, "").trim();
|
|
8905
|
+
const sentenceBreak = /[·.]\s+(?=[\p{Lu}][\p{L}]+(?:\s+[\p{L}]+){3,})/u.exec(value);
|
|
8906
|
+
if (sentenceBreak) {
|
|
8907
|
+
const note = value.slice(sentenceBreak.index + 1).trim();
|
|
8908
|
+
if (note) prose.push(note);
|
|
8909
|
+
value = value.slice(0, sentenceBreak.index + 1);
|
|
8910
|
+
}
|
|
8911
|
+
chips.push(chipFor(hit.label, value));
|
|
8912
|
+
cursor = valueEnd;
|
|
8896
8913
|
}
|
|
8897
|
-
|
|
8898
|
-
|
|
8914
|
+
const tail = line2.slice(cursor).replace(/^[\s·*]+/u, "").trim();
|
|
8915
|
+
if (tail) prose.push(tail);
|
|
8899
8916
|
}
|
|
8900
|
-
const tail = joined.slice(cursor).replace(/^[\s·]+/u, "").trim();
|
|
8901
|
-
if (tail) prose.push(tail);
|
|
8902
8917
|
const chipLine = chips.length > 0 ? `<div class="meta-line">${chips.join("")}</div>` : "";
|
|
8903
8918
|
const proseHtml = prose.length > 0 ? `<p>${inlineMarkdown(prose.join(" \xB7 "))}</p>` : "";
|
|
8904
8919
|
out.push(`<blockquote>${chipLine}${proseHtml}</blockquote>`);
|
|
@@ -9631,6 +9646,71 @@ function lintDelta(delta, livingRequirements, path192, opts = {}) {
|
|
|
9631
9646
|
}
|
|
9632
9647
|
return out;
|
|
9633
9648
|
}
|
|
9649
|
+
var MERMAID_KEYWORDS = [
|
|
9650
|
+
"flowchart",
|
|
9651
|
+
"graph",
|
|
9652
|
+
"sequenceDiagram",
|
|
9653
|
+
"stateDiagram-v2",
|
|
9654
|
+
"stateDiagram",
|
|
9655
|
+
"classDiagram",
|
|
9656
|
+
"erDiagram",
|
|
9657
|
+
"journey",
|
|
9658
|
+
"gantt",
|
|
9659
|
+
"pie",
|
|
9660
|
+
"mindmap",
|
|
9661
|
+
"timeline",
|
|
9662
|
+
"quadrantChart",
|
|
9663
|
+
"xychart-beta",
|
|
9664
|
+
"block-beta",
|
|
9665
|
+
"architecture-beta"
|
|
9666
|
+
];
|
|
9667
|
+
var MERMAID_BLOCKS = /^\s*(alt|loop|opt|par|rect|critical|break|subgraph)\b/;
|
|
9668
|
+
function lintPlan(planText, path192) {
|
|
9669
|
+
const out = [];
|
|
9670
|
+
const blocks = [...planText.matchAll(/```mermaid\r?\n([\s\S]*?)```/g)];
|
|
9671
|
+
for (const [index, block] of blocks.entries()) {
|
|
9672
|
+
const code = (block[1] ?? "").replace(/\r\n?/g, "\n");
|
|
9673
|
+
const lines = code.split("\n");
|
|
9674
|
+
const first = (lines.find((line) => line.trim().length > 0) ?? "").trim();
|
|
9675
|
+
if (!MERMAID_KEYWORDS.some((keyword) => first.startsWith(keyword))) {
|
|
9676
|
+
out.push(
|
|
9677
|
+
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)}"`, {
|
|
9678
|
+
path: path192,
|
|
9679
|
+
suggestion: "Corrige el tipo del diagrama o elimina el bloque"
|
|
9680
|
+
})
|
|
9681
|
+
);
|
|
9682
|
+
continue;
|
|
9683
|
+
}
|
|
9684
|
+
let open = 0;
|
|
9685
|
+
for (const line of lines) {
|
|
9686
|
+
if (MERMAID_BLOCKS.test(line)) open += 1;
|
|
9687
|
+
else if (/^\s*end\b/.test(line)) open -= 1;
|
|
9688
|
+
}
|
|
9689
|
+
if (open !== 0) {
|
|
9690
|
+
out.push(
|
|
9691
|
+
diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: faltan ${Math.abs(open)} \`end\` (bloques alt/loop/subgraph sin cerrar)`, {
|
|
9692
|
+
path: path192,
|
|
9693
|
+
suggestion: "Cierra cada bloque alt/loop/opt/par/rect/subgraph con `end`"
|
|
9694
|
+
})
|
|
9695
|
+
);
|
|
9696
|
+
}
|
|
9697
|
+
if (first.startsWith("sequenceDiagram")) {
|
|
9698
|
+
for (const line of lines) {
|
|
9699
|
+
if (!/^\s*[^\s:]+-{1,2}>{1,2}[^\s:]*\s*:\s*/.test(line)) continue;
|
|
9700
|
+
const message = line.slice(line.indexOf(":") + 1);
|
|
9701
|
+
if (message.includes(";")) {
|
|
9702
|
+
out.push(
|
|
9703
|
+
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`, {
|
|
9704
|
+
path: path192,
|
|
9705
|
+
suggestion: "Sustituye `;` por `\xB7` o `,` en los mensajes de sequenceDiagram"
|
|
9706
|
+
})
|
|
9707
|
+
);
|
|
9708
|
+
}
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
}
|
|
9712
|
+
return out;
|
|
9713
|
+
}
|
|
9634
9714
|
function lintSpec(spec, opts = {}) {
|
|
9635
9715
|
const out = [...spec.diagnostics];
|
|
9636
9716
|
for (const req of spec.requirements) {
|
|
@@ -11720,6 +11800,10 @@ async function runAnalyze(opts) {
|
|
|
11720
11800
|
if (lane !== "fix" && !change.planPath) {
|
|
11721
11801
|
findings.push(diag("ATLAS-ANALYZE-001", "warning", "El cambio no tiene plan.md (plan t\xE9cnico)", { suggestion: "Ejecuta la fase /satlas-plan" }));
|
|
11722
11802
|
}
|
|
11803
|
+
if (change.planPath) {
|
|
11804
|
+
const planText = await readTextIfExists(change.planPath) ?? "";
|
|
11805
|
+
findings.push(...lintPlan(planText, change.planPath));
|
|
11806
|
+
}
|
|
11723
11807
|
const deltaScenarios = [...change.delta?.added ?? [], ...change.delta?.modified ?? []].flatMap((r) => r.scenarios);
|
|
11724
11808
|
const passed = new Set((change.verify?.evidence ?? []).filter((e) => e.result === "pass").map((e) => e.scenario));
|
|
11725
11809
|
const evidence = { done: deltaScenarios.filter((s) => passed.has(s.id)).length, total: deltaScenarios.length };
|
|
@@ -12800,6 +12884,10 @@ async function runCiGate(opts) {
|
|
|
12800
12884
|
requireEvidence: config.gates.verify.mode !== "off" && config.gates.verify.require_evidence
|
|
12801
12885
|
});
|
|
12802
12886
|
const changeDiags = [...lintFindings, ...trace.findings];
|
|
12887
|
+
if (change.planPath) {
|
|
12888
|
+
const planText = await readTextIfExists(change.planPath) ?? "";
|
|
12889
|
+
changeDiags.push(...lintPlan(planText, change.planPath));
|
|
12890
|
+
}
|
|
12803
12891
|
if (change.tasks && change.tasks.counts.total > 0) {
|
|
12804
12892
|
const plan = planWaves(change.tasks, { maxParallel: config.waves.max_parallel });
|
|
12805
12893
|
changeDiags.push(...plan.blocks.flatMap((block) => block.diagnostics));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specatlas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
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.14"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"typecheck": "tsc --noEmit",
|
package/workflow/phases/plan.md
CHANGED
|
@@ -30,6 +30,7 @@ El contenido se escribe en **{{LANGUAGE_NAME}}**. El plan es interno: puede (y d
|
|
|
30
30
|
1. Contexto AS-IS · 2. Enfoque técnico (con alternativas descartadas) · 3. **Diagramas** · 4. Diseño por capa/módulos · 5. Matriz de trazabilidad (REQ → tareas) · 6. Matriz de paridad AS-IS → TO-BE (solo refactors sustitutivos) · 7. Tareas (referencia) · 8. Riesgos y mitigaciones · 9. Rollback · 10. Dependencias y supuestos.
|
|
31
31
|
- **Encabezado**: antes de la sección 1, una cita con los metadatos **una clave por línea** — **Cambio**, **Carril**, **Dominio**, **Spec aprobada** y **Contrato visual** (si existe) — y una última línea: «Los artefactos aprobados no se editan: este plan y `tasks.md` son los únicos artefactos que produce esta fase.»
|
|
32
32
|
3. En `## 3. Diagramas` incluye los diagramas mermaid que el cambio necesite: `erDiagram` si toca datos, `sequenceDiagram` si hay integración/API/jobs, `flowchart` si hay proceso o validaciones, `stateDiagram-v2` si hay estados, `classDiagram` si el dominio no es trivial, diagrama de arquitectura si hay módulos nuevos.
|
|
33
|
+
- Reglas mermaid: cada bloque empieza con el tipo (`flowchart`, `sequenceDiagram`, `stateDiagram-v2`, `erDiagram`…), todo bloque (`alt`/`loop`/`opt`/`par`/`rect`/`subgraph`) se cierra con `end`, y **no uses `;` dentro de los mensajes o etiquetas** de `sequenceDiagram` (mermaid lo interpreta como fin de sentencia: usa `·` o `,`).
|
|
33
34
|
4. Escribe `tasks.md` con la gramática canónica (separador ` · `):
|
|
34
35
|
- `## Bloque N — Título` y `- [ ] T<N>.<seq> Acción · Archivos: ruta · Cubre: REQ-…-S1 · Depende de: T<N>.<seq> · Reversión: cómo revertir`
|
|
35
36
|
- Tareas atómicas (una acción por tarea). Trabajo de infraestructura sin requisito: `· Infra`.
|