@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 +180 -1
- package/dist/index.js +553 -23
- package/package.json +1 -1
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,
|
|
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:
|
|
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,
|
|
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",
|
|
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",
|
|
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:
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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/
|
|
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 =
|
|
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:
|
|
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
|
|
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 =
|
|
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,
|
|
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,
|