@specatlas/core 0.1.22 → 0.1.24

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/index.d.ts CHANGED
@@ -1708,6 +1708,148 @@ interface ArchiveResult {
1708
1708
  declare function archiveChange(opts: ArchiveOptions): Promise<ArchiveResult>;
1709
1709
  declare function regenerateIndex(root: string, cfg?: AtlasConfig, now?: Date): Promise<void>;
1710
1710
 
1711
+ declare const SCHEMA_VERSION = 1;
1712
+ declare const BACKUP_DIRNAME = ".backup";
1713
+ declare const BACKUP_POINTER = ".latest";
1714
+ interface MigrationSpec {
1715
+ id: string;
1716
+ description: string;
1717
+ from: number;
1718
+ to: number;
1719
+ }
1720
+ declare const SCHEMA_MIGRATIONS: MigrationSpec[];
1721
+ interface UpgradeItem {
1722
+ artifact: string;
1723
+ path: string;
1724
+ from: number;
1725
+ to: number;
1726
+ migration: string;
1727
+ summary: string;
1728
+ }
1729
+ interface UpgradeIssue {
1730
+ artifact: string;
1731
+ path: string;
1732
+ reason: string;
1733
+ }
1734
+ interface UpgradePlan {
1735
+ root: string;
1736
+ currentVersion: number;
1737
+ pending: UpgradeItem[];
1738
+ newer: UpgradeIssue[];
1739
+ unreadable: UpgradeIssue[];
1740
+ upToDate: boolean;
1741
+ }
1742
+ interface FoundArtifact {
1743
+ artifact: string;
1744
+ path: string;
1745
+ }
1746
+ declare function collectVersionedArtifacts(root: string): Promise<FoundArtifact[]>;
1747
+ type ArtifactVersionState = {
1748
+ state: 'ok';
1749
+ version: number;
1750
+ } | {
1751
+ state: 'absent';
1752
+ } | {
1753
+ state: 'unreadable';
1754
+ reason: string;
1755
+ };
1756
+ declare function readArtifactVersion(content: string): ArtifactVersionState;
1757
+ declare function stampSchemaVersion(content: string, version: number): string | undefined;
1758
+ declare function planUpgrade(root: string): Promise<UpgradePlan>;
1759
+ interface UpgradeBackupInfo {
1760
+ name: string;
1761
+ dir: string;
1762
+ relativeDir: string;
1763
+ createdAt: string;
1764
+ files: string[];
1765
+ }
1766
+ interface UpgradeApplyReport {
1767
+ status: 'applied' | 'up-to-date' | 'failed';
1768
+ plan: UpgradePlan;
1769
+ applied: UpgradeItem[];
1770
+ backup?: UpgradeBackupInfo;
1771
+ failure?: {
1772
+ artifact: string;
1773
+ message: string;
1774
+ };
1775
+ }
1776
+ declare function applyUpgrade(root: string, now?: Date): Promise<UpgradeApplyReport>;
1777
+ interface UpgradeRollbackReport {
1778
+ status: 'restored' | 'no-backup' | 'failed';
1779
+ restored: string[];
1780
+ backup?: string;
1781
+ failure?: {
1782
+ artifact: string;
1783
+ message: string;
1784
+ };
1785
+ }
1786
+ declare function rollbackUpgrade(root: string): Promise<UpgradeRollbackReport>;
1787
+ interface UpgradeAdvisory {
1788
+ plan: UpgradePlan;
1789
+ diagnostics: Diagnostic[];
1790
+ }
1791
+ declare function upgradeAdvisory(root: string): Promise<UpgradeAdvisory>;
1792
+
1793
+ declare const SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
1794
+ declare const SARIF_VERSION = "2.1.0";
1795
+ declare const TOOL_NAME = "SpecAtlas";
1796
+ declare const TOOL_URL = "https://github.com/AlonsoAM/specatlas";
1797
+ interface SarifOptions {
1798
+ diagnostics: readonly Diagnostic[];
1799
+ root: string;
1800
+ version: string;
1801
+ failed?: boolean;
1802
+ }
1803
+ interface SarifRule {
1804
+ id: string;
1805
+ shortDescription: {
1806
+ text: string;
1807
+ };
1808
+ help?: {
1809
+ text: string;
1810
+ };
1811
+ }
1812
+ interface SarifLocation {
1813
+ physicalLocation: {
1814
+ artifactLocation: {
1815
+ uri: string;
1816
+ };
1817
+ region?: {
1818
+ startLine: number;
1819
+ };
1820
+ };
1821
+ }
1822
+ interface SarifResult {
1823
+ ruleId: string;
1824
+ level: 'error' | 'warning' | 'note';
1825
+ message: {
1826
+ text: string;
1827
+ };
1828
+ locations?: SarifLocation[];
1829
+ }
1830
+ interface SarifReport {
1831
+ $schema: string;
1832
+ version: string;
1833
+ runs: Array<{
1834
+ tool: {
1835
+ driver: {
1836
+ name: string;
1837
+ informationUri: string;
1838
+ version: string;
1839
+ rules: SarifRule[];
1840
+ };
1841
+ };
1842
+ results: SarifResult[];
1843
+ invocations: Array<{
1844
+ executionSuccessful: boolean;
1845
+ }>;
1846
+ }>;
1847
+ }
1848
+ declare function ruleDescription(code: string): string;
1849
+ declare function sarifLevel(severity: Diagnostic['severity']): SarifResult['level'];
1850
+ declare function toSarifReport(opts: SarifOptions): SarifReport;
1851
+ declare function toSarifText(opts: SarifOptions): string;
1852
+
1711
1853
  interface DoctorReport {
1712
1854
  findings: Diagnostic[];
1713
1855
  summary: {
@@ -1747,4 +1889,4 @@ declare function livingRequirementsMap(specs: Array<{
1747
1889
  }>): Map<string, Requirement>;
1748
1890
  declare function runCiGate(opts: CiOptions): Promise<CiResult>;
1749
1891
 
1750
- 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 GlossaryTerm, type ImpactEvidence, type ImpactReport, type ImpactTask, 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 ParsedGlossary, 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, impactOfFile, impactOfRequirement, indexMarkdown, initWorkspace, inline, isCommandAllowed, isDirectory, issueBody, issueLabels, laneOrDefault, lintDelta, lintMockupHtml, lintMockupManifest, lintPlan, 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, parseGlossary, 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 };
1892
+ 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, BACKUP_DIRNAME, BACKUP_POINTER, 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 GlossaryTerm, type ImpactEvidence, type ImpactReport, type ImpactTask, type InitOptions, type InitResult, type IssueBodyInput, type Lane, type Language, type LinkedIssue, type LintOptions, type LoadedConfig, type MigrationSpec, 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 ParsedGlossary, 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, SARIF_SCHEMA, SARIF_VERSION, SCENARIO_HEAD_RE, SCENARIO_ID_RE, SCHEMA_MIGRATIONS, SCHEMA_VERSION, SDD_DIR, SLUG_RE, type SarifLocation, type SarifOptions, type SarifReport, type SarifResult, type SarifRule, type Scenario, type Severity, type SignApprovalOptions, type SignApprovalResult, type SpecFile, type SpecRef, type StackProfile, type SyncGithubOptions, type SyncGithubResult, TASK_ID_RE, TASK_LINE_RE, TOOL_NAME, TOOL_URL, type Task, type TaskBlock, type TasksFile, type TemplateSet, type TraceEdge, type TraceFinding, type TraceGraph, type TraceInput, type TraceNode, type TraceResult, type UpgradeAdvisory, type UpgradeApplyReport, type UpgradeBackupInfo, type UpgradeIssue, type UpgradeItem, type UpgradePlan, type UpgradeRollbackReport, type VerifyFile, type WalkEntry, type WavePlan, type Workspace, type WorkspaceMetrics, adoptWorkspace, appendRunEvent, applyUpgrade, approvalsSchema, approveFromGithub, archiveChange, artifactHash, atlasConfigSchema, buildTraceGraph, canonicalizeMarkdown, captureMockups, changeMarker, changeMetaSchema, changeMetaYaml, checkMockups, checkTrace, collectMetrics, collectVersionedArtifacts, 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, impactOfFile, impactOfRequirement, indexMarkdown, initWorkspace, inline, isCommandAllowed, isDirectory, issueBody, issueLabels, laneOrDefault, lintDelta, lintMockupHtml, lintMockupManifest, lintPlan, 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, parseGlossary, parseIssueUrl, parseRequirementBlocks, parseSpecFile, parseTasksFile, parseVerifyFile, patchChangeMeta, planBlock, planMockups, planUpgrade, planWaves, profileSchema, readArtifactVersion, readMockupManifest, readRun, readText, readTextIfExists, recordEvidence, regenerateIndex, renderMarkdown, renderRequirement, renderTemplate, requiresMockups, resolvePacks, rollbackUpgrade, ruleDescription, runAnalyze, runCiGate, runDoctor, runProcess, sarifLevel, setMockupRequirement, sha256, shortHash, signApproval, specHashOf, splitCommand, stampSchemaVersion, stateLabel, syncGithubIssue, templatesFor, toPosix, toSarifReport, toSarifText, tokensFile, updateMockupScreenshots, updateRunStatus, upgradeAdvisory, verifyApproval, walkFiles, writeConfig, writeMockupManifest, writeMockupPlan, writeText };
package/dist/index.js CHANGED
@@ -794,14 +794,14 @@ function wordBoundary(text, term) {
794
794
  const re = new RegExp(`(?<![\\p{L}\\p{N}])${escaped}(?![\\p{L}\\p{N}])`, "iu");
795
795
  return re.test(text);
796
796
  }
797
- function lintText(text, code, terms, label, path19, line) {
797
+ function lintText(text, code, terms, label, path21, line) {
798
798
  const out = [];
799
799
  const lower = text.toLowerCase();
800
800
  for (const term of terms) {
801
801
  if (wordBoundary(lower, term)) {
802
802
  out.push(
803
803
  diag(code, "error", `${label}: "${term}"`, {
804
- path: path19,
804
+ path: path21,
805
805
  line,
806
806
  suggestion: "La especificaci\xF3n es funcional y de negocio: describe comportamiento, no tecnolog\xEDa ni adjetivos vagos"
807
807
  })
@@ -810,7 +810,7 @@ function lintText(text, code, terms, label, path19, line) {
810
810
  }
811
811
  return out;
812
812
  }
813
- function lintRequirement(req, path19, opts = {}) {
813
+ function lintRequirement(req, path21, opts = {}) {
814
814
  const out = [];
815
815
  const vague = opts.language === "en" ? VAGUE_EN : VAGUE_ES;
816
816
  const tech = opts.language === "en" ? TECH_EN : TECH_ES;
@@ -821,32 +821,32 @@ function lintRequirement(req, path19, opts = {}) {
821
821
  ...req.scenarios.flatMap((s) => [...s.when.map((w) => ({ text: w, line: s.line })), ...s.then.map((t) => ({ text: t, line: s.line }))])
822
822
  ];
823
823
  for (const part of parts) {
824
- out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path19, part.line));
824
+ out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path21, part.line));
825
825
  if (opts.businessOnly !== false) {
826
- out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path19, part.line));
826
+ out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path21, part.line));
827
827
  }
828
828
  }
829
829
  if (req.scenarios.length === 0) {
830
- out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path19, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
830
+ out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path21, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
831
831
  }
832
832
  return out;
833
833
  }
834
- function lintDelta(delta, livingRequirements, path19, opts = {}) {
834
+ function lintDelta(delta, livingRequirements, path21, opts = {}) {
835
835
  const out = [...delta.diagnostics];
836
836
  for (const req of [...delta.added, ...delta.modified]) {
837
- out.push(...lintRequirement(req, path19, opts));
837
+ out.push(...lintRequirement(req, path21, opts));
838
838
  }
839
839
  for (const req of delta.modified) {
840
840
  const living = livingRequirements.get(req.id);
841
841
  if (!living) {
842
- out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path19, line: req.line }));
842
+ out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path21, line: req.line }));
843
843
  continue;
844
844
  }
845
845
  for (const existing of living.scenarios) {
846
846
  if (!req.scenarios.some((s) => s.id === existing.id)) {
847
847
  out.push(
848
848
  diag("TRACE-007", "error", `MODIFIED ${req.id} pierde el escenario ${existing.id}: copia el bloque completo`, {
849
- path: path19,
849
+ path: path21,
850
850
  line: req.line,
851
851
  suggestion: "Copia el bloque completo de la spec viva y ed\xEDtalo; para quitarlo, decl\xE1ralo en REMOVED"
852
852
  })
@@ -857,15 +857,15 @@ function lintDelta(delta, livingRequirements, path19, opts = {}) {
857
857
  for (const req of delta.removed) {
858
858
  const living = livingRequirements.get(req.id);
859
859
  if (!living) {
860
- out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path19, line: req.line }));
860
+ out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path21, line: req.line }));
861
861
  }
862
862
  }
863
863
  for (const rename2 of delta.renamed) {
864
864
  if (rename2.from.id !== rename2.to.id) {
865
- 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 }));
865
+ out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path21, line: rename2.line }));
866
866
  }
867
867
  if (!livingRequirements.has(rename2.from.id)) {
868
- out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path19, line: rename2.line }));
868
+ out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path21, line: rename2.line }));
869
869
  }
870
870
  }
871
871
  return out;
@@ -895,7 +895,7 @@ var MERMAID_KEYWORDS = [
895
895
  "architecture-beta"
896
896
  ];
897
897
  var MERMAID_BLOCKS = /^\s*(alt|loop|opt|par|rect|critical|break|subgraph)\b/;
898
- function lintPlan(planText, path19) {
898
+ function lintPlan(planText, path21) {
899
899
  const out = [];
900
900
  const blocks = [...planText.matchAll(/```mermaid\r?\n([\s\S]*?)```/g)];
901
901
  for (const [index, block] of blocks.entries()) {
@@ -905,7 +905,7 @@ function lintPlan(planText, path19) {
905
905
  if (!MERMAID_KEYWORDS.some((keyword) => first.startsWith(keyword))) {
906
906
  out.push(
907
907
  diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: la primera l\xEDnea debe declarar el tipo (${MERMAID_KEYWORDS.slice(0, 5).join(", ")}\u2026) y empieza por "${first.slice(0, 30)}"`, {
908
- path: path19,
908
+ path: path21,
909
909
  suggestion: "Corrige el tipo del diagrama o elimina el bloque"
910
910
  })
911
911
  );
@@ -919,7 +919,7 @@ function lintPlan(planText, path19) {
919
919
  if (open !== 0) {
920
920
  out.push(
921
921
  diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: faltan ${Math.abs(open)} \`end\` (bloques alt/loop/subgraph sin cerrar)`, {
922
- path: path19,
922
+ path: path21,
923
923
  suggestion: "Cierra cada bloque alt/loop/opt/par/rect/subgraph con `end`"
924
924
  })
925
925
  );
@@ -931,7 +931,7 @@ function lintPlan(planText, path19) {
931
931
  if (message.includes(";")) {
932
932
  out.push(
933
933
  diag("LINT-PLN-003", "error", `Diagrama mermaid ${index + 1}: el mensaje "${message.trim().slice(0, 40)}\u2026" usa \`;\` y mermaid lo interpreta como fin de sentencia`, {
934
- path: path19,
934
+ path: path21,
935
935
  suggestion: "Sustituye `;` por `\xB7` o `,` en los mensajes de sequenceDiagram"
936
936
  })
937
937
  );
@@ -4151,8 +4151,361 @@ async function regenerateIndex(root, cfg, now = /* @__PURE__ */ new Date()) {
4151
4151
  await writeText(path16.join(root, ".sdd", "INDEX.md"), markdown);
4152
4152
  }
4153
4153
 
4154
- // src/doctor.ts
4154
+ // src/migrations.ts
4155
+ import { promises as fs2 } from "fs";
4155
4156
  import path17 from "path";
4157
+ import { parse as parseYaml8, stringify as stringifyYaml5 } from "yaml";
4158
+ var SCHEMA_VERSION = 1;
4159
+ var BACKUP_DIRNAME = ".backup";
4160
+ var BACKUP_POINTER = ".latest";
4161
+ var SCHEMA_MIGRATIONS = [
4162
+ {
4163
+ id: "0001-sellar-version-de-esquema",
4164
+ description: "Sella la versi\xF3n de esquema vigente en los elementos que no la declaran",
4165
+ from: 0,
4166
+ to: SCHEMA_VERSION
4167
+ }
4168
+ ];
4169
+ var FIXED_ARTIFACTS = ["config.yaml", "approvals.yaml", path17.join("profiles", "detected.yaml")];
4170
+ var CHANGE_ARTIFACTS = ["meta.yaml", path17.join("mockups", "manifest.yaml")];
4171
+ async function pushChangeArtifacts(out, sddDir, relDir) {
4172
+ for (const rel of CHANGE_ARTIFACTS) {
4173
+ const abs = path17.join(sddDir, relDir, rel);
4174
+ if (await exists(abs)) out.push({ artifact: toPosix(path17.join(relDir, rel)), path: abs });
4175
+ }
4176
+ }
4177
+ async function collectVersionedArtifacts(root) {
4178
+ const sddDir = path17.join(root, ".sdd");
4179
+ const out = [];
4180
+ for (const rel of FIXED_ARTIFACTS) {
4181
+ const abs = path17.join(sddDir, rel);
4182
+ if (await exists(abs)) out.push({ artifact: toPosix(rel), path: abs });
4183
+ }
4184
+ const changesDir = path17.join(sddDir, "changes");
4185
+ for (const slug of await listDirs(changesDir)) {
4186
+ if (slug === "archive") continue;
4187
+ await pushChangeArtifacts(out, sddDir, path17.join("changes", slug));
4188
+ }
4189
+ for (const entry of await listDirs(path17.join(changesDir, "archive"))) {
4190
+ await pushChangeArtifacts(out, sddDir, path17.join("changes", "archive", entry));
4191
+ }
4192
+ return out;
4193
+ }
4194
+ function readArtifactVersion(content) {
4195
+ let data;
4196
+ try {
4197
+ data = parseYaml8(content);
4198
+ } catch (err) {
4199
+ return { state: "unreadable", reason: `YAML inv\xE1lido (${err.message})` };
4200
+ }
4201
+ if (data === null || data === void 0) return { state: "absent" };
4202
+ if (typeof data !== "object" || Array.isArray(data)) return { state: "unreadable", reason: "la ra\xEDz no es un mapa de claves y valores" };
4203
+ const raw = data["schema_version"];
4204
+ if (raw === void 0) return { state: "absent" };
4205
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) return { state: "unreadable", reason: `schema_version con valor no v\xE1lido: ${String(raw)}` };
4206
+ return { state: "ok", version: raw };
4207
+ }
4208
+ function stampSchemaVersion(content, version) {
4209
+ const line = `schema_version: ${version}`;
4210
+ const bom = content.startsWith("\uFEFF") ? "\uFEFF" : "";
4211
+ const body = bom ? content.slice(1) : content;
4212
+ if (body.trim() === "") return `${bom}${line}
4213
+ `;
4214
+ const eol = body.includes("\r\n") ? "\r\n" : "\n";
4215
+ const lines = body.split(/\r?\n/);
4216
+ const trailing = lines.length > 1 && lines[lines.length - 1] === "";
4217
+ let index = 0;
4218
+ while (index < lines.length) {
4219
+ const trimmed = (lines[index] ?? "").trim();
4220
+ if (trimmed === "" || trimmed.startsWith("#")) {
4221
+ index += 1;
4222
+ continue;
4223
+ }
4224
+ break;
4225
+ }
4226
+ if ((lines[index] ?? "").trim() === "---") index += 1;
4227
+ const insertAt = trailing ? Math.min(index, lines.length - 1) : index;
4228
+ lines.splice(insertAt, 0, line);
4229
+ const next = bom + lines.join(eol);
4230
+ const check = readArtifactVersion(next);
4231
+ if (check.state !== "ok" || check.version !== version) return void 0;
4232
+ return next;
4233
+ }
4234
+ async function planUpgrade(root) {
4235
+ const artifacts = await collectVersionedArtifacts(root);
4236
+ const pending = [];
4237
+ const newer = [];
4238
+ const unreadable = [];
4239
+ for (const found of artifacts) {
4240
+ const content = await readTextIfExists(found.path);
4241
+ if (content === void 0) continue;
4242
+ const state = readArtifactVersion(content);
4243
+ if (state.state === "unreadable") {
4244
+ unreadable.push({ ...found, reason: state.reason });
4245
+ continue;
4246
+ }
4247
+ if (state.state === "ok") {
4248
+ if (state.version > SCHEMA_VERSION) {
4249
+ newer.push({ ...found, reason: `produce la versi\xF3n ${state.version}, m\xE1s nueva que la vigente (${SCHEMA_VERSION})` });
4250
+ continue;
4251
+ }
4252
+ if (state.version === SCHEMA_VERSION) continue;
4253
+ }
4254
+ const from = state.state === "ok" ? state.version : 0;
4255
+ const migration = SCHEMA_MIGRATIONS.find((m) => m.from === from);
4256
+ if (!migration) {
4257
+ unreadable.push({ ...found, reason: `no hay migraci\xF3n registrada desde la versi\xF3n ${from}` });
4258
+ continue;
4259
+ }
4260
+ const stamped = stampSchemaVersion(content, migration.to);
4261
+ if (stamped === void 0) {
4262
+ unreadable.push({ ...found, reason: "no se pudo sellar con seguridad sin alterar el resto del contenido" });
4263
+ continue;
4264
+ }
4265
+ pending.push({ ...found, from, to: migration.to, migration: migration.id, summary: migration.description });
4266
+ }
4267
+ return { root, currentVersion: SCHEMA_VERSION, pending, newer, unreadable, upToDate: pending.length === 0 };
4268
+ }
4269
+ async function removeDir(dir) {
4270
+ try {
4271
+ await fs2.rm(dir, { recursive: true, force: true });
4272
+ } catch {
4273
+ }
4274
+ }
4275
+ async function removeFile(file) {
4276
+ try {
4277
+ await fs2.rm(file, { force: true });
4278
+ } catch {
4279
+ }
4280
+ }
4281
+ function backupName(now) {
4282
+ return `${localCompact(now)}${localOffset(now).replace(":", "")}`;
4283
+ }
4284
+ async function applyUpgrade(root, now = /* @__PURE__ */ new Date()) {
4285
+ const plan = await planUpgrade(root);
4286
+ if (plan.pending.length === 0) return { status: "up-to-date", plan, applied: [] };
4287
+ const sddDir = path17.join(root, ".sdd");
4288
+ const backupRoot = path17.join(sddDir, BACKUP_DIRNAME);
4289
+ const name = backupName(now);
4290
+ const backupDir = path17.join(backupRoot, name);
4291
+ const pointerFile = path17.join(backupRoot, BACKUP_POINTER);
4292
+ const previous = [];
4293
+ try {
4294
+ for (const item of plan.pending) {
4295
+ const content = await readTextIfExists(item.path);
4296
+ if (content === void 0) throw new Error("el elemento desapareci\xF3 mientras se respaldaba");
4297
+ previous.push({ item, contents: content });
4298
+ await writeText(path17.join(backupDir, "files", ...item.artifact.split("/")), content);
4299
+ }
4300
+ await writeText(
4301
+ path17.join(backupDir, "backup.yaml"),
4302
+ stringifyYaml5(
4303
+ {
4304
+ schema_version: SCHEMA_VERSION,
4305
+ created_at: localStamp(now),
4306
+ to_version: SCHEMA_VERSION,
4307
+ files: plan.pending.map((i) => ({ artifact: i.artifact, from: i.from, to: i.to, migration: i.migration }))
4308
+ },
4309
+ { lineWidth: 120 }
4310
+ )
4311
+ );
4312
+ await writeText(pointerFile, `${name}
4313
+ `);
4314
+ } catch (err) {
4315
+ await removeDir(backupDir);
4316
+ await removeFile(pointerFile);
4317
+ return { status: "failed", plan, applied: [], failure: { artifact: "(respaldo)", message: err.message } };
4318
+ }
4319
+ const written = [];
4320
+ for (const { item, contents } of previous) {
4321
+ try {
4322
+ const next = stampSchemaVersion(contents, item.to);
4323
+ if (next === void 0) throw new Error("no se pudo sellar con seguridad");
4324
+ await writeText(item.path, next);
4325
+ written.push(item);
4326
+ } catch (err) {
4327
+ for (const w of written) {
4328
+ const prev = previous.find((p) => p.item.artifact === w.artifact);
4329
+ if (prev) {
4330
+ try {
4331
+ await writeText(prev.item.path, prev.contents);
4332
+ } catch {
4333
+ }
4334
+ }
4335
+ }
4336
+ await removeFile(pointerFile);
4337
+ await removeDir(backupDir);
4338
+ return { status: "failed", plan, applied: [], failure: { artifact: item.artifact, message: err.message } };
4339
+ }
4340
+ }
4341
+ for (const dir of await listDirs(backupRoot)) {
4342
+ if (dir !== name) await removeDir(path17.join(backupRoot, dir));
4343
+ }
4344
+ return {
4345
+ status: "applied",
4346
+ plan,
4347
+ applied: written,
4348
+ backup: {
4349
+ name,
4350
+ dir: backupDir,
4351
+ relativeDir: toPosix(path17.relative(root, backupDir)),
4352
+ createdAt: localStamp(now),
4353
+ files: plan.pending.map((i) => i.artifact)
4354
+ }
4355
+ };
4356
+ }
4357
+ async function rollbackUpgrade(root) {
4358
+ const sddDir = path17.join(root, ".sdd");
4359
+ const backupRoot = path17.join(sddDir, BACKUP_DIRNAME);
4360
+ const pointerFile = path17.join(backupRoot, BACKUP_POINTER);
4361
+ const name = (await readTextIfExists(pointerFile))?.trim();
4362
+ if (!name) return { status: "no-backup", restored: [] };
4363
+ const backupDir = path17.join(backupRoot, name);
4364
+ const manifestRaw = await readTextIfExists(path17.join(backupDir, "backup.yaml"));
4365
+ if (manifestRaw === void 0) return { status: "no-backup", restored: [] };
4366
+ let files = [];
4367
+ try {
4368
+ const parsed = parseYaml8(manifestRaw);
4369
+ files = (parsed?.files ?? []).filter((f) => typeof f?.artifact === "string");
4370
+ } catch {
4371
+ return { status: "no-backup", restored: [] };
4372
+ }
4373
+ const restored = [];
4374
+ for (const file of files) {
4375
+ const from = path17.join(backupDir, "files", ...file.artifact.split("/"));
4376
+ const to = path17.join(sddDir, ...file.artifact.split("/"));
4377
+ const content = await readTextIfExists(from);
4378
+ if (content === void 0) continue;
4379
+ try {
4380
+ await writeText(to, content);
4381
+ restored.push(file.artifact);
4382
+ } catch (err) {
4383
+ return { status: "failed", restored, backup: name, failure: { artifact: file.artifact, message: err.message } };
4384
+ }
4385
+ }
4386
+ await removeDir(backupDir);
4387
+ await removeFile(pointerFile);
4388
+ return { status: "restored", restored, backup: name };
4389
+ }
4390
+ async function upgradeAdvisory(root) {
4391
+ const plan = await planUpgrade(root);
4392
+ const diagnostics = [];
4393
+ if (plan.pending.length > 0) {
4394
+ diagnostics.push(
4395
+ diag("ATLAS-UPGRADE-001", "warning", `El estado del proyecto no corresponde a la versi\xF3n vigente: ${plan.pending.length} elemento(s) por actualizar`, {
4396
+ suggestion: "Vista previa: `satlas upgrade` \xB7 Aplicar: `satlas upgrade --apply`"
4397
+ })
4398
+ );
4399
+ }
4400
+ for (const item of plan.newer) {
4401
+ diagnostics.push(
4402
+ diag("ATLAS-UPGRADE-002", "warning", `El proyecto fue producido por una versi\xF3n m\xE1s nueva: "${item.artifact}" (${item.reason})`, {
4403
+ path: item.path,
4404
+ suggestion: "Actualiza la herramienta; no se actualiza hacia atr\xE1s"
4405
+ })
4406
+ );
4407
+ }
4408
+ for (const item of plan.unreadable) {
4409
+ diagnostics.push(
4410
+ diag("ATLAS-UPGRADE-003", "warning", `No se pudo interpretar "${item.artifact}": ${item.reason}`, {
4411
+ path: item.path,
4412
+ suggestion: "Revisa el elemento; no se modificar\xE1"
4413
+ })
4414
+ );
4415
+ }
4416
+ return { plan, diagnostics };
4417
+ }
4418
+
4419
+ // src/sarif.ts
4420
+ import path18 from "path";
4421
+ var SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
4422
+ var SARIF_VERSION = "2.1.0";
4423
+ var TOOL_NAME = "SpecAtlas";
4424
+ var TOOL_URL = "https://github.com/AlonsoAM/specatlas";
4425
+ var RULE_FAMILIES = [
4426
+ [/^LINT-STR-/, "Estructura de los artefactos"],
4427
+ [/^LINT-BIZ-/, "Lenguaje de negocio de la especificaci\xF3n"],
4428
+ [/^LINT-DLT-/, "Delta de especificaci\xF3n"],
4429
+ [/^LINT-EVD-/, "Evidencia registrada"],
4430
+ [/^LINT-TSK-/, "Tareas declaradas"],
4431
+ [/^LINT-PLN-/, "Plan t\xE9cnico"],
4432
+ [/^(LINT-MKP|MKP)-/, "Mockups"],
4433
+ [/^TRACE-/, "Trazabilidad requisito, escenario, tarea y evidencia"],
4434
+ [/^PACK-/, "Pack de cumplimiento"],
4435
+ [/^ATLAS-UPGRADE-/, "Actualizaci\xF3n del estado del proyecto"],
4436
+ [/^ATLAS-ADAPTERS-/, "Adaptadores de agente"],
4437
+ [/^ATLAS-CI-/, "Comprobaci\xF3n continua"],
4438
+ [/^ATLAS-/, "Estado del proyecto"]
4439
+ ];
4440
+ function ruleDescription(code) {
4441
+ for (const [pattern, description] of RULE_FAMILIES) {
4442
+ if (pattern.test(code)) return description;
4443
+ }
4444
+ return code;
4445
+ }
4446
+ function sarifLevel(severity) {
4447
+ if (severity === "error") return "error";
4448
+ if (severity === "warning") return "warning";
4449
+ return "note";
4450
+ }
4451
+ function resultOf(diagnostic, root) {
4452
+ const result = {
4453
+ ruleId: diagnostic.code,
4454
+ level: sarifLevel(diagnostic.severity),
4455
+ message: { text: diagnostic.message }
4456
+ };
4457
+ if (diagnostic.path) {
4458
+ const rel = path18.relative(root, diagnostic.path);
4459
+ if (rel !== "") {
4460
+ const physicalLocation = {
4461
+ artifactLocation: { uri: toPosix(rel) },
4462
+ ...diagnostic.line ? { region: { startLine: diagnostic.line } } : {}
4463
+ };
4464
+ result.locations = [{ physicalLocation }];
4465
+ }
4466
+ }
4467
+ return result;
4468
+ }
4469
+ function toSarifReport(opts) {
4470
+ const rules = /* @__PURE__ */ new Map();
4471
+ const results = [];
4472
+ for (const diagnostic of opts.diagnostics) {
4473
+ let rule = rules.get(diagnostic.code);
4474
+ if (!rule) {
4475
+ rule = { id: diagnostic.code, shortDescription: { text: ruleDescription(diagnostic.code) } };
4476
+ if (diagnostic.suggestion) rule.help = { text: diagnostic.suggestion };
4477
+ rules.set(diagnostic.code, rule);
4478
+ } else if (!rule.help && diagnostic.suggestion) {
4479
+ rule.help = { text: diagnostic.suggestion };
4480
+ }
4481
+ results.push(resultOf(diagnostic, opts.root));
4482
+ }
4483
+ return {
4484
+ $schema: SARIF_SCHEMA,
4485
+ version: SARIF_VERSION,
4486
+ runs: [
4487
+ {
4488
+ tool: {
4489
+ driver: {
4490
+ name: TOOL_NAME,
4491
+ informationUri: TOOL_URL,
4492
+ version: opts.version,
4493
+ rules: [...rules.values()]
4494
+ }
4495
+ },
4496
+ results,
4497
+ invocations: [{ executionSuccessful: !(opts.failed ?? false) }]
4498
+ }
4499
+ ]
4500
+ };
4501
+ }
4502
+ function toSarifText(opts) {
4503
+ return `${JSON.stringify(toSarifReport(opts), null, 2)}
4504
+ `;
4505
+ }
4506
+
4507
+ // src/doctor.ts
4508
+ import path19 from "path";
4156
4509
  async function runDoctor(root) {
4157
4510
  const findings = [];
4158
4511
  const { workspace, config } = await loadWorkspace(root);
@@ -4160,7 +4513,7 @@ async function runDoctor(root) {
4160
4513
  const approvals = await loadApprovals(workspace.sddDir);
4161
4514
  findings.push(...approvals.diagnostics);
4162
4515
  for (const change of workspace.changes) {
4163
- const deltaPath = path17.join(change.dir, "spec.md");
4516
+ const deltaPath = path19.join(change.dir, "spec.md");
4164
4517
  const deltaContent = await readTextIfExists(deltaPath);
4165
4518
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
4166
4519
  if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
@@ -4182,7 +4535,7 @@ async function runDoctor(root) {
4182
4535
  }
4183
4536
  for (const override of change.meta?.overrides ?? []) {
4184
4537
  if (!override.reason.trim() || !override.by.trim()) {
4185
- findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path17.join(change.dir, "meta.yaml") }));
4538
+ findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path19.join(change.dir, "meta.yaml") }));
4186
4539
  }
4187
4540
  }
4188
4541
  }
@@ -4205,7 +4558,7 @@ async function specHashOf(filePath) {
4205
4558
  }
4206
4559
 
4207
4560
  // src/gate.ts
4208
- import path18 from "path";
4561
+ import path20 from "path";
4209
4562
  var UI_DOMAINS2 = /* @__PURE__ */ new Set(["frontend", "mobile", "fullstack"]);
4210
4563
  function count(name, diagnostics) {
4211
4564
  return {
@@ -4222,7 +4575,7 @@ function livingRequirementsMap(specs) {
4222
4575
  return map;
4223
4576
  }
4224
4577
  async function runCiGate(opts) {
4225
- const root = path18.resolve(opts.root);
4578
+ const root = path20.resolve(opts.root);
4226
4579
  const { workspace, config } = await loadWorkspace(root);
4227
4580
  const diagnostics = [];
4228
4581
  const checks = [];
@@ -4233,7 +4586,7 @@ async function runCiGate(opts) {
4233
4586
  let changesErrors = 0;
4234
4587
  let changesWarnings = 0;
4235
4588
  for (const change of workspace.changes) {
4236
- const lintFindings = change.delta ? lintDelta(change.delta, living, path18.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
4589
+ const lintFindings = change.delta ? lintDelta(change.delta, living, path20.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
4237
4590
  const trace = checkTrace({
4238
4591
  specs: workspace.specs,
4239
4592
  change,
@@ -4277,6 +4630,8 @@ async function runCiGate(opts) {
4277
4630
  return { checks, diagnostics, errors, warnings, failed };
4278
4631
  }
4279
4632
  export {
4633
+ BACKUP_DIRNAME,
4634
+ BACKUP_POINTER,
4280
4635
  BLOCK_HEAD_RE,
4281
4636
  BUILTIN_PACKS,
4282
4637
  CONFIG_FILE,
@@ -4286,14 +4641,21 @@ export {
4286
4641
  RULE_ID_RE,
4287
4642
  RULE_RE,
4288
4643
  RUN_EVENT_TYPES,
4644
+ SARIF_SCHEMA,
4645
+ SARIF_VERSION,
4289
4646
  SCENARIO_HEAD_RE,
4290
4647
  SCENARIO_ID_RE,
4648
+ SCHEMA_MIGRATIONS,
4649
+ SCHEMA_VERSION,
4291
4650
  SDD_DIR,
4292
4651
  SLUG_RE,
4293
4652
  TASK_ID_RE,
4294
4653
  TASK_LINE_RE,
4654
+ TOOL_NAME,
4655
+ TOOL_URL,
4295
4656
  adoptWorkspace,
4296
4657
  appendRunEvent,
4658
+ applyUpgrade,
4297
4659
  approvalsSchema,
4298
4660
  approveFromGithub,
4299
4661
  archiveChange,
@@ -4308,6 +4670,7 @@ export {
4308
4670
  checkMockups,
4309
4671
  checkTrace,
4310
4672
  collectMetrics,
4673
+ collectVersionedArtifacts,
4311
4674
  commentIssue,
4312
4675
  compareTaskIds,
4313
4676
  computeInputsHash,
@@ -4403,8 +4766,10 @@ export {
4403
4766
  patchChangeMeta,
4404
4767
  planBlock,
4405
4768
  planMockups,
4769
+ planUpgrade,
4406
4770
  planWaves,
4407
4771
  profileSchema,
4772
+ readArtifactVersion,
4408
4773
  readMockupManifest,
4409
4774
  readRun,
4410
4775
  readText,
@@ -4418,23 +4783,30 @@ export {
4418
4783
  renderTemplate,
4419
4784
  requiresMockups,
4420
4785
  resolvePacks,
4786
+ rollbackUpgrade,
4787
+ ruleDescription,
4421
4788
  runAnalyze,
4422
4789
  runCiGate,
4423
4790
  runDoctor,
4424
4791
  runProcess,
4792
+ sarifLevel,
4425
4793
  setMockupRequirement,
4426
4794
  sha256,
4427
4795
  shortHash,
4428
4796
  signApproval,
4429
4797
  specHashOf,
4430
4798
  splitCommand,
4799
+ stampSchemaVersion,
4431
4800
  stateLabel,
4432
4801
  syncGithubIssue,
4433
4802
  templatesFor,
4434
4803
  toPosix,
4804
+ toSarifReport,
4805
+ toSarifText,
4435
4806
  tokensFile,
4436
4807
  updateMockupScreenshots,
4437
4808
  updateRunStatus,
4809
+ upgradeAdvisory,
4438
4810
  verifyApproval,
4439
4811
  walkFiles,
4440
4812
  writeConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specatlas/core",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Kernel determinista de SpecAtlas: parsing, validación, trazabilidad, olas y ciclo de vida",
5
5
  "license": "MIT",
6
6
  "type": "module",