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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  signApproval
4
- } from "./chunk-IFALGLUJ.js";
4
+ } from "./chunk-X4FIHYOJ.js";
5
5
  import "./chunk-DKFIPJ74.js";
6
6
  export {
7
7
  signApproval
package/dist/bin.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  sha256,
9
9
  shortHash,
10
10
  signApproval
11
- } from "./chunk-IFALGLUJ.js";
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-HCBC7QDM-PSBJC7EV.js");
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 state = deriveState({ change, cfg: config, approval, blockingFindings: change.delta ? blocking : 0 });
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-2BFXQ65Q.js
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.9",
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.7"
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. Verifica con el CLI y corrige hasta que no haya errores:
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
- 7. Reporta: número de requisitos y escenarios, supuestos, y las preguntas que quedaron abiertas.
44
+ 8. Reporta: número de requisitos y escenarios, supuestos, y las preguntas que quedaron abiertas.
44
45
 
45
46
  ## Prohibido
46
47