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.
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-
|
|
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
|
|
9623
|
-
if (
|
|
9624
|
-
out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${
|
|
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(
|
|
9627
|
-
out.push(diag("TRACE-007", "error", `RENAMED ${
|
|
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-
|
|
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:
|
|
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 =
|
|
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:
|
|
11044
|
-
updatedAt:
|
|
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:
|
|
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 =
|
|
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:
|
|
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:
|
|
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,
|
|
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:
|
|
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:
|
|
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: ${
|
|
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,
|
|
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
|
|
12542
|
-
const range = findRange(
|
|
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 ${
|
|
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 ${
|
|
12550
|
-
applied.renamed.push(
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
43
|
+
"@specatlas/core": "0.1.13"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"typecheck": "tsc --noEmit",
|