@specatlas/core 0.1.11 → 0.1.13

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,6 +1,6 @@
1
1
  import {
2
2
  signApproval
3
- } from "./chunk-CTT5JQA5.js";
3
+ } from "./chunk-WF6H5ITI.js";
4
4
  import "./chunk-5RKNY2ZC.js";
5
5
  export {
6
6
  signApproval
@@ -17,12 +17,17 @@ function localDate(date = /* @__PURE__ */ new Date()) {
17
17
  function localMonth(date = /* @__PURE__ */ new Date()) {
18
18
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}`;
19
19
  }
20
- function localIso(date = /* @__PURE__ */ new Date()) {
20
+ function localOffset(date = /* @__PURE__ */ new Date()) {
21
21
  const offset = -date.getTimezoneOffset();
22
22
  const sign = offset >= 0 ? "+" : "-";
23
23
  const absolute = Math.abs(offset);
24
- const time = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`;
25
- return `${localDate(date)}T${time}${sign}${pad(Math.floor(absolute / 60))}:${pad(absolute % 60)}`;
24
+ return `${sign}${pad(Math.floor(absolute / 60))}:${pad(absolute % 60)}`;
25
+ }
26
+ function localStamp(date = /* @__PURE__ */ new Date()) {
27
+ return `${localDate(date)} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())} ${localOffset(date)}`;
28
+ }
29
+ function localCompact(date = /* @__PURE__ */ new Date()) {
30
+ return `${localDate(date).replace(/-/g, "")}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
26
31
  }
27
32
 
28
33
  // src/approvals.ts
@@ -219,7 +224,7 @@ async function signApproval(opts) {
219
224
  artifact: rel,
220
225
  artifactHash: artifactHash(content),
221
226
  approvedBy: opts.by.trim(),
222
- approvedAt: localIso(now),
227
+ approvedAt: localStamp(now),
223
228
  channel: opts.channel ?? "cli"
224
229
  };
225
230
  if (opts.note) approval.note = opts.note;
@@ -255,7 +260,9 @@ export {
255
260
  shortHash,
256
261
  localDate,
257
262
  localMonth,
258
- localIso,
263
+ localOffset,
264
+ localStamp,
265
+ localCompact,
259
266
  changeMetaSchema,
260
267
  parseChangeMeta,
261
268
  approvalsSchema,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { rename, cp, rm } from 'node:fs/promises';
2
3
  export { defaultTokens, renderDocument, renderStyles } from '@specatlas/render';
3
4
 
4
5
  declare const CORE_VERSION = "0.0.1";
@@ -590,7 +591,9 @@ declare function shortHash(hash: string): string;
590
591
 
591
592
  declare function localDate(date?: Date): string;
592
593
  declare function localMonth(date?: Date): string;
593
- declare function localIso(date?: Date): string;
594
+ declare function localOffset(date?: Date): string;
595
+ declare function localStamp(date?: Date): string;
596
+ declare function localCompact(date?: Date): string;
594
597
 
595
598
  declare const REQ_HEAD_RE: RegExp;
596
599
  declare const SCENARIO_HEAD_RE: RegExp;
@@ -1641,6 +1644,14 @@ interface FoldOutcome {
1641
1644
  diagnostics: Diagnostic[];
1642
1645
  }
1643
1646
  declare function foldDelta(livingBody: string, delta: Delta, language?: Language): FoldOutcome;
1647
+ interface MoveOps {
1648
+ rename?: typeof rename;
1649
+ cp?: typeof cp;
1650
+ rm?: typeof rm;
1651
+ }
1652
+ declare function moveDirectory(from: string, to: string, ops?: MoveOps): Promise<{
1653
+ strategy: 'rename' | 'copy';
1654
+ }>;
1644
1655
  interface ArchiveOptions {
1645
1656
  root: string;
1646
1657
  slug: string;
@@ -1698,4 +1709,4 @@ declare function livingRequirementsMap(specs: Array<{
1698
1709
  }>): Map<string, Requirement>;
1699
1710
  declare function runCiGate(opts: CiOptions): Promise<CiResult>;
1700
1711
 
1701
- export { type AdoptDomain, type AdoptOptions, type AdoptResult, type AgingBucket, type AnalyzeOptions, type AnalyzePackSummary, type AnalyzeResult, type Approval, type ApprovalStatus, type ApprovalsFile, type ApprovalsIndex, type ApproveFromGithubOptions, type ApproveFromGithubResult, type ArchiveOptions, type ArchiveResult, type AtlasConfig, type AttentionItem, BLOCK_HEAD_RE, BUILTIN_PACKS, type BlockWavePlan, CONFIG_FILE, CORE_VERSION, type CaptureResult, type Change, type ChangeMeta, type ChangeMetrics, type ChangeState, type CiCheck, type CiExtraCheck, type CiOptions, type CiResult, type Delta, type DeltaOp, type DeriveInput, type DerivedState, type DetectionResult, type Diagnostic, type DoctorReport, type Domain, type Evidence, type EvidenceMethod, type EvidenceResult, type ExecOptions, type ExecResult, type FoldOutcome, type FrontmatterResult, type GhRunner, type GitHubRepo, type InitOptions, type InitResult, type IssueBodyInput, type Lane, type Language, type LinkedIssue, type LintOptions, type LoadedConfig, type MockupCheckResult, type MockupManifest, type MockupPlan, type MockupPlanScreen, type MockupScreen, type NewChangeOptions, type NewChangeResult, type NextAction, type Override, type Pack, type PackCheck, type PackCheckResult, type PackCheckType, type PackEvaluation, type PresentOptions, type PresentResult, type ProfileMatch, REQ_HEAD_RE, REQ_ID_RE, RULE_ID_RE, RULE_RE, RUN_EVENT_TYPES, type RecordEvidenceOptions, type RecordEvidenceResult, type Rename, type Requirement, type RiskLevel, type Rule, type RunEvent, type RunEventType, type RunRecord, type RunState, type RunStatus, SCENARIO_HEAD_RE, SCENARIO_ID_RE, SDD_DIR, SLUG_RE, type Scenario, type Severity, type SignApprovalOptions, type SignApprovalResult, type SpecFile, type SpecRef, type StackProfile, type SyncGithubOptions, type SyncGithubResult, TASK_ID_RE, TASK_LINE_RE, type Task, type TaskBlock, type TasksFile, type TemplateSet, type TraceEdge, type TraceFinding, type TraceGraph, type TraceInput, type TraceNode, type TraceResult, type VerifyFile, type WalkEntry, type WavePlan, type Workspace, type WorkspaceMetrics, adoptWorkspace, appendRunEvent, approvalsSchema, approveFromGithub, archiveChange, artifactHash, atlasConfigSchema, buildTraceGraph, canonicalizeMarkdown, captureMockups, changeMarker, changeMetaSchema, changeMetaYaml, checkMockups, checkTrace, collectMetrics, commentIssue, compareTaskIds, computeInputsHash, configToYaml, copyFile, countBySeverity, createChange, createIssue, createRun, defaultConfig, deriveState, detectProfiles, detectRepo, detectionToYaml, diag, editIssue, emitFrontmatter, ensureDir, ensureSddDirs, esc, evaluatePacks, evidenceSummary, exists, findLinkedIssue, findWorkspaceRoot, firstToken, foldDelta, generatePresentation, generateRunId, getNumber, getString, ghAuthStatus, ghAvailable, hasErrors, hasShellMetacharacters, indexMarkdown, initWorkspace, inline, isCommandAllowed, isDirectory, issueBody, issueLabels, laneOrDefault, lintDelta, lintMockupHtml, lintMockupManifest, lintRequirement, lintSpec, lintTasks, lintVerify, listChangeSlugs, listDir, listDirs, listRuns, livingRequirementsMap, loadActiveProfile, loadApprovals, loadChange, loadConfig, loadDetectedBest, loadProfileFile, loadProfilesFromDir, loadProjectPacks, loadSpecs, loadWorkspace, localDate, localIso, localMonth, matchGlob, mockupManifestSchema, mockupsDir, mockupsReady, packFindings, parseApprovals, parseChangeMeta, parseConfig, parseDelta, parseFrontmatter, parseGitHubRemote, parseIssueUrl, parseRequirementBlocks, parseSpecFile, parseTasksFile, parseVerifyFile, patchChangeMeta, planBlock, planMockups, planWaves, profileSchema, readMockupManifest, readRun, readText, readTextIfExists, recordEvidence, regenerateIndex, renderMarkdown, renderRequirement, renderTemplate, requiresMockups, resolvePacks, runAnalyze, runCiGate, runDoctor, runProcess, setMockupRequirement, sha256, shortHash, signApproval, specHashOf, splitCommand, stateLabel, syncGithubIssue, templatesFor, toPosix, tokensFile, updateMockupScreenshots, updateRunStatus, verifyApproval, walkFiles, writeConfig, writeMockupManifest, writeMockupPlan, writeText };
1712
+ export { type AdoptDomain, type AdoptOptions, type AdoptResult, type AgingBucket, type AnalyzeOptions, type AnalyzePackSummary, type AnalyzeResult, type Approval, type ApprovalStatus, type ApprovalsFile, type ApprovalsIndex, type ApproveFromGithubOptions, type ApproveFromGithubResult, type ArchiveOptions, type ArchiveResult, type AtlasConfig, type AttentionItem, BLOCK_HEAD_RE, BUILTIN_PACKS, type BlockWavePlan, CONFIG_FILE, CORE_VERSION, type CaptureResult, type Change, type ChangeMeta, type ChangeMetrics, type ChangeState, type CiCheck, type CiExtraCheck, type CiOptions, type CiResult, type Delta, type DeltaOp, type DeriveInput, type DerivedState, type DetectionResult, type Diagnostic, type DoctorReport, type Domain, type Evidence, type EvidenceMethod, type EvidenceResult, type ExecOptions, type ExecResult, type FoldOutcome, type FrontmatterResult, type GhRunner, type GitHubRepo, type InitOptions, type InitResult, type IssueBodyInput, type Lane, type Language, type LinkedIssue, type LintOptions, type LoadedConfig, type MockupCheckResult, type MockupManifest, type MockupPlan, type MockupPlanScreen, type MockupScreen, type MoveOps, type NewChangeOptions, type NewChangeResult, type NextAction, type Override, type Pack, type PackCheck, type PackCheckResult, type PackCheckType, type PackEvaluation, type PresentOptions, type PresentResult, type ProfileMatch, REQ_HEAD_RE, REQ_ID_RE, RULE_ID_RE, RULE_RE, RUN_EVENT_TYPES, type RecordEvidenceOptions, type RecordEvidenceResult, type Rename, type Requirement, type RiskLevel, type Rule, type RunEvent, type RunEventType, type RunRecord, type RunState, type RunStatus, SCENARIO_HEAD_RE, SCENARIO_ID_RE, SDD_DIR, SLUG_RE, type Scenario, type Severity, type SignApprovalOptions, type SignApprovalResult, type SpecFile, type SpecRef, type StackProfile, type SyncGithubOptions, type SyncGithubResult, TASK_ID_RE, TASK_LINE_RE, type Task, type TaskBlock, type TasksFile, type TemplateSet, type TraceEdge, type TraceFinding, type TraceGraph, type TraceInput, type TraceNode, type TraceResult, type VerifyFile, type WalkEntry, type WavePlan, type Workspace, type WorkspaceMetrics, adoptWorkspace, appendRunEvent, approvalsSchema, approveFromGithub, archiveChange, artifactHash, atlasConfigSchema, buildTraceGraph, canonicalizeMarkdown, captureMockups, changeMarker, changeMetaSchema, changeMetaYaml, checkMockups, checkTrace, collectMetrics, commentIssue, compareTaskIds, computeInputsHash, configToYaml, copyFile, countBySeverity, createChange, createIssue, createRun, defaultConfig, deriveState, detectProfiles, detectRepo, detectionToYaml, diag, editIssue, emitFrontmatter, ensureDir, ensureSddDirs, esc, evaluatePacks, evidenceSummary, exists, findLinkedIssue, findWorkspaceRoot, firstToken, foldDelta, generatePresentation, generateRunId, getNumber, getString, ghAuthStatus, ghAvailable, hasErrors, hasShellMetacharacters, indexMarkdown, initWorkspace, inline, isCommandAllowed, isDirectory, issueBody, issueLabels, laneOrDefault, lintDelta, lintMockupHtml, lintMockupManifest, lintRequirement, lintSpec, lintTasks, lintVerify, listChangeSlugs, listDir, listDirs, listRuns, livingRequirementsMap, loadActiveProfile, loadApprovals, loadChange, loadConfig, loadDetectedBest, loadProfileFile, loadProfilesFromDir, loadProjectPacks, loadSpecs, loadWorkspace, localCompact, localDate, localMonth, localOffset, localStamp, matchGlob, mockupManifestSchema, mockupsDir, mockupsReady, moveDirectory, packFindings, parseApprovals, parseChangeMeta, parseConfig, parseDelta, parseFrontmatter, parseGitHubRemote, parseIssueUrl, parseRequirementBlocks, parseSpecFile, parseTasksFile, parseVerifyFile, patchChangeMeta, planBlock, planMockups, planWaves, profileSchema, readMockupManifest, readRun, readText, readTextIfExists, recordEvidence, regenerateIndex, renderMarkdown, renderRequirement, renderTemplate, requiresMockups, resolvePacks, runAnalyze, runCiGate, runDoctor, runProcess, setMockupRequirement, sha256, shortHash, signApproval, specHashOf, splitCommand, stateLabel, syncGithubIssue, templatesFor, toPosix, tokensFile, updateMockupScreenshots, updateRunStatus, verifyApproval, walkFiles, writeConfig, writeMockupManifest, writeMockupPlan, writeText };
package/dist/index.js CHANGED
@@ -6,15 +6,17 @@ import {
6
6
  countBySeverity,
7
7
  diag,
8
8
  hasErrors,
9
+ localCompact,
9
10
  localDate,
10
- localIso,
11
11
  localMonth,
12
+ localOffset,
13
+ localStamp,
12
14
  parseApprovals,
13
15
  parseChangeMeta,
14
16
  sha256,
15
17
  shortHash,
16
18
  signApproval
17
- } from "./chunk-CTT5JQA5.js";
19
+ } from "./chunk-WF6H5ITI.js";
18
20
  import {
19
21
  copyFile,
20
22
  ensureDir,
@@ -714,12 +716,12 @@ function lintDelta(delta, livingRequirements, path19, opts = {}) {
714
716
  out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path19, line: req.line }));
715
717
  }
716
718
  }
717
- for (const rename of delta.renamed) {
718
- if (rename.from.id !== rename.to.id) {
719
- out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename.from.id} \u2192 ${rename.to.id}); los ids son inmutables`, { path: path19, line: rename.line }));
719
+ for (const rename2 of delta.renamed) {
720
+ if (rename2.from.id !== rename2.to.id) {
721
+ out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path19, line: rename2.line }));
720
722
  }
721
- if (!livingRequirements.has(rename.from.id)) {
722
- out.push(diag("TRACE-007", "error", `RENAMED ${rename.from.id} no existe en la spec viva`, { path: path19, line: rename.line }));
723
+ if (!livingRequirements.has(rename2.from.id)) {
724
+ out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path19, line: rename2.line }));
723
725
  }
724
726
  }
725
727
  return out;
@@ -1972,7 +1974,7 @@ async function approveFromGithub(opts) {
1972
1974
  if (!by) {
1973
1975
  return { approved: false, label, issueNumber: number, diagnostics: [diag("ATLAS-GH-011", "error", "No se pudo determinar qui\xE9n aprueba (--by ni `gh api user`)")] };
1974
1976
  }
1975
- const { signApproval: signApproval2 } = await import("./approvals-XPI466UK.js");
1977
+ const { signApproval: signApproval2 } = await import("./approvals-2PO6223I.js");
1976
1978
  const signed = await signApproval2({
1977
1979
  root: opts.root,
1978
1980
  artifact: path5.posix.join("changes", opts.slug, "spec.md"),
@@ -2125,7 +2127,7 @@ ${exec.stderr}`);
2125
2127
  scenario,
2126
2128
  method,
2127
2129
  result,
2128
- date: localIso(opts.now),
2130
+ date: localStamp(opts.now),
2129
2131
  by: opts.by.trim(),
2130
2132
  line: 0
2131
2133
  };
@@ -2195,7 +2197,7 @@ var RUN_EVENT_TYPES = [
2195
2197
  "run_completed"
2196
2198
  ];
2197
2199
  function generateRunId(now = /* @__PURE__ */ new Date()) {
2198
- const iso = localIso(now).replace(/[-+:.Z]/g, "").slice(0, 15);
2200
+ const iso = localCompact(now);
2199
2201
  return `${iso.slice(0, 8)}-${iso.slice(8, 14)}-${randomBytes(3).toString("hex")}`;
2200
2202
  }
2201
2203
  function runsDir(root) {
@@ -2208,8 +2210,8 @@ async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Da
2208
2210
  slug,
2209
2211
  phase,
2210
2212
  status: "active",
2211
- startedAt: localIso(now),
2212
- updatedAt: localIso(now)
2213
+ startedAt: localStamp(now),
2214
+ updatedAt: localStamp(now)
2213
2215
  };
2214
2216
  if (inputs) state.inputs = inputs;
2215
2217
  const dir = path7.join(runsDir(root), runId);
@@ -2221,7 +2223,7 @@ async function createRun(root, slug, phase, inputs, now = /* @__PURE__ */ new Da
2221
2223
  return state;
2222
2224
  }
2223
2225
  async function appendRunEvent(root, runId, type, data, now = /* @__PURE__ */ new Date()) {
2224
- const event = { eventId: randomBytes(4).toString("hex"), at: localIso(now), type };
2226
+ const event = { eventId: randomBytes(4).toString("hex"), at: localStamp(now), type };
2225
2227
  if (data) event.data = data;
2226
2228
  const file = path7.join(runsDir(root), runId, "events.jsonl");
2227
2229
  const previous = await readTextIfExists(file) ?? "";
@@ -2235,7 +2237,7 @@ async function updateRunStatus(root, runId, status, now = /* @__PURE__ */ new Da
2235
2237
  if (raw === void 0) return;
2236
2238
  const state = JSON.parse(raw);
2237
2239
  state.status = status;
2238
- state.updatedAt = localIso(now);
2240
+ state.updatedAt = localStamp(now);
2239
2241
  await writeText(file, `${JSON.stringify(state, null, 2)}
2240
2242
  `);
2241
2243
  }
@@ -2468,7 +2470,7 @@ async function writeMockupPlan(root, slug, plan, now = /* @__PURE__ */ new Date(
2468
2470
  if (await exists(file)) return file;
2469
2471
  const doc = {
2470
2472
  schema_version: 1,
2471
- generated_at: localIso(now),
2473
+ generated_at: localStamp(now),
2472
2474
  platform: plan.platform,
2473
2475
  screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints }))
2474
2476
  };
@@ -2484,7 +2486,7 @@ async function writeMockupManifest(root, slug, plan, inputsHash, now = /* @__PUR
2484
2486
  level: "hifi",
2485
2487
  platform: plan.platform,
2486
2488
  inputs_hash: inputsHash,
2487
- generated_at: localIso(now),
2489
+ generated_at: localStamp(now),
2488
2490
  screens: plan.screens.map((s) => ({ id: s.id, title: s.title, file: s.file, illustrates: s.illustrates, states: s.states, breakpoints: s.breakpoints })),
2489
2491
  screenshots: []
2490
2492
  };
@@ -2933,7 +2935,7 @@ async function runAnalyze(opts) {
2933
2935
  if (packs) result.packs = packs;
2934
2936
  if (opts.write !== false) {
2935
2937
  const file = path10.join(change.dir, "analyze.md");
2936
- await writeText(file, renderAnalyze(change, result, localIso(opts.now)));
2938
+ await writeText(file, renderAnalyze(change, result, localStamp(opts.now)));
2937
2939
  result.path = file;
2938
2940
  }
2939
2941
  return result;
@@ -3075,7 +3077,7 @@ async function collectMetrics(root, now = /* @__PURE__ */ new Date()) {
3075
3077
  archived: archivedEntries.length
3076
3078
  };
3077
3079
  return {
3078
- generatedAt: localIso(now),
3080
+ generatedAt: localStamp(now),
3079
3081
  project: config.project.name,
3080
3082
  root: resolved,
3081
3083
  changes,
@@ -3135,7 +3137,7 @@ async function generatePresentation(opts) {
3135
3137
  project: config.project.name,
3136
3138
  version: change.delta.sectionsFound.length,
3137
3139
  hash,
3138
- generatedAt: localIso(now),
3140
+ generatedAt: localStamp(now),
3139
3141
  proposalHtml: proposalBody ? renderMarkdown(proposalBody) : `<p><em>${labels.noProposal}</em></p>`,
3140
3142
  requirements: change.delta ? [...change.delta.added.map((r) => ({ req: r, kind: "added" })), ...change.delta.modified.map((r) => ({ req: r, kind: "modified" }))] : [],
3141
3143
  evidence: new Map((change.verify?.evidence ?? []).map((e) => [e.scenario, e.result])),
@@ -3538,7 +3540,7 @@ function adoptReport(input) {
3538
3540
  lines.push(es ? "# Adopci\xF3n (brownfield)" : "# Adoption (brownfield)");
3539
3541
  lines.push("");
3540
3542
  lines.push(es ? `- Proyecto: ${input.config.project.name}` : `- Project: ${input.config.project.name}`);
3541
- lines.push(es ? `- Generado: ${localIso(input.now)}` : `- Generated: ${localIso(input.now)}`);
3543
+ lines.push(es ? `- Generado: ${localStamp(input.now)}` : `- Generated: ${localStamp(input.now)}`);
3542
3544
  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)`);
3543
3545
  if (input.detection) lines.push(es ? `- Stack detectado: ${input.detection.name} (${input.detection.score} puntos)` : `- Detected stack: ${input.detection.name} (${input.detection.score})`);
3544
3546
  lines.push("");
@@ -3593,7 +3595,7 @@ async function initWorkspace(opts) {
3593
3595
  created.push(path14.join(sddDir, "glossary.md"));
3594
3596
  const detected = await detectIfPossible(root, opts.profilesDir);
3595
3597
  if (detected) {
3596
- const yaml = detectionToYaml(detected, localIso(opts.now));
3598
+ const yaml = detectionToYaml(detected, localStamp(opts.now));
3597
3599
  await writeText(path14.join(sddDir, "profiles", "detected.yaml"), yaml);
3598
3600
  created.push(path14.join(sddDir, "profiles", "detected.yaml"));
3599
3601
  if (detected.best) {
@@ -3698,6 +3700,7 @@ async function createChange(opts) {
3698
3700
 
3699
3701
  // src/archive.ts
3700
3702
  import { promises as fs } from "fs";
3703
+ import { cp, rename, rm } from "fs/promises";
3701
3704
  import path16 from "path";
3702
3705
  var REQ_HEADER_RE = /^###\s+(?:Requisito|Requirement):\s+(REQ-[A-Z0-9-]+)\s*(?:—|-|–)\s*(.*)$/;
3703
3706
  function foldDelta(livingBody, delta, language = "es") {
@@ -3742,16 +3745,16 @@ function foldDelta(livingBody, delta, language = "es") {
3742
3745
  lines = [...lines.slice(0, range.start), ...lines.slice(range.end)];
3743
3746
  applied.removed.push(req.id);
3744
3747
  }
3745
- for (const rename of delta.renamed) {
3746
- const range = findRange(rename.from.id);
3748
+ for (const rename2 of delta.renamed) {
3749
+ const range = findRange(rename2.from.id);
3747
3750
  if (!range) {
3748
- 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 }));
3751
+ 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 }));
3749
3752
  continue;
3750
3753
  }
3751
3754
  const header = lines[range.start] ?? "";
3752
3755
  const label = /Requirement/i.test(header) ? "Requirement" : "Requisito";
3753
- lines[range.start] = header.replace(REQ_HEADER_RE, (_match, id) => `### ${label}: ${id} \u2014 ${rename.to.title}`);
3754
- applied.renamed.push(rename.from.id);
3756
+ lines[range.start] = header.replace(REQ_HEADER_RE, (_match, id) => `### ${label}: ${id} \u2014 ${rename2.to.title}`);
3757
+ applied.renamed.push(rename2.from.id);
3755
3758
  }
3756
3759
  for (const req of delta.added) {
3757
3760
  const rendered = renderRequirement(req, language);
@@ -3766,6 +3769,46 @@ function trimTrailingBlanks(lines) {
3766
3769
  while (out.length > 0 && (out[out.length - 1] ?? "").trim() === "") out.pop();
3767
3770
  return out;
3768
3771
  }
3772
+ var TRANSIENT_MOVE_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "ENOTEMPTY", "EACCES"]);
3773
+ function delay(ms) {
3774
+ return new Promise((resolve) => setTimeout(resolve, ms));
3775
+ }
3776
+ async function moveDirectory(from, to, ops = {}) {
3777
+ const doRename = ops.rename ?? rename;
3778
+ const doCopy = ops.cp ?? cp;
3779
+ const doRemove = ops.rm ?? rm;
3780
+ let lastError;
3781
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
3782
+ try {
3783
+ await doRename(from, to);
3784
+ return { strategy: "rename" };
3785
+ } catch (error) {
3786
+ lastError = error;
3787
+ const code = error.code ?? "";
3788
+ if (!TRANSIENT_MOVE_CODES.has(code)) throw error;
3789
+ await delay(250 * attempt);
3790
+ }
3791
+ }
3792
+ await doCopy(from, to, { recursive: true, force: true });
3793
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
3794
+ try {
3795
+ await doRemove(from, { recursive: true, force: true, maxRetries: 2 });
3796
+ return { strategy: "copy" };
3797
+ } catch (error) {
3798
+ lastError = error;
3799
+ await delay(350 * attempt);
3800
+ }
3801
+ }
3802
+ await doRemove(to, { recursive: true, force: true }).catch(() => void 0);
3803
+ throw lastError;
3804
+ }
3805
+ async function restoreFile(file, previous) {
3806
+ try {
3807
+ if (previous === void 0) await fs.rm(file, { force: true });
3808
+ else await writeText(file, previous);
3809
+ } catch {
3810
+ }
3811
+ }
3769
3812
  async function archiveChange(opts) {
3770
3813
  const root = path16.resolve(opts.root);
3771
3814
  const diagnostics = [];
@@ -3790,7 +3833,17 @@ async function archiveChange(opts) {
3790
3833
  }
3791
3834
  if (!opts.dryRun) {
3792
3835
  await ensureDir(path16.dirname(targetFix));
3793
- await fs.rename(change.dir, targetFix);
3836
+ try {
3837
+ await moveDirectory(change.dir, targetFix);
3838
+ } catch (error) {
3839
+ diagnostics.push(
3840
+ diag("ATLAS-ARCH-004", "error", `No se pudo mover el cambio al hist\xF3rico: ${error.message}`, {
3841
+ path: change.dir,
3842
+ suggestion: "Cierra las pesta\xF1as con archivos de este cambio (y espera unos segundos si OneDrive est\xE1 sincronizando) y vuelve a intentar"
3843
+ })
3844
+ );
3845
+ return { slug: opts.slug, fold: emptyFold, diagnostics, dryRun: false };
3846
+ }
3794
3847
  await regenerateIndex(root, config);
3795
3848
  }
3796
3849
  return { slug: opts.slug, archivedTo: targetFix, fold: emptyFold, diagnostics, dryRun: opts.dryRun ?? false };
@@ -3843,7 +3896,18 @@ ${body}`;
3843
3896
  if (!opts.dryRun) {
3844
3897
  await writeText(specFile, nextContent);
3845
3898
  await ensureDir(archiveDir);
3846
- await fs.rename(change.dir, target);
3899
+ try {
3900
+ await moveDirectory(change.dir, target);
3901
+ } catch (error) {
3902
+ await restoreFile(specFile, existing);
3903
+ diagnostics.push(
3904
+ diag("ATLAS-ARCH-004", "error", `No se pudo mover el cambio al hist\xF3rico: ${error.message}`, {
3905
+ path: change.dir,
3906
+ 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"
3907
+ })
3908
+ );
3909
+ return { slug: opts.slug, domain, fold, diagnostics, dryRun: false };
3910
+ }
3847
3911
  await regenerateIndex(root, config, now);
3848
3912
  }
3849
3913
  return { slug: opts.slug, domain, archivedTo: target, fold, diagnostics, dryRun: opts.dryRun ?? false };
@@ -4082,13 +4146,16 @@ export {
4082
4146
  loadProjectPacks,
4083
4147
  loadSpecs,
4084
4148
  loadWorkspace,
4149
+ localCompact,
4085
4150
  localDate,
4086
- localIso,
4087
4151
  localMonth,
4152
+ localOffset,
4153
+ localStamp,
4088
4154
  matchGlob,
4089
4155
  mockupManifestSchema,
4090
4156
  mockupsDir,
4091
4157
  mockupsReady,
4158
+ moveDirectory,
4092
4159
  packFindings,
4093
4160
  parseApprovals,
4094
4161
  parseChangeMeta,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specatlas/core",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Kernel determinista de SpecAtlas: parsing, validación, trazabilidad, olas y ciclo de vida",
5
5
  "license": "MIT",
6
6
  "type": "module",