@specatlas/core 0.1.3 → 0.1.5

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
@@ -202,6 +202,7 @@ interface Change {
202
202
  verify?: VerifyFile;
203
203
  fix?: VerifyFile;
204
204
  planPath?: string;
205
+ reviewPath?: string;
205
206
  mockupManifestPath?: string;
206
207
  diagnostics: Diagnostic[];
207
208
  }
@@ -1654,4 +1655,32 @@ interface DoctorReport {
1654
1655
  declare function runDoctor(root: string): Promise<DoctorReport>;
1655
1656
  declare function specHashOf(filePath: string): Promise<string | undefined>;
1656
1657
 
1657
- 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 Delta, type DeltaOp, type DeriveInput, type DerivedState, type DetectionResult, type Diagnostic, type DoctorReport, type Domain, type Evidence, type EvidenceMethod, type EvidenceResult, type ExecOptions, type ExecResult, type FoldOutcome, type FrontmatterResult, type GhRunner, type GitHubRepo, type InitOptions, type InitResult, type IssueBodyInput, type Lane, type Language, type LinkedIssue, type LintOptions, type LoadedConfig, type MockupCheckResult, type MockupManifest, type MockupPlan, type MockupPlanScreen, type MockupScreen, type NewChangeOptions, type NewChangeResult, type NextAction, type Override, type Pack, type PackCheck, type PackCheckResult, type PackCheckType, type PackEvaluation, type PresentOptions, type PresentResult, type ProfileMatch, REQ_HEAD_RE, REQ_ID_RE, RULE_ID_RE, RULE_RE, RUN_EVENT_TYPES, type RecordEvidenceOptions, type RecordEvidenceResult, type Rename, type Requirement, type RiskLevel, type Rule, type RunEvent, type RunEventType, type RunRecord, type RunState, type RunStatus, SCENARIO_HEAD_RE, SCENARIO_ID_RE, SDD_DIR, SLUG_RE, type Scenario, type Severity, type SignApprovalOptions, type SignApprovalResult, type SpecFile, type SpecRef, type StackProfile, type SyncGithubOptions, type SyncGithubResult, TASK_ID_RE, TASK_LINE_RE, type Task, type TaskBlock, type TasksFile, type TemplateSet, type TraceEdge, type TraceFinding, type TraceGraph, type TraceInput, type TraceNode, type TraceResult, type VerifyFile, type WalkEntry, type WavePlan, type Workspace, type WorkspaceMetrics, adoptWorkspace, appendRunEvent, approvalsSchema, approveFromGithub, archiveChange, artifactHash, atlasConfigSchema, buildTraceGraph, canonicalizeMarkdown, captureMockups, changeMarker, changeMetaSchema, changeMetaYaml, checkMockups, checkTrace, collectMetrics, commentIssue, compareTaskIds, computeInputsHash, configToYaml, copyFile, countBySeverity, createChange, createIssue, createRun, defaultConfig, deriveState, detectProfiles, detectRepo, detectionToYaml, diag, editIssue, emitFrontmatter, ensureDir, ensureSddDirs, esc, evaluatePacks, evidenceSummary, exists, findLinkedIssue, findWorkspaceRoot, firstToken, foldDelta, generatePresentation, generateRunId, getNumber, getString, ghAuthStatus, ghAvailable, hasErrors, hasShellMetacharacters, indexMarkdown, initWorkspace, inline, isCommandAllowed, isDirectory, issueBody, issueLabels, laneOrDefault, lintDelta, lintMockupHtml, lintMockupManifest, lintRequirement, lintSpec, lintTasks, lintVerify, listChangeSlugs, listDir, listDirs, listRuns, loadActiveProfile, loadApprovals, loadChange, loadConfig, loadDetectedBest, loadProfileFile, loadProfilesFromDir, loadProjectPacks, loadSpecs, loadWorkspace, matchGlob, mockupManifestSchema, mockupsDir, 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, resolvePacks, runAnalyze, runDoctor, runProcess, sha256, shortHash, signApproval, specHashOf, splitCommand, stateLabel, syncGithubIssue, templatesFor, toPosix, tokensFile, updateMockupScreenshots, updateRunStatus, verifyApproval, walkFiles, writeConfig, writeMockupManifest, writeMockupPlan, writeText };
1658
+ interface CiCheck {
1659
+ name: string;
1660
+ errors: number;
1661
+ warnings: number;
1662
+ }
1663
+ interface CiExtraCheck {
1664
+ name: string;
1665
+ diagnostics: Diagnostic[];
1666
+ }
1667
+ interface CiResult {
1668
+ checks: CiCheck[];
1669
+ diagnostics: Diagnostic[];
1670
+ errors: number;
1671
+ warnings: number;
1672
+ failed: boolean;
1673
+ }
1674
+ interface CiOptions {
1675
+ root: string;
1676
+ strict?: boolean;
1677
+ extra?: CiExtraCheck[];
1678
+ }
1679
+ declare function livingRequirementsMap(specs: Array<{
1680
+ spec: {
1681
+ requirements: Requirement[];
1682
+ };
1683
+ }>): Map<string, Requirement>;
1684
+ declare function runCiGate(opts: CiOptions): Promise<CiResult>;
1685
+
1686
+ export { type AdoptDomain, type AdoptOptions, type AdoptResult, type AgingBucket, type AnalyzeOptions, type AnalyzePackSummary, type AnalyzeResult, type Approval, type ApprovalStatus, type ApprovalsFile, type ApprovalsIndex, type ApproveFromGithubOptions, type ApproveFromGithubResult, type ArchiveOptions, type ArchiveResult, type AtlasConfig, type AttentionItem, BLOCK_HEAD_RE, BUILTIN_PACKS, type BlockWavePlan, CONFIG_FILE, CORE_VERSION, type CaptureResult, type Change, type ChangeMeta, type ChangeMetrics, type ChangeState, type CiCheck, type CiExtraCheck, type CiOptions, type CiResult, type Delta, type DeltaOp, type DeriveInput, type DerivedState, type DetectionResult, type Diagnostic, type DoctorReport, type Domain, type Evidence, type EvidenceMethod, type EvidenceResult, type ExecOptions, type ExecResult, type FoldOutcome, type FrontmatterResult, type GhRunner, type GitHubRepo, type InitOptions, type InitResult, type IssueBodyInput, type Lane, type Language, type LinkedIssue, type LintOptions, type LoadedConfig, type MockupCheckResult, type MockupManifest, type MockupPlan, type MockupPlanScreen, type MockupScreen, type NewChangeOptions, type NewChangeResult, type NextAction, type Override, type Pack, type PackCheck, type PackCheckResult, type PackCheckType, type PackEvaluation, type PresentOptions, type PresentResult, type ProfileMatch, REQ_HEAD_RE, REQ_ID_RE, RULE_ID_RE, RULE_RE, RUN_EVENT_TYPES, type RecordEvidenceOptions, type RecordEvidenceResult, type Rename, type Requirement, type RiskLevel, type Rule, type RunEvent, type RunEventType, type RunRecord, type RunState, type RunStatus, SCENARIO_HEAD_RE, SCENARIO_ID_RE, SDD_DIR, SLUG_RE, type Scenario, type Severity, type SignApprovalOptions, type SignApprovalResult, type SpecFile, type SpecRef, type StackProfile, type SyncGithubOptions, type SyncGithubResult, TASK_ID_RE, TASK_LINE_RE, type Task, type TaskBlock, type TasksFile, type TemplateSet, type TraceEdge, type TraceFinding, type TraceGraph, type TraceInput, type TraceNode, type TraceResult, type VerifyFile, type WalkEntry, type WavePlan, type Workspace, type WorkspaceMetrics, adoptWorkspace, appendRunEvent, approvalsSchema, approveFromGithub, archiveChange, artifactHash, atlasConfigSchema, buildTraceGraph, canonicalizeMarkdown, captureMockups, changeMarker, changeMetaSchema, changeMetaYaml, checkMockups, checkTrace, collectMetrics, commentIssue, compareTaskIds, computeInputsHash, configToYaml, copyFile, countBySeverity, createChange, createIssue, createRun, defaultConfig, deriveState, detectProfiles, detectRepo, detectionToYaml, diag, editIssue, emitFrontmatter, ensureDir, ensureSddDirs, esc, evaluatePacks, evidenceSummary, exists, findLinkedIssue, findWorkspaceRoot, firstToken, foldDelta, generatePresentation, generateRunId, getNumber, getString, ghAuthStatus, ghAvailable, hasErrors, hasShellMetacharacters, indexMarkdown, initWorkspace, inline, isCommandAllowed, isDirectory, issueBody, issueLabels, laneOrDefault, lintDelta, lintMockupHtml, lintMockupManifest, lintRequirement, lintSpec, lintTasks, lintVerify, listChangeSlugs, listDir, listDirs, listRuns, livingRequirementsMap, loadActiveProfile, loadApprovals, loadChange, loadConfig, loadDetectedBest, loadProfileFile, loadProfilesFromDir, loadProjectPacks, loadSpecs, loadWorkspace, matchGlob, mockupManifestSchema, mockupsDir, 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, resolvePacks, runAnalyze, runCiGate, runDoctor, runProcess, sha256, shortHash, signApproval, specHashOf, splitCommand, stateLabel, syncGithubIssue, templatesFor, toPosix, tokensFile, updateMockupScreenshots, updateRunStatus, verifyApproval, walkFiles, writeConfig, writeMockupManifest, writeMockupPlan, writeText };
package/dist/index.js CHANGED
@@ -645,14 +645,14 @@ function wordBoundary(text, term) {
645
645
  const re = new RegExp(`(?<![\\p{L}\\p{N}])${escaped}(?![\\p{L}\\p{N}])`, "iu");
646
646
  return re.test(text);
647
647
  }
648
- function lintText(text, code, terms, label, path18, line) {
648
+ function lintText(text, code, terms, label, path19, line) {
649
649
  const out = [];
650
650
  const lower = text.toLowerCase();
651
651
  for (const term of terms) {
652
652
  if (wordBoundary(lower, term)) {
653
653
  out.push(
654
654
  diag(code, "error", `${label}: "${term}"`, {
655
- path: path18,
655
+ path: path19,
656
656
  line,
657
657
  suggestion: "La especificaci\xF3n es funcional y de negocio: describe comportamiento, no tecnolog\xEDa ni adjetivos vagos"
658
658
  })
@@ -661,7 +661,7 @@ function lintText(text, code, terms, label, path18, line) {
661
661
  }
662
662
  return out;
663
663
  }
664
- function lintRequirement(req, path18, opts = {}) {
664
+ function lintRequirement(req, path19, opts = {}) {
665
665
  const out = [];
666
666
  const vague = opts.language === "en" ? VAGUE_EN : VAGUE_ES;
667
667
  const tech = opts.language === "en" ? TECH_EN : TECH_ES;
@@ -672,32 +672,32 @@ function lintRequirement(req, path18, opts = {}) {
672
672
  ...req.scenarios.flatMap((s) => [...s.when.map((w) => ({ text: w, line: s.line })), ...s.then.map((t) => ({ text: t, line: s.line }))])
673
673
  ];
674
674
  for (const part of parts) {
675
- out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path18, part.line));
675
+ out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path19, part.line));
676
676
  if (opts.businessOnly !== false) {
677
- out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path18, part.line));
677
+ out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path19, part.line));
678
678
  }
679
679
  }
680
680
  if (req.scenarios.length === 0) {
681
- out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path18, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
681
+ 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" }));
682
682
  }
683
683
  return out;
684
684
  }
685
- function lintDelta(delta, livingRequirements, path18, opts = {}) {
685
+ function lintDelta(delta, livingRequirements, path19, opts = {}) {
686
686
  const out = [...delta.diagnostics];
687
687
  for (const req of [...delta.added, ...delta.modified]) {
688
- out.push(...lintRequirement(req, path18, opts));
688
+ out.push(...lintRequirement(req, path19, opts));
689
689
  }
690
690
  for (const req of delta.modified) {
691
691
  const living = livingRequirements.get(req.id);
692
692
  if (!living) {
693
- out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path18, line: req.line }));
693
+ out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path19, line: req.line }));
694
694
  continue;
695
695
  }
696
696
  for (const existing of living.scenarios) {
697
697
  if (!req.scenarios.some((s) => s.id === existing.id)) {
698
698
  out.push(
699
699
  diag("TRACE-007", "error", `MODIFIED ${req.id} pierde el escenario ${existing.id}: copia el bloque completo`, {
700
- path: path18,
700
+ path: path19,
701
701
  line: req.line,
702
702
  suggestion: "Copia el bloque completo de la spec viva y ed\xEDtalo; para quitarlo, decl\xE1ralo en REMOVED"
703
703
  })
@@ -708,15 +708,15 @@ function lintDelta(delta, livingRequirements, path18, opts = {}) {
708
708
  for (const req of delta.removed) {
709
709
  const living = livingRequirements.get(req.id);
710
710
  if (!living) {
711
- out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path18, line: req.line }));
711
+ out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path19, line: req.line }));
712
712
  }
713
713
  }
714
714
  for (const rename of delta.renamed) {
715
715
  if (rename.from.id !== rename.to.id) {
716
- out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename.from.id} \u2192 ${rename.to.id}); los ids son inmutables`, { path: path18, line: rename.line }));
716
+ out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename.from.id} \u2192 ${rename.to.id}); los ids son inmutables`, { path: path19, line: rename.line }));
717
717
  }
718
718
  if (!livingRequirements.has(rename.from.id)) {
719
- out.push(diag("TRACE-007", "error", `RENAMED ${rename.from.id} no existe en la spec viva`, { path: path18, line: rename.line }));
719
+ out.push(diag("TRACE-007", "error", `RENAMED ${rename.from.id} no existe en la spec viva`, { path: path19, line: rename.line }));
720
720
  }
721
721
  }
722
722
  return out;
@@ -1059,7 +1059,7 @@ function deriveState(input) {
1059
1059
  blockedBy.push(`evidencia ${scenariosEvidenced}/${deltaScenarios.length}`);
1060
1060
  return { state: "built", blockedBy, nextAction: next(`satlas verify ${change.slug}`, "Registrar evidencia por escenario"), progress };
1061
1061
  }
1062
- if (lane === "full" && cfg.gates.review.mode === "blocking" && !change.verify) {
1062
+ if (lane === "full" && cfg.gates.review.mode === "blocking" && !change.reviewPath) {
1063
1063
  blockedBy.push("review pendiente");
1064
1064
  return { state: "verified", blockedBy, nextAction: next(`/satlas.review ${change.slug}`, "Revisi\xF3n de c\xF3digo", true), progress };
1065
1065
  }
@@ -1278,6 +1278,8 @@ async function loadChange(root, slug, relDir) {
1278
1278
  }
1279
1279
  const planFile = path4.join(dir, "plan.md");
1280
1280
  if (await exists(planFile)) change.planPath = planFile;
1281
+ const reviewFile = path4.join(dir, "review.md");
1282
+ if (await exists(reviewFile)) change.reviewPath = reviewFile;
1281
1283
  const tasksFile = path4.join(dir, "tasks.md");
1282
1284
  const tasksRaw = await readTextIfExists(tasksFile);
1283
1285
  if (tasksRaw !== void 0) {
@@ -3862,6 +3864,75 @@ async function specHashOf(filePath) {
3862
3864
  if (content === void 0) return void 0;
3863
3865
  return artifactHash(content);
3864
3866
  }
3867
+
3868
+ // src/gate.ts
3869
+ import path18 from "path";
3870
+ var UI_DOMAINS2 = /* @__PURE__ */ new Set(["frontend", "mobile", "fullstack"]);
3871
+ function count(name, diagnostics) {
3872
+ return {
3873
+ name,
3874
+ errors: diagnostics.filter((d) => d.severity === "error").length,
3875
+ warnings: diagnostics.filter((d) => d.severity === "warning").length
3876
+ };
3877
+ }
3878
+ function livingRequirementsMap(specs) {
3879
+ const map = /* @__PURE__ */ new Map();
3880
+ for (const spec of specs) {
3881
+ for (const requirement of spec.spec.requirements) map.set(requirement.id, requirement);
3882
+ }
3883
+ return map;
3884
+ }
3885
+ async function runCiGate(opts) {
3886
+ const root = path18.resolve(opts.root);
3887
+ const { workspace, config } = await loadWorkspace(root);
3888
+ const diagnostics = [];
3889
+ const checks = [];
3890
+ const specFindings = workspace.specs.flatMap((spec) => lintSpec(spec.spec, { language: config.spec.language, businessOnly: config.spec.business_only }));
3891
+ diagnostics.push(...specFindings);
3892
+ checks.push(count("specs vivas", specFindings));
3893
+ const living = livingRequirementsMap(workspace.specs);
3894
+ let changesErrors = 0;
3895
+ let changesWarnings = 0;
3896
+ for (const change of workspace.changes) {
3897
+ const lintFindings = change.delta ? lintDelta(change.delta, living, path18.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
3898
+ const trace = checkTrace({
3899
+ specs: workspace.specs,
3900
+ change,
3901
+ requireEvidence: config.gates.verify.mode !== "off" && config.gates.verify.require_evidence
3902
+ });
3903
+ const changeDiags = [...lintFindings, ...trace.findings];
3904
+ if (change.tasks && change.tasks.counts.total > 0) {
3905
+ const plan = planWaves(change.tasks, { maxParallel: config.waves.max_parallel });
3906
+ changeDiags.push(...plan.blocks.flatMap((block) => block.diagnostics));
3907
+ }
3908
+ if (UI_DOMAINS2.has(change.meta?.domain ?? "")) {
3909
+ const mockups = await checkMockups(root, change.slug, change);
3910
+ changeDiags.push(...mockups.findings);
3911
+ }
3912
+ if (config.packs.length > 0) {
3913
+ const resolved = await resolvePacks(workspace.sddDir, config);
3914
+ const evaluations = evaluatePacks(resolved.packs, change, config);
3915
+ changeDiags.push(...packFindings(evaluations, change));
3916
+ }
3917
+ diagnostics.push(...changeDiags);
3918
+ const entry = count(change.slug, changeDiags);
3919
+ changesErrors += entry.errors;
3920
+ changesWarnings += entry.warnings;
3921
+ }
3922
+ checks.push({ name: "cambios (lint + trace + waves + mockups)", errors: changesErrors, warnings: changesWarnings });
3923
+ const doctor = await runDoctor(root);
3924
+ const doctorErrors = doctor.findings.filter((d) => d.severity === "error");
3925
+ diagnostics.push(...doctorErrors);
3926
+ checks.push({ name: "doctor", errors: doctorErrors.length, warnings: 0 });
3927
+ for (const extra of opts.extra ?? []) {
3928
+ diagnostics.push(...extra.diagnostics);
3929
+ checks.push(count(extra.name, extra.diagnostics));
3930
+ }
3931
+ const errors = diagnostics.filter((d) => d.severity === "error").length;
3932
+ const warnings = diagnostics.filter((d) => d.severity === "warning").length;
3933
+ const failed = errors > 0 || (opts.strict ?? false) && warnings > 0;
3934
+ return { checks, diagnostics, errors, warnings, failed };
3935
+ }
3865
3936
  export {
3866
3937
  BLOCK_HEAD_RE,
3867
3938
  BUILTIN_PACKS,
@@ -3949,6 +4020,7 @@ export {
3949
4020
  listDir,
3950
4021
  listDirs,
3951
4022
  listRuns,
4023
+ livingRequirementsMap,
3952
4024
  loadActiveProfile,
3953
4025
  loadApprovals,
3954
4026
  loadChange,
@@ -3992,6 +4064,7 @@ export {
3992
4064
  renderTemplate,
3993
4065
  resolvePacks,
3994
4066
  runAnalyze,
4067
+ runCiGate,
3995
4068
  runDoctor,
3996
4069
  runProcess,
3997
4070
  sha256,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specatlas/core",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Kernel determinista de SpecAtlas: parsing, validación, trazabilidad, olas y ciclo de vida",
5
5
  "license": "MIT",
6
6
  "type": "module",