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.
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-
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
11011
|
-
updatedAt: now
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: ${(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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.
|
|
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.
|
|
43
|
+
"@specatlas/core": "0.1.11"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"typecheck": "tsc --noEmit",
|
package/workflow/phases/plan.md
CHANGED
|
@@ -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`
|