@specatlas/core 0.1.21 → 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
@@ -802,6 +802,43 @@ declare function parseApprovals(raw: string, filePath: string): {
802
802
  diagnostics: Diagnostic[];
803
803
  };
804
804
 
805
+ interface GlossaryTerm {
806
+ term: string;
807
+ definition: string;
808
+ synonyms: string[];
809
+ }
810
+ interface ParsedGlossary {
811
+ terms: GlossaryTerm[];
812
+ diagnostics: Diagnostic[];
813
+ }
814
+ declare function parseGlossary(md: string, filePath?: string): ParsedGlossary;
815
+
816
+ interface ImpactTask {
817
+ id: string;
818
+ change: string;
819
+ text: string;
820
+ files: string[];
821
+ covers: string[];
822
+ }
823
+ interface ImpactEvidence {
824
+ scenario: string;
825
+ result: string;
826
+ change: string;
827
+ }
828
+ interface ImpactReport {
829
+ target: string;
830
+ kind: 'requirement' | 'file';
831
+ exists: boolean;
832
+ scenarios: string[];
833
+ tasks: ImpactTask[];
834
+ requirements: string[];
835
+ changes: string[];
836
+ files: string[];
837
+ evidence: ImpactEvidence[];
838
+ }
839
+ declare function impactOfRequirement(workspace: Workspace, reqId: string): ImpactReport;
840
+ declare function impactOfFile(workspace: Workspace, file: string): ImpactReport;
841
+
805
842
  interface LintOptions {
806
843
  language?: 'es' | 'en';
807
844
  businessOnly?: boolean;
@@ -1671,6 +1708,148 @@ interface ArchiveResult {
1671
1708
  declare function archiveChange(opts: ArchiveOptions): Promise<ArchiveResult>;
1672
1709
  declare function regenerateIndex(root: string, cfg?: AtlasConfig, now?: Date): Promise<void>;
1673
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
+
1674
1853
  interface DoctorReport {
1675
1854
  findings: Diagnostic[];
1676
1855
  summary: {
@@ -1710,4 +1889,4 @@ declare function livingRequirementsMap(specs: Array<{
1710
1889
  }>): Map<string, Requirement>;
1711
1890
  declare function runCiGate(opts: CiOptions): Promise<CiResult>;
1712
1891
 
1713
- 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, 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, 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
@@ -640,6 +640,150 @@ function findScenarioHeading(lines, blockLineIndex) {
640
640
  return void 0;
641
641
  }
642
642
 
643
+ // src/parse/glossary.ts
644
+ var HEADER_KEYS = /* @__PURE__ */ new Set(["t\xE9rmino", "termino", "term", "definici\xF3n", "definicion", "definition", "sin\xF3nimos", "sinonimos", "synonyms"]);
645
+ var SEPARATOR_RE = /^:?-{2,}:?$/;
646
+ function parseGlossary(md, filePath) {
647
+ const diagnostics = [];
648
+ const terms = [];
649
+ const lines = md.replace(/\r\n?/g, "\n").split("\n");
650
+ for (let i = 0; i < lines.length; i += 1) {
651
+ const raw = lines[i] ?? "";
652
+ if (!raw.trim().startsWith("|")) continue;
653
+ const inner = raw.trim().replace(/^\|/, "").replace(/\|$/, "");
654
+ const cells = inner.split("|").map((c) => c.trim());
655
+ if (cells.length < 2) {
656
+ diagnostics.push(diag("LINT-GLO-001", "warning", "Fila del glosario sin columnas suficientes", { path: filePath, line: i + 1 }));
657
+ continue;
658
+ }
659
+ const first = cells[0] ?? "";
660
+ if (HEADER_KEYS.has(first.toLowerCase()) || SEPARATOR_RE.test(first)) continue;
661
+ const term = first;
662
+ const definition = cells[1] ?? "";
663
+ if (term === "" || definition === "") {
664
+ diagnostics.push(diag("LINT-GLO-002", "warning", "T\xE9rmino o definici\xF3n vac\xEDos en el glosario", { path: filePath, line: i + 1 }));
665
+ continue;
666
+ }
667
+ const synonyms = (cells[2] ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
668
+ terms.push({ term, definition, synonyms });
669
+ }
670
+ return { terms, diagnostics };
671
+ }
672
+
673
+ // src/impact.ts
674
+ function normalizePath(p) {
675
+ return toPosix(p.trim()).replace(/^\.\//, "").replace(/\/+$/, "").toLowerCase();
676
+ }
677
+ function fileMatches(query, candidate) {
678
+ if (query === candidate) return true;
679
+ return candidate.endsWith(`/${query}`);
680
+ }
681
+ function scenarioToReq(workspace) {
682
+ const map = /* @__PURE__ */ new Map();
683
+ for (const spec of workspace.specs) {
684
+ for (const req of spec.spec.requirements) {
685
+ for (const sc of req.scenarios) map.set(sc.id, req.id);
686
+ }
687
+ }
688
+ for (const change of workspace.changes) {
689
+ for (const req of [...change.delta?.added ?? [], ...change.delta?.modified ?? []]) {
690
+ for (const sc of req.scenarios) map.set(sc.id, req.id);
691
+ }
692
+ }
693
+ return map;
694
+ }
695
+ function emptyReport(target, kind) {
696
+ return { target, kind, exists: false, scenarios: [], tasks: [], requirements: [], changes: [], files: [], evidence: [] };
697
+ }
698
+ function collect(workspace, report, covers, reqOf) {
699
+ const requirements = /* @__PURE__ */ new Set();
700
+ for (const change of workspace.changes) {
701
+ let changeMatches = false;
702
+ for (const block of change.tasks?.blocks ?? []) {
703
+ for (const task of block.tasks) {
704
+ const hit = task.covers.some((c) => covers.has(c.toUpperCase()));
705
+ if (!hit) continue;
706
+ changeMatches = true;
707
+ report.tasks.push({ id: task.id, change: change.slug, text: task.text, files: task.files, covers: task.covers });
708
+ for (const c of task.covers) {
709
+ const upper = c.toUpperCase();
710
+ if (covers.has(upper)) {
711
+ const req = reqOf.get(upper) ?? upper;
712
+ requirements.add(req);
713
+ }
714
+ }
715
+ for (const f of task.files) report.files.push(f);
716
+ }
717
+ }
718
+ if (changeMatches) report.changes.push(change.slug);
719
+ for (const ev of change.verify?.evidence ?? []) {
720
+ if (covers.has(ev.scenario.toUpperCase())) {
721
+ report.evidence.push({ scenario: ev.scenario, result: ev.result, change: change.slug });
722
+ }
723
+ }
724
+ }
725
+ report.requirements = [...requirements].sort();
726
+ report.changes = [...new Set(report.changes)].sort();
727
+ report.files = [...new Set(report.files)].sort();
728
+ report.evidence.sort((a, b) => a.scenario.localeCompare(b.scenario));
729
+ }
730
+ function impactOfRequirement(workspace, reqId) {
731
+ const id = reqId.toUpperCase();
732
+ const scenarios = /* @__PURE__ */ new Set();
733
+ let exists2 = false;
734
+ for (const spec of workspace.specs) {
735
+ for (const req of spec.spec.requirements) {
736
+ if (req.id !== id) continue;
737
+ exists2 = true;
738
+ for (const sc of req.scenarios) scenarios.add(sc.id);
739
+ }
740
+ }
741
+ for (const change of workspace.changes) {
742
+ for (const req of [...change.delta?.added ?? [], ...change.delta?.modified ?? []]) {
743
+ if (req.id !== id) continue;
744
+ exists2 = true;
745
+ for (const sc of req.scenarios) scenarios.add(sc.id);
746
+ }
747
+ if ((change.delta?.removed ?? []).some((r) => r.id === id)) exists2 = true;
748
+ if ((change.delta?.renamed ?? []).some((r) => r.from.id === id || r.to.id === id)) exists2 = true;
749
+ }
750
+ const report = emptyReport(id, "requirement");
751
+ if (!exists2) return report;
752
+ report.exists = true;
753
+ report.scenarios = [...scenarios].sort();
754
+ const reqOf = scenarioToReq(workspace);
755
+ collect(workspace, report, /* @__PURE__ */ new Set([id, ...scenarios]), reqOf);
756
+ if (!report.requirements.includes(id)) report.requirements.unshift(id);
757
+ return report;
758
+ }
759
+ function impactOfFile(workspace, file) {
760
+ const query = normalizePath(file);
761
+ const report = emptyReport(file, "file");
762
+ const covers = /* @__PURE__ */ new Set();
763
+ const matched = /* @__PURE__ */ new Set();
764
+ const changes = /* @__PURE__ */ new Set();
765
+ for (const change of workspace.changes) {
766
+ for (const block of change.tasks?.blocks ?? []) {
767
+ for (const task of block.tasks) {
768
+ if (!task.files.some((f) => fileMatches(query, normalizePath(f)))) continue;
769
+ changes.add(change.slug);
770
+ for (const f of task.files) matched.add(f);
771
+ for (const c of task.covers) covers.add(c.toUpperCase());
772
+ report.tasks.push({ id: task.id, change: change.slug, text: task.text, files: task.files, covers: task.covers });
773
+ }
774
+ }
775
+ }
776
+ if (matched.size === 0) return report;
777
+ report.exists = true;
778
+ report.files = [...matched].sort();
779
+ report.changes = [...changes].sort();
780
+ const reqOf = scenarioToReq(workspace);
781
+ const requirements = /* @__PURE__ */ new Set();
782
+ for (const c of covers) requirements.add(reqOf.get(c) ?? c);
783
+ report.requirements = [...requirements].sort();
784
+ return report;
785
+ }
786
+
643
787
  // src/lint.ts
644
788
  var VAGUE_ES = ["r\xE1pido", "r\xE1pida", "r\xE1pidos", "r\xE1pidas", "f\xE1cil", "f\xE1ciles", "varios", "varias", "\xF3ptimo", "\xF3ptima", "robusto", "robusta", "adecuado", "adecuada", "eficiente", "amigable", "moderno", "moderna", "mejor", "mejores", "simple", "sencillo", "intuitivo", "intuitiva", "apropiado", "apropiada", "suficiente", "razonable"];
645
789
  var VAGUE_EN = ["fast", "quick", "easy", "several", "optimal", "robust", "adequate", "efficient", "friendly", "modern", "better", "best", "simple", "intuitive", "appropriate", "sufficient", "reasonable", "nice", "clean"];
@@ -650,14 +794,14 @@ function wordBoundary(text, term) {
650
794
  const re = new RegExp(`(?<![\\p{L}\\p{N}])${escaped}(?![\\p{L}\\p{N}])`, "iu");
651
795
  return re.test(text);
652
796
  }
653
- function lintText(text, code, terms, label, path19, line) {
797
+ function lintText(text, code, terms, label, path21, line) {
654
798
  const out = [];
655
799
  const lower = text.toLowerCase();
656
800
  for (const term of terms) {
657
801
  if (wordBoundary(lower, term)) {
658
802
  out.push(
659
803
  diag(code, "error", `${label}: "${term}"`, {
660
- path: path19,
804
+ path: path21,
661
805
  line,
662
806
  suggestion: "La especificaci\xF3n es funcional y de negocio: describe comportamiento, no tecnolog\xEDa ni adjetivos vagos"
663
807
  })
@@ -666,7 +810,7 @@ function lintText(text, code, terms, label, path19, line) {
666
810
  }
667
811
  return out;
668
812
  }
669
- function lintRequirement(req, path19, opts = {}) {
813
+ function lintRequirement(req, path21, opts = {}) {
670
814
  const out = [];
671
815
  const vague = opts.language === "en" ? VAGUE_EN : VAGUE_ES;
672
816
  const tech = opts.language === "en" ? TECH_EN : TECH_ES;
@@ -677,32 +821,32 @@ function lintRequirement(req, path19, opts = {}) {
677
821
  ...req.scenarios.flatMap((s) => [...s.when.map((w) => ({ text: w, line: s.line })), ...s.then.map((t) => ({ text: t, line: s.line }))])
678
822
  ];
679
823
  for (const part of parts) {
680
- 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));
681
825
  if (opts.businessOnly !== false) {
682
- 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));
683
827
  }
684
828
  }
685
829
  if (req.scenarios.length === 0) {
686
- 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" }));
687
831
  }
688
832
  return out;
689
833
  }
690
- function lintDelta(delta, livingRequirements, path19, opts = {}) {
834
+ function lintDelta(delta, livingRequirements, path21, opts = {}) {
691
835
  const out = [...delta.diagnostics];
692
836
  for (const req of [...delta.added, ...delta.modified]) {
693
- out.push(...lintRequirement(req, path19, opts));
837
+ out.push(...lintRequirement(req, path21, opts));
694
838
  }
695
839
  for (const req of delta.modified) {
696
840
  const living = livingRequirements.get(req.id);
697
841
  if (!living) {
698
- 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 }));
699
843
  continue;
700
844
  }
701
845
  for (const existing of living.scenarios) {
702
846
  if (!req.scenarios.some((s) => s.id === existing.id)) {
703
847
  out.push(
704
848
  diag("TRACE-007", "error", `MODIFIED ${req.id} pierde el escenario ${existing.id}: copia el bloque completo`, {
705
- path: path19,
849
+ path: path21,
706
850
  line: req.line,
707
851
  suggestion: "Copia el bloque completo de la spec viva y ed\xEDtalo; para quitarlo, decl\xE1ralo en REMOVED"
708
852
  })
@@ -713,15 +857,15 @@ function lintDelta(delta, livingRequirements, path19, opts = {}) {
713
857
  for (const req of delta.removed) {
714
858
  const living = livingRequirements.get(req.id);
715
859
  if (!living) {
716
- 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 }));
717
861
  }
718
862
  }
719
863
  for (const rename2 of delta.renamed) {
720
864
  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 }));
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 }));
722
866
  }
723
867
  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 }));
868
+ out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path21, line: rename2.line }));
725
869
  }
726
870
  }
727
871
  return out;
@@ -751,7 +895,7 @@ var MERMAID_KEYWORDS = [
751
895
  "architecture-beta"
752
896
  ];
753
897
  var MERMAID_BLOCKS = /^\s*(alt|loop|opt|par|rect|critical|break|subgraph)\b/;
754
- function lintPlan(planText, path19) {
898
+ function lintPlan(planText, path21) {
755
899
  const out = [];
756
900
  const blocks = [...planText.matchAll(/```mermaid\r?\n([\s\S]*?)```/g)];
757
901
  for (const [index, block] of blocks.entries()) {
@@ -761,7 +905,7 @@ function lintPlan(planText, path19) {
761
905
  if (!MERMAID_KEYWORDS.some((keyword) => first.startsWith(keyword))) {
762
906
  out.push(
763
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)}"`, {
764
- path: path19,
908
+ path: path21,
765
909
  suggestion: "Corrige el tipo del diagrama o elimina el bloque"
766
910
  })
767
911
  );
@@ -775,7 +919,7 @@ function lintPlan(planText, path19) {
775
919
  if (open !== 0) {
776
920
  out.push(
777
921
  diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: faltan ${Math.abs(open)} \`end\` (bloques alt/loop/subgraph sin cerrar)`, {
778
- path: path19,
922
+ path: path21,
779
923
  suggestion: "Cierra cada bloque alt/loop/opt/par/rect/subgraph con `end`"
780
924
  })
781
925
  );
@@ -787,7 +931,7 @@ function lintPlan(planText, path19) {
787
931
  if (message.includes(";")) {
788
932
  out.push(
789
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`, {
790
- path: path19,
934
+ path: path21,
791
935
  suggestion: "Sustituye `;` por `\xB7` o `,` en los mensajes de sequenceDiagram"
792
936
  })
793
937
  );
@@ -1116,6 +1260,17 @@ function deriveState(input) {
1116
1260
  }
1117
1261
  if (blockingFindings > 0) {
1118
1262
  blockedBy.push(`${blockingFindings} hallazgo(s) bloqueante(s)`);
1263
+ if (tasksTotal > 0 && tasksDone < tasksTotal) {
1264
+ return {
1265
+ state: "building",
1266
+ blockedBy,
1267
+ nextAction: next(`/satlas.build ${change.slug}`, `Construir en olas (${tasksDone}/${tasksTotal} tareas) \xB7 ${blockingFindings} hallazgo(s) pendientes`, true),
1268
+ progress
1269
+ };
1270
+ }
1271
+ if (tasksTotal > 0) {
1272
+ return { state: "built", blockedBy, nextAction: next(`satlas verify ${change.slug}`, "Registrar evidencia por escenario"), progress };
1273
+ }
1119
1274
  return { state: "spec_draft", blockedBy, nextAction: next(`satlas validate --change ${change.slug}`, "Corregir los hallazgos de la especificaci\xF3n"), progress };
1120
1275
  }
1121
1276
  if ((approval.status === "missing" || approval.status === "stale") && requiresMockups(change.meta, cfg) && input.mockupsReady !== true && !mockupOverride(change)) {
@@ -3996,8 +4151,361 @@ async function regenerateIndex(root, cfg, now = /* @__PURE__ */ new Date()) {
3996
4151
  await writeText(path16.join(root, ".sdd", "INDEX.md"), markdown);
3997
4152
  }
3998
4153
 
3999
- // src/doctor.ts
4154
+ // src/migrations.ts
4155
+ import { promises as fs2 } from "fs";
4000
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";
4001
4509
  async function runDoctor(root) {
4002
4510
  const findings = [];
4003
4511
  const { workspace, config } = await loadWorkspace(root);
@@ -4005,7 +4513,7 @@ async function runDoctor(root) {
4005
4513
  const approvals = await loadApprovals(workspace.sddDir);
4006
4514
  findings.push(...approvals.diagnostics);
4007
4515
  for (const change of workspace.changes) {
4008
- const deltaPath = path17.join(change.dir, "spec.md");
4516
+ const deltaPath = path19.join(change.dir, "spec.md");
4009
4517
  const deltaContent = await readTextIfExists(deltaPath);
4010
4518
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
4011
4519
  if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
@@ -4027,7 +4535,7 @@ async function runDoctor(root) {
4027
4535
  }
4028
4536
  for (const override of change.meta?.overrides ?? []) {
4029
4537
  if (!override.reason.trim() || !override.by.trim()) {
4030
- 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") }));
4031
4539
  }
4032
4540
  }
4033
4541
  }
@@ -4050,7 +4558,7 @@ async function specHashOf(filePath) {
4050
4558
  }
4051
4559
 
4052
4560
  // src/gate.ts
4053
- import path18 from "path";
4561
+ import path20 from "path";
4054
4562
  var UI_DOMAINS2 = /* @__PURE__ */ new Set(["frontend", "mobile", "fullstack"]);
4055
4563
  function count(name, diagnostics) {
4056
4564
  return {
@@ -4067,7 +4575,7 @@ function livingRequirementsMap(specs) {
4067
4575
  return map;
4068
4576
  }
4069
4577
  async function runCiGate(opts) {
4070
- const root = path18.resolve(opts.root);
4578
+ const root = path20.resolve(opts.root);
4071
4579
  const { workspace, config } = await loadWorkspace(root);
4072
4580
  const diagnostics = [];
4073
4581
  const checks = [];
@@ -4078,7 +4586,7 @@ async function runCiGate(opts) {
4078
4586
  let changesErrors = 0;
4079
4587
  let changesWarnings = 0;
4080
4588
  for (const change of workspace.changes) {
4081
- 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 }) : [];
4082
4590
  const trace = checkTrace({
4083
4591
  specs: workspace.specs,
4084
4592
  change,
@@ -4122,6 +4630,8 @@ async function runCiGate(opts) {
4122
4630
  return { checks, diagnostics, errors, warnings, failed };
4123
4631
  }
4124
4632
  export {
4633
+ BACKUP_DIRNAME,
4634
+ BACKUP_POINTER,
4125
4635
  BLOCK_HEAD_RE,
4126
4636
  BUILTIN_PACKS,
4127
4637
  CONFIG_FILE,
@@ -4131,14 +4641,21 @@ export {
4131
4641
  RULE_ID_RE,
4132
4642
  RULE_RE,
4133
4643
  RUN_EVENT_TYPES,
4644
+ SARIF_SCHEMA,
4645
+ SARIF_VERSION,
4134
4646
  SCENARIO_HEAD_RE,
4135
4647
  SCENARIO_ID_RE,
4648
+ SCHEMA_MIGRATIONS,
4649
+ SCHEMA_VERSION,
4136
4650
  SDD_DIR,
4137
4651
  SLUG_RE,
4138
4652
  TASK_ID_RE,
4139
4653
  TASK_LINE_RE,
4654
+ TOOL_NAME,
4655
+ TOOL_URL,
4140
4656
  adoptWorkspace,
4141
4657
  appendRunEvent,
4658
+ applyUpgrade,
4142
4659
  approvalsSchema,
4143
4660
  approveFromGithub,
4144
4661
  archiveChange,
@@ -4153,6 +4670,7 @@ export {
4153
4670
  checkMockups,
4154
4671
  checkTrace,
4155
4672
  collectMetrics,
4673
+ collectVersionedArtifacts,
4156
4674
  commentIssue,
4157
4675
  compareTaskIds,
4158
4676
  computeInputsHash,
@@ -4189,6 +4707,8 @@ export {
4189
4707
  ghAvailable,
4190
4708
  hasErrors,
4191
4709
  hasShellMetacharacters,
4710
+ impactOfFile,
4711
+ impactOfRequirement,
4192
4712
  indexMarkdown,
4193
4713
  initWorkspace,
4194
4714
  inline,
@@ -4237,6 +4757,7 @@ export {
4237
4757
  parseDelta,
4238
4758
  parseFrontmatter,
4239
4759
  parseGitHubRemote,
4760
+ parseGlossary,
4240
4761
  parseIssueUrl,
4241
4762
  parseRequirementBlocks,
4242
4763
  parseSpecFile,
@@ -4245,8 +4766,10 @@ export {
4245
4766
  patchChangeMeta,
4246
4767
  planBlock,
4247
4768
  planMockups,
4769
+ planUpgrade,
4248
4770
  planWaves,
4249
4771
  profileSchema,
4772
+ readArtifactVersion,
4250
4773
  readMockupManifest,
4251
4774
  readRun,
4252
4775
  readText,
@@ -4260,23 +4783,30 @@ export {
4260
4783
  renderTemplate,
4261
4784
  requiresMockups,
4262
4785
  resolvePacks,
4786
+ rollbackUpgrade,
4787
+ ruleDescription,
4263
4788
  runAnalyze,
4264
4789
  runCiGate,
4265
4790
  runDoctor,
4266
4791
  runProcess,
4792
+ sarifLevel,
4267
4793
  setMockupRequirement,
4268
4794
  sha256,
4269
4795
  shortHash,
4270
4796
  signApproval,
4271
4797
  specHashOf,
4272
4798
  splitCommand,
4799
+ stampSchemaVersion,
4273
4800
  stateLabel,
4274
4801
  syncGithubIssue,
4275
4802
  templatesFor,
4276
4803
  toPosix,
4804
+ toSarifReport,
4805
+ toSarifText,
4277
4806
  tokensFile,
4278
4807
  updateMockupScreenshots,
4279
4808
  updateRunStatus,
4809
+ upgradeAdvisory,
4280
4810
  verifyApproval,
4281
4811
  walkFiles,
4282
4812
  writeConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specatlas/core",
3
- "version": "0.1.21",
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",