specatlas 0.1.14 → 0.1.16

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-N3AY2VXJ.js";
4
+ } from "./chunk-HYN75DTZ.js";
5
5
  import "./chunk-DKFIPJ74.js";
6
6
  export {
7
7
  signApproval
package/dist/bin.js CHANGED
@@ -3,15 +3,16 @@ import {
3
3
  artifactHash,
4
4
  countBySeverity,
5
5
  diag,
6
+ localCompact,
6
7
  localDate,
7
- localIso,
8
8
  localMonth,
9
+ localStamp,
9
10
  parseApprovals,
10
11
  parseChangeMeta,
11
12
  sha256,
12
13
  shortHash,
13
14
  signApproval
14
- } from "./chunk-N3AY2VXJ.js";
15
+ } from "./chunk-HYN75DTZ.js";
15
16
  import {
16
17
  __commonJS,
17
18
  __toESM,
@@ -8975,6 +8976,7 @@ import path13 from "path";
8975
8976
  import path14 from "path";
8976
8977
  import path15 from "path";
8977
8978
  import { promises as fs } from "fs";
8979
+ import { cp, rename, rm } from "fs/promises";
8978
8980
  import path16 from "path";
8979
8981
  import path17 from "path";
8980
8982
  import path18 from "path";
@@ -9619,12 +9621,12 @@ function lintDelta(delta, livingRequirements, path192, opts = {}) {
9619
9621
  out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path192, line: req.line }));
9620
9622
  }
9621
9623
  }
9622
- for (const rename of delta.renamed) {
9623
- if (rename.from.id !== rename.to.id) {
9624
- out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename.from.id} \u2192 ${rename.to.id}); los ids son inmutables`, { path: path192, line: rename.line }));
9624
+ for (const rename2 of delta.renamed) {
9625
+ if (rename2.from.id !== rename2.to.id) {
9626
+ out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path192, line: rename2.line }));
9625
9627
  }
9626
- if (!livingRequirements.has(rename.from.id)) {
9627
- out.push(diag("TRACE-007", "error", `RENAMED ${rename.from.id} no existe en la spec viva`, { path: path192, line: rename.line }));
9628
+ if (!livingRequirements.has(rename2.from.id)) {
9629
+ out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path192, line: rename2.line }));
9628
9630
  }
9629
9631
  }
9630
9632
  return out;
@@ -10840,7 +10842,7 @@ async function approveFromGithub(opts) {
10840
10842
  if (!by) {
10841
10843
  return { approved: false, label, issueNumber: number, diagnostics: [diag("ATLAS-GH-011", "error", "No se pudo determinar qui\xE9n aprueba (--by ni `gh api user`)")] };
10842
10844
  }
10843
- const { signApproval: signApproval2 } = await import("./approvals-XPI466UK-HLB4NIVC.js");
10845
+ const { signApproval: signApproval2 } = await import("./approvals-2PO6223I-54L7NGZ5.js");
10844
10846
  const signed = await signApproval2({
10845
10847
  root: opts.root,
10846
10848
  artifact: path5.posix.join("changes", opts.slug, "spec.md"),
@@ -10990,7 +10992,7 @@ ${exec.stderr}`);
10990
10992
  scenario,
10991
10993
  method,
10992
10994
  result,
10993
- date: localIso(opts.now),
10995
+ date: localStamp(opts.now),
10994
10996
  by: opts.by.trim(),
10995
10997
  line: 0
10996
10998
  };
@@ -11027,7 +11029,7 @@ ${yaml2}
11027
11029
  return returned;
11028
11030
  }
11029
11031
  function generateRunId(now = /* @__PURE__ */ new Date()) {
11030
- const iso = localIso(now).replace(/[-+:.Z]/g, "").slice(0, 15);
11032
+ const iso = localCompact(now);
11031
11033
  return `${iso.slice(0, 8)}-${iso.slice(8, 14)}-${randomBytes(3).toString("hex")}`;
11032
11034
  }
11033
11035
  function runsDir(root) {
@@ -11040,8 +11042,8 @@ async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Da
11040
11042
  slug,
11041
11043
  phase,
11042
11044
  status: "active",
11043
- startedAt: localIso(now),
11044
- updatedAt: localIso(now)
11045
+ startedAt: localStamp(now),
11046
+ updatedAt: localStamp(now)
11045
11047
  };
11046
11048
  if (inputs) state.inputs = inputs;
11047
11049
  const dir = path7.join(runsDir(root), runId);
@@ -11053,7 +11055,7 @@ async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Da
11053
11055
  return state;
11054
11056
  }
11055
11057
  async function appendRunEvent(root, runId, type, data, now = /* @__PURE__ */ new Date()) {
11056
- const event = { eventId: randomBytes(4).toString("hex"), at: localIso(now), type };
11058
+ const event = { eventId: randomBytes(4).toString("hex"), at: localStamp(now), type };
11057
11059
  if (data) event.data = data;
11058
11060
  const file = path7.join(runsDir(root), runId, "events.jsonl");
11059
11061
  const previous = await readTextIfExists(file) ?? "";
@@ -11067,7 +11069,7 @@ async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Da
11067
11069
  if (raw === void 0) return;
11068
11070
  const state = JSON.parse(raw);
11069
11071
  state.status = status;
11070
- state.updatedAt = localIso(now);
11072
+ state.updatedAt = localStamp(now);
11071
11073
  await writeText(file, `${JSON.stringify(state, null, 2)}
11072
11074
  `);
11073
11075
  }
@@ -11292,7 +11294,7 @@ async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date(
11292
11294
  if (await exists(file)) return file;
11293
11295
  const doc = {
11294
11296
  schema_version: 1,
11295
- generated_at: localIso(now),
11297
+ generated_at: localStamp(now),
11296
11298
  platform: plan.platform,
11297
11299
  screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints }))
11298
11300
  };
@@ -11308,7 +11310,7 @@ async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PUR
11308
11310
  level: "hifi",
11309
11311
  platform: plan.platform,
11310
11312
  inputs_hash: inputsHash,
11311
- generated_at: localIso(now),
11313
+ generated_at: localStamp(now),
11312
11314
  screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints })),
11313
11315
  screenshots: []
11314
11316
  };
@@ -11750,7 +11752,7 @@ async function runAnalyze(opts) {
11750
11752
  if (packs) result.packs = packs;
11751
11753
  if (opts.write !== false) {
11752
11754
  const file = path10.join(change.dir, "analyze.md");
11753
- await writeText(file, renderAnalyze(change, result, localIso(opts.now)));
11755
+ await writeText(file, renderAnalyze(change, result, localStamp(opts.now)));
11754
11756
  result.path = file;
11755
11757
  }
11756
11758
  return result;
@@ -11889,7 +11891,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
11889
11891
  archived: archivedEntries.length
11890
11892
  };
11891
11893
  return {
11892
- generatedAt: localIso(now),
11894
+ generatedAt: localStamp(now),
11893
11895
  project: config.project.name,
11894
11896
  root: resolved,
11895
11897
  changes,
@@ -11944,7 +11946,7 @@ async function generatePresentation(opts) {
11944
11946
  project: config.project.name,
11945
11947
  version: change.delta.sectionsFound.length,
11946
11948
  hash,
11947
- generatedAt: localIso(now),
11949
+ generatedAt: localStamp(now),
11948
11950
  proposalHtml: proposalBody ? renderMarkdown2(proposalBody) : `<p><em>${labels.noProposal}</em></p>`,
11949
11951
  requirements: change.delta ? [...change.delta.added.map((r) => ({ req: r, kind: "added" })), ...change.delta.modified.map((r) => ({ req: r, kind: "modified" }))] : [],
11950
11952
  evidence: new Map((change.verify?.evidence ?? []).map((e) => [e.scenario, e.result])),
@@ -12344,7 +12346,7 @@ function adoptReport(input) {
12344
12346
  lines.push(es ? "# Adopci\xF3n (brownfield)" : "# Adoption (brownfield)");
12345
12347
  lines.push("");
12346
12348
  lines.push(es ? `- Proyecto: ${input.config.project.name}` : `- Project: ${input.config.project.name}`);
12347
- lines.push(es ? `- Generado: ${localIso(input.now)}` : `- Generated: ${localIso(input.now)}`);
12349
+ lines.push(es ? `- Generado: ${localStamp(input.now)}` : `- Generated: ${localStamp(input.now)}`);
12348
12350
  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)`);
12349
12351
  if (input.detection) lines.push(es ? `- Stack detectado: ${input.detection.name} (${input.detection.score} puntos)` : `- Detected stack: ${input.detection.name} (${input.detection.score})`);
12350
12352
  lines.push("");
@@ -12396,7 +12398,7 @@ async function initWorkspace(opts) {
12396
12398
  created.push(path14.join(sddDir, "glossary.md"));
12397
12399
  const detected = await detectIfPossible(root, opts.profilesDir);
12398
12400
  if (detected) {
12399
- const yaml2 = detectionToYaml(detected, localIso(opts.now));
12401
+ const yaml2 = detectionToYaml(detected, localStamp(opts.now));
12400
12402
  await writeText(path14.join(sddDir, "profiles", "detected.yaml"), yaml2);
12401
12403
  created.push(path14.join(sddDir, "profiles", "detected.yaml"));
12402
12404
  if (detected.best) {
@@ -12538,16 +12540,16 @@ function foldDelta(livingBody, delta, language = "es") {
12538
12540
  lines = [...lines.slice(0, range.start), ...lines.slice(range.end)];
12539
12541
  applied.removed.push(req.id);
12540
12542
  }
12541
- for (const rename of delta.renamed) {
12542
- const range = findRange(rename.from.id);
12543
+ for (const rename2 of delta.renamed) {
12544
+ const range = findRange(rename2.from.id);
12543
12545
  if (!range) {
12544
- diagnostics.push(diag("TRACE-007", "error", `No se puede renombrar ${rename.from.id}: no existe en la spec viva`, { path: delta.path, line: rename.line }));
12546
+ diagnostics.push(diag("TRACE-007", "error", `No se puede renombrar ${rename2.from.id}: no existe en la spec viva`, { path: delta.path, line: rename2.line }));
12545
12547
  continue;
12546
12548
  }
12547
12549
  const header = lines[range.start] ?? "";
12548
12550
  const label = /Requirement/i.test(header) ? "Requirement" : "Requisito";
12549
- lines[range.start] = header.replace(REQ_HEADER_RE, (_match, id) => `### ${label}: ${id} \u2014 ${rename.to.title}`);
12550
- applied.renamed.push(rename.from.id);
12551
+ lines[range.start] = header.replace(REQ_HEADER_RE, (_match, id) => `### ${label}: ${id} \u2014 ${rename2.to.title}`);
12552
+ applied.renamed.push(rename2.from.id);
12551
12553
  }
12552
12554
  for (const req of delta.added) {
12553
12555
  const rendered = renderRequirement(req, language);
@@ -12562,6 +12564,46 @@ function trimTrailingBlanks(lines) {
12562
12564
  while (out.length > 0 && (out[out.length - 1] ?? "").trim() === "") out.pop();
12563
12565
  return out;
12564
12566
  }
12567
+ var TRANSIENT_MOVE_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "ENOTEMPTY", "EACCES"]);
12568
+ function delay(ms) {
12569
+ return new Promise((resolve) => setTimeout(resolve, ms));
12570
+ }
12571
+ async function moveDirectory(from, to, ops = {}) {
12572
+ const doRename = ops.rename ?? rename;
12573
+ const doCopy = ops.cp ?? cp;
12574
+ const doRemove = ops.rm ?? rm;
12575
+ let lastError;
12576
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
12577
+ try {
12578
+ await doRename(from, to);
12579
+ return { strategy: "rename" };
12580
+ } catch (error) {
12581
+ lastError = error;
12582
+ const code = error.code ?? "";
12583
+ if (!TRANSIENT_MOVE_CODES.has(code)) throw error;
12584
+ await delay(250 * attempt);
12585
+ }
12586
+ }
12587
+ await doCopy(from, to, { recursive: true, force: true });
12588
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
12589
+ try {
12590
+ await doRemove(from, { recursive: true, force: true, maxRetries: 2 });
12591
+ return { strategy: "copy" };
12592
+ } catch (error) {
12593
+ lastError = error;
12594
+ await delay(350 * attempt);
12595
+ }
12596
+ }
12597
+ await doRemove(to, { recursive: true, force: true }).catch(() => void 0);
12598
+ throw lastError;
12599
+ }
12600
+ async function restoreFile(file, previous) {
12601
+ try {
12602
+ if (previous === void 0) await fs.rm(file, { force: true });
12603
+ else await writeText(file, previous);
12604
+ } catch {
12605
+ }
12606
+ }
12565
12607
  async function archiveChange(opts) {
12566
12608
  const root = path16.resolve(opts.root);
12567
12609
  const diagnostics = [];
@@ -12586,7 +12628,17 @@ async function archiveChange(opts) {
12586
12628
  }
12587
12629
  if (!opts.dryRun) {
12588
12630
  await ensureDir(path16.dirname(targetFix));
12589
- await fs.rename(change.dir, targetFix);
12631
+ try {
12632
+ await moveDirectory(change.dir, targetFix);
12633
+ } catch (error) {
12634
+ diagnostics.push(
12635
+ diag("ATLAS-ARCH-004", "error", `No se pudo mover el cambio al hist\xF3rico: ${error.message}`, {
12636
+ path: change.dir,
12637
+ suggestion: "Cierra las pesta\xF1as con archivos de este cambio (y espera unos segundos si OneDrive est\xE1 sincronizando) y vuelve a intentar"
12638
+ })
12639
+ );
12640
+ return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: false };
12641
+ }
12590
12642
  await regenerateIndex(root, config);
12591
12643
  }
12592
12644
  return { slug: opts.slug, archivedTo: targetFix, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
@@ -12639,7 +12691,18 @@ ${body}`;
12639
12691
  if (!opts.dryRun) {
12640
12692
  await writeText(specFile, nextContent);
12641
12693
  await ensureDir(archiveDir);
12642
- await fs.rename(change.dir, target);
12694
+ try {
12695
+ await moveDirectory(change.dir, target);
12696
+ } catch (error) {
12697
+ await restoreFile(specFile, existing);
12698
+ diagnostics.push(
12699
+ diag("ATLAS-ARCH-004", "error", `No se pudo mover el cambio al hist\xF3rico: ${error.message}`, {
12700
+ path: change.dir,
12701
+ suggestion: "Cierra las pesta\xF1as con archivos de este cambio (y espera unos segundos si OneDrive est\xE1 sincronizando) y vuelve a intentar; el cambio y la spec viva quedaron como estaban"
12702
+ })
12703
+ );
12704
+ return { slug: opts.slug, domain, fold, diagnostics, dryRun: false };
12705
+ }
12643
12706
  await regenerateIndex(root, config, now);
12644
12707
  }
12645
12708
  return { slug: opts.slug, domain, archivedTo: target, fold, diagnostics, dryRun: opts.dryRun ?? false };
@@ -5,7 +5,7 @@ import {
5
5
  writeText
6
6
  } from "./chunk-DKFIPJ74.js";
7
7
 
8
- // ../core/dist/chunk-CTT5JQA5.js
8
+ // ../core/dist/chunk-WF6H5ITI.js
9
9
  import path from "path";
10
10
  import { stringify as stringifyYaml } from "yaml";
11
11
  import { createHash } from "crypto";
@@ -20,12 +20,17 @@ function localDate(date = /* @__PURE__ */ new Date()) {
20
20
  function localMonth(date = /* @__PURE__ */ new Date()) {
21
21
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}`;
22
22
  }
23
- function localIso(date = /* @__PURE__ */ new Date()) {
23
+ function localOffset(date = /* @__PURE__ */ new Date()) {
24
24
  const offset = -date.getTimezoneOffset();
25
25
  const sign = offset >= 0 ? "+" : "-";
26
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)}`;
27
+ return `${sign}${pad(Math.floor(absolute / 60))}:${pad(absolute % 60)}`;
28
+ }
29
+ function localStamp(date = /* @__PURE__ */ new Date()) {
30
+ return `${localDate(date)} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())} ${localOffset(date)}`;
31
+ }
32
+ function localCompact(date = /* @__PURE__ */ new Date()) {
33
+ return `${localDate(date).replace(/-/g, "")}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
29
34
  }
30
35
  function diag(code, severity, message, opts = {}) {
31
36
  const d = { code, severity, message };
@@ -205,7 +210,7 @@ async function signApproval(opts) {
205
210
  artifact: rel,
206
211
  artifactHash: artifactHash(content),
207
212
  approvedBy: opts.by.trim(),
208
- approvedAt: localIso(now),
213
+ approvedAt: localStamp(now),
209
214
  channel: opts.channel ?? "cli"
210
215
  };
211
216
  if (opts.note) approval.note = opts.note;
@@ -234,7 +239,8 @@ async function signApproval(opts) {
234
239
  export {
235
240
  localDate,
236
241
  localMonth,
237
- localIso,
242
+ localStamp,
243
+ localCompact,
238
244
  diag,
239
245
  countBySeverity,
240
246
  sha256,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specatlas",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
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.11"
43
+ "@specatlas/core": "0.1.13"
44
44
  },
45
45
  "scripts": {
46
46
  "typecheck": "tsc --noEmit",