specatlas 0.1.12 → 0.1.14

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-X4FIHYOJ.js";
4
+ } from "./chunk-N3AY2VXJ.js";
5
5
  import "./chunk-DKFIPJ74.js";
6
6
  export {
7
7
  signApproval
package/dist/bin.js CHANGED
@@ -3,12 +3,15 @@ import {
3
3
  artifactHash,
4
4
  countBySeverity,
5
5
  diag,
6
+ localDate,
7
+ localIso,
8
+ localMonth,
6
9
  parseApprovals,
7
10
  parseChangeMeta,
8
11
  sha256,
9
12
  shortHash,
10
13
  signApproval
11
- } from "./chunk-X4FIHYOJ.js";
14
+ } from "./chunk-N3AY2VXJ.js";
12
15
  import {
13
16
  __commonJS,
14
17
  __toESM,
@@ -8867,7 +8870,37 @@ function renderMarkdown(markdown2, opts = {}) {
8867
8870
  quote.push((lines[i] ?? "").replace(/^>\s?/, ""));
8868
8871
  i += 1;
8869
8872
  }
8870
- out.push(`<blockquote><p>${inlineMarkdown(quote.join(" "))}</p></blockquote>`);
8873
+ const joined = quote.join(" ");
8874
+ const labelRe = /(?:^|\s)([\p{L}][\p{L}\s/()-]{1,32}):\s/gu;
8875
+ const hits = [];
8876
+ let hitMatch;
8877
+ while ((hitMatch = labelRe.exec(joined)) !== null) {
8878
+ if (hitMatch[1].trim().split(/\s+/).length > 4) continue;
8879
+ hits.push({ start: hitMatch.index, end: hitMatch.index + hitMatch[0].length, label: hitMatch[1] });
8880
+ }
8881
+ const chips = [];
8882
+ const prose = [];
8883
+ let cursor = 0;
8884
+ for (let h = 0; h < hits.length; h += 1) {
8885
+ const hit = hits[h];
8886
+ const before = joined.slice(cursor, hit.start).replace(/[\s·]+$/u, "").trim();
8887
+ if (before) prose.push(before);
8888
+ const valueEnd = h + 1 < hits.length ? hits[h + 1].start : joined.length;
8889
+ let value = joined.slice(hit.end, valueEnd).replace(/[\s·]+$/u, "").trim();
8890
+ const sentenceBreak = /[·.]\s+(?=[\p{Lu}][\p{L}]+(?:\s+[\p{L}]+){3,})/u.exec(value);
8891
+ if (sentenceBreak) {
8892
+ const note = value.slice(sentenceBreak.index + 1).trim();
8893
+ if (note) prose.push(note);
8894
+ value = value.slice(0, sentenceBreak.index + 1);
8895
+ }
8896
+ chips.push(`<span class="meta-chip"><b>${inlineMarkdown(hit.label)}:</b> ${inlineMarkdown(value)}</span>`);
8897
+ cursor = valueEnd;
8898
+ }
8899
+ const tail = joined.slice(cursor).replace(/^[\s·]+/u, "").trim();
8900
+ if (tail) prose.push(tail);
8901
+ const chipLine = chips.length > 0 ? `<div class="meta-line">${chips.join("")}</div>` : "";
8902
+ const proseHtml = prose.length > 0 ? `<p>${inlineMarkdown(prose.join(" \xB7 "))}</p>` : "";
8903
+ out.push(`<blockquote>${chipLine}${proseHtml}</blockquote>`);
8871
8904
  continue;
8872
8905
  }
8873
8906
  if (/^\|/.test(line) && /^\|[\s:|-]+\|$/.test(lines[i + 1] ?? "")) {
@@ -10807,7 +10840,7 @@ async function approveFromGithub(opts) {
10807
10840
  if (!by) {
10808
10841
  return { approved: false, label, issueNumber: number, diagnostics: [diag("ATLAS-GH-011", "error", "No se pudo determinar qui\xE9n aprueba (--by ni `gh api user`)")] };
10809
10842
  }
10810
- const { signApproval: signApproval2 } = await import("./approvals-4QHIT7F6-JAI5IHNX.js");
10843
+ const { signApproval: signApproval2 } = await import("./approvals-XPI466UK-HLB4NIVC.js");
10811
10844
  const signed = await signApproval2({
10812
10845
  root: opts.root,
10813
10846
  artifact: path5.posix.join("changes", opts.slug, "spec.md"),
@@ -10957,7 +10990,7 @@ ${exec.stderr}`);
10957
10990
  scenario,
10958
10991
  method,
10959
10992
  result,
10960
- date: (opts.now ?? /* @__PURE__ */ new Date()).toISOString(),
10993
+ date: localIso(opts.now),
10961
10994
  by: opts.by.trim(),
10962
10995
  line: 0
10963
10996
  };
@@ -10994,7 +11027,7 @@ ${yaml2}
10994
11027
  return returned;
10995
11028
  }
10996
11029
  function generateRunId(now = /* @__PURE__ */ new Date()) {
10997
- const iso = now.toISOString().replace(/[-:T]/g, "").slice(0, 14);
11030
+ const iso = localIso(now).replace(/[-+:.Z]/g, "").slice(0, 15);
10998
11031
  return `${iso.slice(0, 8)}-${iso.slice(8, 14)}-${randomBytes(3).toString("hex")}`;
10999
11032
  }
11000
11033
  function runsDir(root) {
@@ -11007,8 +11040,8 @@ async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Da
11007
11040
  slug,
11008
11041
  phase,
11009
11042
  status: "active",
11010
- startedAt: now.toISOString(),
11011
- updatedAt: now.toISOString()
11043
+ startedAt: localIso(now),
11044
+ updatedAt: localIso(now)
11012
11045
  };
11013
11046
  if (inputs) state.inputs = inputs;
11014
11047
  const dir = path7.join(runsDir(root), runId);
@@ -11020,7 +11053,7 @@ async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Da
11020
11053
  return state;
11021
11054
  }
11022
11055
  async function appendRunEvent(root, runId, type, data, now = /* @__PURE__ */ new Date()) {
11023
- const event = { eventId: randomBytes(4).toString("hex"), at: now.toISOString(), type };
11056
+ const event = { eventId: randomBytes(4).toString("hex"), at: localIso(now), type };
11024
11057
  if (data) event.data = data;
11025
11058
  const file = path7.join(runsDir(root), runId, "events.jsonl");
11026
11059
  const previous = await readTextIfExists(file) ?? "";
@@ -11034,7 +11067,7 @@ async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Da
11034
11067
  if (raw === void 0) return;
11035
11068
  const state = JSON.parse(raw);
11036
11069
  state.status = status;
11037
- state.updatedAt = now.toISOString();
11070
+ state.updatedAt = localIso(now);
11038
11071
  await writeText(file, `${JSON.stringify(state, null, 2)}
11039
11072
  `);
11040
11073
  }
@@ -11259,7 +11292,7 @@ async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date(
11259
11292
  if (await exists(file)) return file;
11260
11293
  const doc = {
11261
11294
  schema_version: 1,
11262
- generated_at: now.toISOString(),
11295
+ generated_at: localIso(now),
11263
11296
  platform: plan.platform,
11264
11297
  screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints }))
11265
11298
  };
@@ -11275,7 +11308,7 @@ async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PUR
11275
11308
  level: "hifi",
11276
11309
  platform: plan.platform,
11277
11310
  inputs_hash: inputsHash,
11278
- generated_at: now.toISOString(),
11311
+ generated_at: localIso(now),
11279
11312
  screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints })),
11280
11313
  screenshots: []
11281
11314
  };
@@ -11717,7 +11750,7 @@ async function runAnalyze(opts) {
11717
11750
  if (packs) result.packs = packs;
11718
11751
  if (opts.write !== false) {
11719
11752
  const file = path10.join(change.dir, "analyze.md");
11720
- await writeText(file, renderAnalyze(change, result, (opts.now ?? /* @__PURE__ */ new Date()).toISOString()));
11753
+ await writeText(file, renderAnalyze(change, result, localIso(opts.now)));
11721
11754
  result.path = file;
11722
11755
  }
11723
11756
  return result;
@@ -11856,7 +11889,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
11856
11889
  archived: archivedEntries.length
11857
11890
  };
11858
11891
  return {
11859
- generatedAt: now.toISOString(),
11892
+ generatedAt: localIso(now),
11860
11893
  project: config.project.name,
11861
11894
  root: resolved,
11862
11895
  changes,
@@ -11911,7 +11944,7 @@ async function generatePresentation(opts) {
11911
11944
  project: config.project.name,
11912
11945
  version: change.delta.sectionsFound.length,
11913
11946
  hash,
11914
- generatedAt: now.toISOString(),
11947
+ generatedAt: localIso(now),
11915
11948
  proposalHtml: proposalBody ? renderMarkdown2(proposalBody) : `<p><em>${labels.noProposal}</em></p>`,
11916
11949
  requirements: change.delta ? [...change.delta.added.map((r) => ({ req: r, kind: "added" })), ...change.delta.modified.map((r) => ({ req: r, kind: "modified" }))] : [],
11917
11950
  evidence: new Map((change.verify?.evidence ?? []).map((e) => [e.scenario, e.result])),
@@ -12290,7 +12323,7 @@ function baselineSpec(domain, files) {
12290
12323
  lines.push(`domain: ${domain}`);
12291
12324
  lines.push(`title: ${title}`);
12292
12325
  lines.push("version: 1");
12293
- lines.push(`updated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
12326
+ lines.push(`updated: ${localDate()}`);
12294
12327
  lines.push("---");
12295
12328
  lines.push("");
12296
12329
  lines.push(`# ${title}`);
@@ -12311,7 +12344,7 @@ function adoptReport(input) {
12311
12344
  lines.push(es ? "# Adopci\xF3n (brownfield)" : "# Adoption (brownfield)");
12312
12345
  lines.push("");
12313
12346
  lines.push(es ? `- Proyecto: ${input.config.project.name}` : `- Project: ${input.config.project.name}`);
12314
- lines.push(es ? `- Generado: ${input.now.toISOString()}` : `- Generated: ${input.now.toISOString()}`);
12347
+ lines.push(es ? `- Generado: ${localIso(input.now)}` : `- Generated: ${localIso(input.now)}`);
12315
12348
  lines.push(es ? `- Archivos de c\xF3digo inventariados: ${input.files} (de ${input.inventoried} archivos totales)` : `- Inventoried source files: ${input.files} (of ${input.inventoried} total files)`);
12316
12349
  if (input.detection) lines.push(es ? `- Stack detectado: ${input.detection.name} (${input.detection.score} puntos)` : `- Detected stack: ${input.detection.name} (${input.detection.score})`);
12317
12350
  lines.push("");
@@ -12363,7 +12396,7 @@ async function initWorkspace(opts) {
12363
12396
  created.push(path14.join(sddDir, "glossary.md"));
12364
12397
  const detected = await detectIfPossible(root, opts.profilesDir);
12365
12398
  if (detected) {
12366
- const yaml2 = detectionToYaml(detected, (opts.now ?? /* @__PURE__ */ new Date()).toISOString());
12399
+ const yaml2 = detectionToYaml(detected, localIso(opts.now));
12367
12400
  await writeText(path14.join(sddDir, "profiles", "detected.yaml"), yaml2);
12368
12401
  created.push(path14.join(sddDir, "profiles", "detected.yaml"));
12369
12402
  if (detected.best) {
@@ -12439,7 +12472,7 @@ async function createChange(opts) {
12439
12472
  title,
12440
12473
  domain,
12441
12474
  lane,
12442
- created: (opts.now ?? /* @__PURE__ */ new Date()).toISOString().slice(0, 10)
12475
+ created: localDate(opts.now)
12443
12476
  };
12444
12477
  await ensureDir(dir);
12445
12478
  const templates = templatesFor(language);
@@ -12546,7 +12579,7 @@ async function archiveChange(opts) {
12546
12579
  return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
12547
12580
  }
12548
12581
  const nowFix = opts.now ?? /* @__PURE__ */ new Date();
12549
- const targetFix = path16.join(root, ".sdd", "changes", "archive", `${nowFix.toISOString().slice(0, 7)}-${opts.slug}`);
12582
+ const targetFix = path16.join(root, ".sdd", "changes", "archive", `${localMonth(nowFix)}-${opts.slug}`);
12550
12583
  if (await exists(targetFix)) {
12551
12584
  diagnostics.push(diag("ATLAS-ARCH-002", "error", `Ya existe un cambio archivado en ${targetFix}`, { path: targetFix }));
12552
12585
  return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
@@ -12587,7 +12620,7 @@ version: 0
12587
12620
  title: fm.data["title"] ?? change.meta.title ?? domain,
12588
12621
  owner: fm.data["owner"],
12589
12622
  version: version + 1,
12590
- updated: now.toISOString().slice(0, 10)
12623
+ updated: localDate(now)
12591
12624
  };
12592
12625
  for (const key of Object.keys(nextFm)) if (nextFm[key] === void 0) delete nextFm[key];
12593
12626
  const body = fold.content.replace(/^[\r\n]+/, "").trimEnd() + "\n";
@@ -12596,7 +12629,7 @@ ${Object.entries(nextFm).map(([k, v]) => `${k}: ${String(v)}`).join("\n")}
12596
12629
  ---
12597
12630
 
12598
12631
  ${body}`;
12599
- const month = now.toISOString().slice(0, 7);
12632
+ const month = localMonth(now);
12600
12633
  const archiveDir = path16.join(root, ".sdd", "changes", "archive");
12601
12634
  const target = path16.join(archiveDir, `${month}-${opts.slug}`);
12602
12635
  if (await exists(target)) {
@@ -5,12 +5,28 @@ import {
5
5
  writeText
6
6
  } from "./chunk-DKFIPJ74.js";
7
7
 
8
- // ../core/dist/chunk-LMJURGRU.js
8
+ // ../core/dist/chunk-CTT5JQA5.js
9
9
  import path from "path";
10
10
  import { stringify as stringifyYaml } from "yaml";
11
11
  import { createHash } from "crypto";
12
12
  import { parse as parseYaml } from "yaml";
13
13
  import { z } from "zod";
14
+ function pad(value, length = 2) {
15
+ return String(value).padStart(length, "0");
16
+ }
17
+ function localDate(date = /* @__PURE__ */ new Date()) {
18
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
19
+ }
20
+ function localMonth(date = /* @__PURE__ */ new Date()) {
21
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}`;
22
+ }
23
+ function localIso(date = /* @__PURE__ */ new Date()) {
24
+ const offset = -date.getTimezoneOffset();
25
+ const sign = offset >= 0 ? "+" : "-";
26
+ const absolute = Math.abs(offset);
27
+ const time = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`;
28
+ return `${localDate(date)}T${time}${sign}${pad(Math.floor(absolute / 60))}:${pad(absolute % 60)}`;
29
+ }
14
30
  function diag(code, severity, message, opts = {}) {
15
31
  const d = { code, severity, message };
16
32
  if (opts.path !== void 0) d.path = opts.path;
@@ -189,7 +205,7 @@ async function signApproval(opts) {
189
205
  artifact: rel,
190
206
  artifactHash: artifactHash(content),
191
207
  approvedBy: opts.by.trim(),
192
- approvedAt: now.toISOString(),
208
+ approvedAt: localIso(now),
193
209
  channel: opts.channel ?? "cli"
194
210
  };
195
211
  if (opts.note) approval.note = opts.note;
@@ -216,6 +232,9 @@ async function signApproval(opts) {
216
232
  }
217
233
 
218
234
  export {
235
+ localDate,
236
+ localMonth,
237
+ localIso,
219
238
  diag,
220
239
  countBySeverity,
221
240
  sha256,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specatlas",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
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.9"
43
+ "@specatlas/core": "0.1.11"
44
44
  },
45
45
  "scripts": {
46
46
  "typecheck": "tsc --noEmit",
@@ -28,6 +28,7 @@ El contenido se escribe en **{{LANGUAGE_NAME}}**. El plan es interno: puede (y d
28
28
  1. Lee `.sdd/changes/{{SLUG}}/spec.md`, `.sdd/constitution.md`, `.sdd/profiles/detected.yaml`, `.sdd/profiles/custom/*` y el código real que vayas a tocar (anclas AS-IS: existe / no existe / blast radius).
29
29
  2. Escribe `plan.md` con las secciones canónicas:
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
+ - **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.»
31
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.
32
33
  4. Escribe `tasks.md` con la gramática canónica (separador ` · `):
33
34
  - `## 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`