specatlas 0.1.9 → 0.1.11
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),
|
|
@@ -12692,7 +12732,7 @@ async function runCiGate(opts) {
|
|
|
12692
12732
|
}
|
|
12693
12733
|
|
|
12694
12734
|
// src/args.ts
|
|
12695
|
-
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "local", "strict", "yes", "dry-run", "require-evidence", "help", "version"]);
|
|
12735
|
+
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "local", "strict", "yes", "dry-run", "require-evidence", "require", "help", "version"]);
|
|
12696
12736
|
function parseArgs(argv) {
|
|
12697
12737
|
const positionals = [];
|
|
12698
12738
|
const flags = {};
|
|
@@ -12764,7 +12804,7 @@ var CATALOG = [
|
|
|
12764
12804
|
{ name: "archive", description: "Pliega los deltas a la spec viva y archiva el cambio", flags: ["yes", "dry-run", "domain", "json"], usage: "satlas archive <slug> [--yes] [--dry-run] [--json]" },
|
|
12765
12805
|
{ name: "verify", description: "Registra evidencia por escenario (ejecuta el comando y guarda resultado y hash)", flags: ["scenario", "command", "method", "result", "by", "notes", "file", "allow-command", "json"], usage: 'satlas verify <slug> [--scenario REQ-\u2026-S1 --command "npm test" --by "<nombre>"]' },
|
|
12766
12806
|
{ name: "analyze", description: "Chequeo cruzado (lint + trace + waves + mockups) y escribe analyze.md", flags: ["json"], usage: "satlas analyze <slug>" },
|
|
12767
|
-
{ name: "mockup", description: "Planifica, valida o captura los mockups del cambio", flags: ["plan", "check", "capture", "json"], usage: "satlas mockup <slug> [--check|--capture]" },
|
|
12807
|
+
{ name: "mockup", description: "Planifica, valida o captura los mockups del cambio", flags: ["plan", "check", "capture", "require", "json"], usage: "satlas mockup <slug> [--plan|--check|--capture|--require]" },
|
|
12768
12808
|
{ name: "present", description: "Genera el paquete de propuesta para el stakeholder (HTML autocontenido)", flags: ["json"], usage: "satlas present <slug>" },
|
|
12769
12809
|
{ name: "ci", description: "Gate de pipeline: specs + cambios + doctor + adaptadores (sin agente)", flags: ["strict", "json"], usage: "satlas ci [--strict]" },
|
|
12770
12810
|
{ name: "metrics", description: "M\xE9tricas locales del workspace (progreso, WIP, throughput, evidencia)", flags: ["json"], usage: "satlas metrics [--json]" },
|
|
@@ -13480,10 +13520,26 @@ async function runApprove(ctx) {
|
|
|
13480
13520
|
diagnostics: [{ code: "ATLAS-APPROVE-000", severity: "error", message: "Falta --by <nombre>: toda aprobaci\xF3n es nominal y auditada" }]
|
|
13481
13521
|
};
|
|
13482
13522
|
}
|
|
13483
|
-
const { root } = await requireWorkspace(ctx);
|
|
13523
|
+
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
13484
13524
|
const artifact = target.includes("/") || target.includes("\\") ? target : path26.join("changes", target, "spec.md");
|
|
13485
13525
|
const channelFlag = flagString(ctx.flags, "channel");
|
|
13486
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
|
+
}
|
|
13487
13543
|
const result = await signApproval({
|
|
13488
13544
|
root,
|
|
13489
13545
|
artifact,
|
|
@@ -13876,12 +13932,15 @@ async function runMockup(ctx) {
|
|
|
13876
13932
|
const { root, workspace, config } = await requireWorkspace(ctx);
|
|
13877
13933
|
const slug = ctx.positionals[0];
|
|
13878
13934
|
if (!slug) {
|
|
13879
|
-
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]" }] };
|
|
13880
13936
|
}
|
|
13881
13937
|
const change = workspace.changes.find((c) => c.slug === slug);
|
|
13882
13938
|
if (!change) {
|
|
13883
13939
|
return { exitCode: 2, diagnostics: [{ code: "ATLAS-MKP-000", severity: "error", message: `No existe el cambio "${slug}"` }] };
|
|
13884
13940
|
}
|
|
13941
|
+
if (flagBool(ctx.flags, "require")) {
|
|
13942
|
+
await setMockupRequirement(root, slug, "required");
|
|
13943
|
+
}
|
|
13885
13944
|
if (flagBool(ctx.flags, "check")) {
|
|
13886
13945
|
const result = await checkMockups(root, slug, change);
|
|
13887
13946
|
const errors = result.findings.filter((d) => d.severity === "error").length;
|
|
@@ -14050,7 +14109,14 @@ async function evaluateChange(workspace, config, change, approvals) {
|
|
|
14050
14109
|
requireEvidence: config.gates.verify.mode !== "off" && config.gates.verify.require_evidence
|
|
14051
14110
|
});
|
|
14052
14111
|
const blocking = lintFindings.filter((d) => d.severity === "error").length + trace.findings.filter((d) => d.severity === "error").length;
|
|
14053
|
-
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
|
+
});
|
|
14054
14120
|
return { change, approval, lintFindings, trace, blocking, state };
|
|
14055
14121
|
}
|
|
14056
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.11",
|
|
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
|
|