specatlas 0.1.8 → 0.1.10
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
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
sha256,
|
|
9
9
|
shortHash,
|
|
10
10
|
signApproval
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-X4FIHYOJ.js";
|
|
12
12
|
import {
|
|
13
13
|
__commonJS,
|
|
14
14
|
__toESM,
|
|
@@ -9864,6 +9864,14 @@ function verifyApproval(change, approvals, cfg, specContent) {
|
|
|
9864
9864
|
if (current !== approval.hash) return { status: "stale", approvedBy: approval.by, approvedAt: approval.at };
|
|
9865
9865
|
return { status: "valid", approvedBy: approval.by, approvedAt: approval.at };
|
|
9866
9866
|
}
|
|
9867
|
+
function requiresMockups(meta, cfg) {
|
|
9868
|
+
if (meta?.mockups === "required") return true;
|
|
9869
|
+
if (meta?.mockups === "skip") return false;
|
|
9870
|
+
return cfg.gates.mockup.require_approval;
|
|
9871
|
+
}
|
|
9872
|
+
function mockupOverride(change) {
|
|
9873
|
+
return (change.meta?.overrides ?? []).some((override) => override.gate === "mockup");
|
|
9874
|
+
}
|
|
9867
9875
|
function deriveState(input) {
|
|
9868
9876
|
const { change, cfg, approval, blockingFindings } = input;
|
|
9869
9877
|
const lane = change.meta?.lane ?? cfg.lanes.default;
|
|
@@ -9896,6 +9904,14 @@ function deriveState(input) {
|
|
|
9896
9904
|
blockedBy.push(`${blockingFindings} hallazgo(s) bloqueante(s)`);
|
|
9897
9905
|
return { state: "spec_draft", blockedBy, nextAction: next(`satlas validate --change ${change.slug}`, "Corregir los hallazgos de la especificaci\xF3n"), progress };
|
|
9898
9906
|
}
|
|
9907
|
+
if ((approval.status === "missing" || approval.status === "stale") && requiresMockups(change.meta, cfg) && input.mockupsReady !== true && !mockupOverride(change)) {
|
|
9908
|
+
return {
|
|
9909
|
+
state: "awaiting_mockups",
|
|
9910
|
+
blockedBy: ["mockups requeridos y no listos"],
|
|
9911
|
+
nextAction: next(`/satlas-mockup ${change.slug}`, "Generar los mockups (contrato visual) antes de aprobar", true),
|
|
9912
|
+
progress
|
|
9913
|
+
};
|
|
9914
|
+
}
|
|
9899
9915
|
if (approval.status === "missing" || approval.status === "stale") {
|
|
9900
9916
|
blockedBy.push(approval.status === "stale" ? "la firma de la spec qued\xF3 obsoleta (el archivo cambi\xF3)" : "la spec no est\xE1 aprobada");
|
|
9901
9917
|
const presented = change.presentationPath !== void 0;
|
|
@@ -9926,6 +9942,7 @@ function stateLabel(state) {
|
|
|
9926
9942
|
const labels = {
|
|
9927
9943
|
draft: "borrador",
|
|
9928
9944
|
spec_draft: "spec en borrador",
|
|
9945
|
+
awaiting_mockups: "esperando mockups",
|
|
9929
9946
|
awaiting_approval: "esperando aprobaci\xF3n",
|
|
9930
9947
|
approved: "aprobado",
|
|
9931
9948
|
planned: "planificado",
|
|
@@ -10498,6 +10515,7 @@ function changeMetaYaml(meta) {
|
|
|
10498
10515
|
if (meta.risk) doc["risk"] = meta.risk;
|
|
10499
10516
|
if (meta.created) doc["created"] = meta.created;
|
|
10500
10517
|
if (meta.owner) doc["owner"] = meta.owner;
|
|
10518
|
+
if (meta.mockups) doc["mockups"] = meta.mockups;
|
|
10501
10519
|
return `# Estado del cambio. La fase se DERIVA de los artefactos; aqu\xED solo hechos.
|
|
10502
10520
|
` + stringifyYaml3(doc, { lineWidth: 120 });
|
|
10503
10521
|
}
|
|
@@ -10783,7 +10801,7 @@ async function approveFromGithub(opts) {
|
|
|
10783
10801
|
if (!by) {
|
|
10784
10802
|
return { approved: false, label, issueNumber: number, diagnostics: [diag("ATLAS-GH-011", "error", "No se pudo determinar qui\xE9n aprueba (--by ni `gh api user`)")] };
|
|
10785
10803
|
}
|
|
10786
|
-
const { signApproval: signApproval2 } = await import("./approvals-
|
|
10804
|
+
const { signApproval: signApproval2 } = await import("./approvals-4QHIT7F6-JAI5IHNX.js");
|
|
10787
10805
|
const signed = await signApproval2({
|
|
10788
10806
|
root: opts.root,
|
|
10789
10807
|
artifact: path5.posix.join("changes", opts.slug, "spec.md"),
|
|
@@ -11302,6 +11320,28 @@ async function captureMockups(root, slug, manifest, now = /* @__PURE__ */ new Da
|
|
|
11302
11320
|
void now;
|
|
11303
11321
|
return { screenshots, diagnostics };
|
|
11304
11322
|
}
|
|
11323
|
+
async function mockupsReady(root, slug, change) {
|
|
11324
|
+
const check = await checkMockups(root, slug, change);
|
|
11325
|
+
return Boolean(check.manifest && check.manifest.screens.length > 0 && !check.stale);
|
|
11326
|
+
}
|
|
11327
|
+
async function setMockupRequirement(root, slug, value) {
|
|
11328
|
+
const file = path8.join(root, ".sdd", "changes", slug, "meta.yaml");
|
|
11329
|
+
const raw = await readTextIfExists(file) ?? `schema_version: 1
|
|
11330
|
+
slug: ${slug}
|
|
11331
|
+
lane: standard
|
|
11332
|
+
`;
|
|
11333
|
+
const lines = raw.replace(/\r\n?/g, "\n").split("\n");
|
|
11334
|
+
const index = lines.findIndex((line) => /^mockups:/.test(line));
|
|
11335
|
+
if (index >= 0) {
|
|
11336
|
+
lines[index] = `mockups: ${value}`;
|
|
11337
|
+
} else {
|
|
11338
|
+
let last = lines.length;
|
|
11339
|
+
while (last > 0 && (lines[last - 1] ?? "").trim() === "") last -= 1;
|
|
11340
|
+
lines.splice(last, 0, `mockups: ${value}`);
|
|
11341
|
+
}
|
|
11342
|
+
await writeText(file, lines.join("\n"));
|
|
11343
|
+
return { path: file };
|
|
11344
|
+
}
|
|
11305
11345
|
var checkSchema = z5.object({
|
|
11306
11346
|
id: z5.string().min(1),
|
|
11307
11347
|
title: z5.string().min(1),
|
|
@@ -11853,6 +11893,8 @@ async function generatePresentation(opts) {
|
|
|
11853
11893
|
const mockupInfo = await copyMockups(root, change, mockupsCopyDir);
|
|
11854
11894
|
const proposalRaw = await readTextIfExists(path12.join(change.dir, "proposal.md"));
|
|
11855
11895
|
const proposalBody = proposalRaw ? parseFrontmatter(proposalRaw).body : "";
|
|
11896
|
+
const approvals = await loadApprovals(path12.join(root, ".sdd"));
|
|
11897
|
+
const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent);
|
|
11856
11898
|
const labels = labelsFor(language);
|
|
11857
11899
|
const html = page({
|
|
11858
11900
|
language,
|
|
@@ -11869,7 +11911,8 @@ async function generatePresentation(opts) {
|
|
|
11869
11911
|
evidence: new Map((change.verify?.evidence ?? []).map((e) => [e.scenario, e.result])),
|
|
11870
11912
|
mockups: mockupInfo.screens,
|
|
11871
11913
|
screenshots: mockupInfo.screenshots,
|
|
11872
|
-
approveHint: `satlas approve ${change.slug} --by "<nombre>" --channel presentation
|
|
11914
|
+
approveHint: `satlas approve ${change.slug} --by "<nombre>" --channel presentation`,
|
|
11915
|
+
...approval.status === "valid" && approval.approvedBy ? { approval: { by: approval.approvedBy, at: approval.approvedAt ?? "" } } : {}
|
|
11873
11916
|
});
|
|
11874
11917
|
const outFile = path12.join(presentationDir, "index.html");
|
|
11875
11918
|
await writeText(outFile, html);
|
|
@@ -11920,6 +11963,7 @@ function labelsFor(language) {
|
|
|
11920
11963
|
mockups: "Mockups",
|
|
11921
11964
|
approve: "Approval",
|
|
11922
11965
|
approveText: "This proposal is approved by signing the spec (hash + author). Any later change invalidates the signature.",
|
|
11966
|
+
approveDone: "Approved by {by} on {at}. Any later change invalidates the signature.",
|
|
11923
11967
|
command: "Command",
|
|
11924
11968
|
hash: "Hash",
|
|
11925
11969
|
generated: "Generated",
|
|
@@ -11941,6 +11985,7 @@ function labelsFor(language) {
|
|
|
11941
11985
|
mockups: "Mockups",
|
|
11942
11986
|
approve: "Aprobaci\xF3n",
|
|
11943
11987
|
approveText: "Esta propuesta se aprueba firmando la spec (hash + autor). Cualquier cambio posterior invalida la firma.",
|
|
11988
|
+
approveDone: "Aprobada por {by} el {at}. Cualquier cambio posterior invalida la firma.",
|
|
11944
11989
|
command: "Comando",
|
|
11945
11990
|
hash: "Hash",
|
|
11946
11991
|
generated: "Generado",
|
|
@@ -12056,8 +12101,8 @@ function page(input) {
|
|
|
12056
12101
|
|
|
12057
12102
|
<section>
|
|
12058
12103
|
<h2>${esc(l.approve)}</h2>
|
|
12059
|
-
|
|
12060
|
-
<p>${esc(l.command)}: <code>${esc(input.approveHint)}</code></p
|
|
12104
|
+
${input.approval ? `<div class="callout" style="border-left-color:#15803d;background:rgba(21,128,61,.12)">\u2714 ${esc(l.approveDone.replace("{by}", input.approval.by).replace("{at}", input.approval.at))}</div>` : `<div class="callout">${esc(l.approveText)}</div>
|
|
12105
|
+
<p>${esc(l.command)}: <code>${esc(input.approveHint)}</code></p>`}
|
|
12061
12106
|
</section>
|
|
12062
12107
|
|
|
12063
12108
|
<footer>SpecAtlas \xB7 ${esc(input.project)} \xB7 ${esc(input.generatedAt)}</footer>
|
|
@@ -13475,10 +13520,26 @@ async function runApprove(ctx) {
|
|
|
13475
13520
|
diagnostics: [{ code: "ATLAS-APPROVE-000", severity: "error", message: "Falta --by <nombre>: toda aprobaci\xF3n es nominal y auditada" }]
|
|
13476
13521
|
};
|
|
13477
13522
|
}
|
|
13478
|
-
const { root } = await requireWorkspace(ctx);
|
|
13523
|
+
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
13479
13524
|
const artifact = target.includes("/") || target.includes("\\") ? target : path26.join("changes", target, "spec.md");
|
|
13480
13525
|
const channelFlag = flagString(ctx.flags, "channel");
|
|
13481
13526
|
const channel = channelFlag === "presentation" || channelFlag === "editor" || channelFlag === "pr" || channelFlag === "tracker" ? channelFlag : "cli";
|
|
13527
|
+
const change = workspace.changes.find(
|
|
13528
|
+
(candidate) => candidate.slug === target || artifact.includes(`changes/${candidate.slug}/`) || artifact.includes(`changes\\${candidate.slug}\\`)
|
|
13529
|
+
);
|
|
13530
|
+
if (change && requiresMockups(change.meta, config) && !(change.meta?.overrides ?? []).some((override) => override.gate === "mockup") && !await mockupsReady(root, change.slug, change)) {
|
|
13531
|
+
return {
|
|
13532
|
+
exitCode: 1,
|
|
13533
|
+
diagnostics: [
|
|
13534
|
+
{
|
|
13535
|
+
code: "ATLAS-APPROVE-002",
|
|
13536
|
+
severity: "error",
|
|
13537
|
+
message: `El cambio "${change.slug}" exige mockups (meta.yaml: mockups: required) y no est\xE1n listos`,
|
|
13538
|
+
suggestion: `Genera el contrato visual con \`/satlas-mockup ${change.slug}\` y vuelve a aprobar (o registra un override del gate "mockup" en meta.yaml)`
|
|
13539
|
+
}
|
|
13540
|
+
]
|
|
13541
|
+
};
|
|
13542
|
+
}
|
|
13482
13543
|
const result = await signApproval({
|
|
13483
13544
|
root,
|
|
13484
13545
|
artifact,
|
|
@@ -13871,12 +13932,15 @@ async function runMockup(ctx) {
|
|
|
13871
13932
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
13872
13933
|
const slug = ctx.positionals[0];
|
|
13873
13934
|
if (!slug) {
|
|
13874
|
-
return { exitCode: 2, diagnostics: [{ code: "ATLAS-MKP-000", severity: "error", message: "Falta el slug: satlas mockup <slug> [--plan|--check|--capture]" }] };
|
|
13935
|
+
return { exitCode: 2, diagnostics: [{ code: "ATLAS-MKP-000", severity: "error", message: "Falta el slug: satlas mockup <slug> [--plan|--check|--capture|--require]" }] };
|
|
13875
13936
|
}
|
|
13876
13937
|
const change = workspace.changes.find((c) => c.slug === slug);
|
|
13877
13938
|
if (!change) {
|
|
13878
13939
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-MKP-000", severity: "error", message: `No existe el cambio "${slug}"` }] };
|
|
13879
13940
|
}
|
|
13941
|
+
if (flagBool(ctx.flags, "require")) {
|
|
13942
|
+
await setMockupRequirement(root, slug, "required");
|
|
13943
|
+
}
|
|
13880
13944
|
if (flagBool(ctx.flags, "check")) {
|
|
13881
13945
|
const result = await checkMockups(root, slug, change);
|
|
13882
13946
|
const errors = result.findings.filter((d) => d.severity === "error").length;
|
|
@@ -14045,7 +14109,14 @@ async function evaluateChange(workspace, config, change, approvals) {
|
|
|
14045
14109
|
requireEvidence: config.gates.verify.mode !== "off" && config.gates.verify.require_evidence
|
|
14046
14110
|
});
|
|
14047
14111
|
const blocking = lintFindings.filter((d) => d.severity === "error").length + trace.findings.filter((d) => d.severity === "error").length;
|
|
14048
|
-
const
|
|
14112
|
+
const mockupsAreReady = requiresMockups(change.meta, config) ? await mockupsReady(workspace.root, change.slug, change) : void 0;
|
|
14113
|
+
const state = deriveState({
|
|
14114
|
+
change,
|
|
14115
|
+
cfg: config,
|
|
14116
|
+
approval,
|
|
14117
|
+
blockingFindings: change.delta ? blocking : 0,
|
|
14118
|
+
...mockupsAreReady !== void 0 ? { mockupsReady: mockupsAreReady } : {}
|
|
14119
|
+
});
|
|
14049
14120
|
return { change, approval, lintFindings, trace, blocking, state };
|
|
14050
14121
|
}
|
|
14051
14122
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
writeText
|
|
6
6
|
} from "./chunk-DKFIPJ74.js";
|
|
7
7
|
|
|
8
|
-
// ../core/dist/chunk-
|
|
8
|
+
// ../core/dist/chunk-LMJURGRU.js
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { stringify as stringifyYaml } from "yaml";
|
|
11
11
|
import { createHash } from "crypto";
|
|
@@ -70,6 +70,7 @@ var changeMetaSchema = z.object({
|
|
|
70
70
|
risk: z.enum(["low", "medium", "high"]).optional(),
|
|
71
71
|
created: z.string().optional(),
|
|
72
72
|
owner: z.string().optional(),
|
|
73
|
+
mockups: z.enum(["required", "skip"]).optional(),
|
|
73
74
|
tracker: z.object({ provider: z.string(), id: z.string() }).optional(),
|
|
74
75
|
paused: z.object({ reason: z.string(), at: z.string(), by: z.string() }).optional(),
|
|
75
76
|
lane_history: z.array(z.object({ from: z.enum(["fix", "standard", "full"]), to: z.enum(["fix", "standard", "full"]), at: z.string(), by: z.string() })).optional(),
|
|
@@ -99,6 +100,7 @@ function parseChangeMeta(raw, filePath) {
|
|
|
99
100
|
if (v.risk !== void 0) meta.risk = v.risk;
|
|
100
101
|
if (v.created !== void 0) meta.created = v.created;
|
|
101
102
|
if (v.owner !== void 0) meta.owner = v.owner;
|
|
103
|
+
if (v.mockups !== void 0) meta.mockups = v.mockups;
|
|
102
104
|
if (v.tracker !== void 0) meta.tracker = v.tracker;
|
|
103
105
|
if (v.paused !== void 0) meta.paused = v.paused;
|
|
104
106
|
if (v.lane_history !== void 0) meta.laneHistory = v.lane_history;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specatlas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
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.8"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"typecheck": "tsc --noEmit",
|
|
@@ -19,10 +19,10 @@ El contenido se escribe en **{{LANGUAGE_NAME}}**.
|
|
|
19
19
|
|
|
20
20
|
## Pasos
|
|
21
21
|
|
|
22
|
-
1. Prepara el plan y el manifiesto:
|
|
22
|
+
1. Prepara el plan y el manifiesto (y marca el cambio como que exige mockups si aún no lo declara):
|
|
23
23
|
|
|
24
24
|
```
|
|
25
|
-
satlas mockup {{SLUG}}
|
|
25
|
+
satlas mockup {{SLUG}} --require
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
2. Lee el plan (`changes/{{SLUG}}/mockups/plan.yaml`), la spec, el glosario y, si existen, `design/tokens.json` o `DESIGN.md` (sistema de diseño del proyecto).
|
|
@@ -34,13 +34,14 @@ El contenido se escribe en **{{LANGUAGE_NAME}}**.
|
|
|
34
34
|
- `MODIFIED` copia el bloque **completo** del requisito tal como está en la spec viva y lo edita.
|
|
35
35
|
- `REMOVED` declara `- Motivo:` y `- Migración:`.
|
|
36
36
|
5. Completa `.sdd/changes/{{SLUG}}/proposal.md` en lenguaje de negocio (sin tecnología y sin dejar los textos entre paréntesis de la plantilla): **Por qué** (problema u oportunidad, con la historia de usuario), **Qué cambia** (alcance funcional), **Fuera de alcance** (lo que no se hará) y **Cómo se mide el éxito** (indicadores observables).
|
|
37
|
-
6.
|
|
37
|
+
6. Si el cambio toca interfaz, **pregunta si llevará mockups** y anótalo en `.sdd/changes/{{SLUG}}/meta.yaml`: `mockups: required` (no se podrá aprobar sin ellos; el siguiente paso será `/satlas-mockup`) o `mockups: skip` (se aprueba sin contrato visual).
|
|
38
|
+
7. Verifica con el CLI y corrige hasta que no haya errores:
|
|
38
39
|
|
|
39
40
|
```
|
|
40
41
|
satlas validate --change {{SLUG}}
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
8. Reporta: número de requisitos y escenarios, supuestos, y las preguntas que quedaron abiertas.
|
|
44
45
|
|
|
45
46
|
## Prohibido
|
|
46
47
|
|