@specatlas/core 0.1.4 → 0.1.6

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
@@ -203,6 +203,7 @@ interface Change {
203
203
  fix?: VerifyFile;
204
204
  planPath?: string;
205
205
  reviewPath?: string;
206
+ presentationPath?: string;
206
207
  mockupManifestPath?: string;
207
208
  diagnostics: Diagnostic[];
208
209
  }
@@ -1655,4 +1656,32 @@ interface DoctorReport {
1655
1656
  declare function runDoctor(root: string): Promise<DoctorReport>;
1656
1657
  declare function specHashOf(filePath: string): Promise<string | undefined>;
1657
1658
 
1658
- 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 };
1659
+ interface CiCheck {
1660
+ name: string;
1661
+ errors: number;
1662
+ warnings: number;
1663
+ }
1664
+ interface CiExtraCheck {
1665
+ name: string;
1666
+ diagnostics: Diagnostic[];
1667
+ }
1668
+ interface CiResult {
1669
+ checks: CiCheck[];
1670
+ diagnostics: Diagnostic[];
1671
+ errors: number;
1672
+ warnings: number;
1673
+ failed: boolean;
1674
+ }
1675
+ interface CiOptions {
1676
+ root: string;
1677
+ strict?: boolean;
1678
+ extra?: CiExtraCheck[];
1679
+ }
1680
+ declare function livingRequirementsMap(specs: Array<{
1681
+ spec: {
1682
+ requirements: Requirement[];
1683
+ };
1684
+ }>): Map<string, Requirement>;
1685
+ declare function runCiGate(opts: CiOptions): Promise<CiResult>;
1686
+
1687
+ 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;
@@ -1042,10 +1042,11 @@ function deriveState(input) {
1042
1042
  }
1043
1043
  if (approval.status === "missing" || approval.status === "stale") {
1044
1044
  blockedBy.push(approval.status === "stale" ? "la firma de la spec qued\xF3 obsoleta (el archivo cambi\xF3)" : "la spec no est\xE1 aprobada");
1045
+ const presented = change.presentationPath !== void 0;
1045
1046
  return {
1046
1047
  state: "awaiting_approval",
1047
1048
  blockedBy,
1048
- nextAction: next(`satlas present ${change.slug}`, "Presentar la propuesta y firmar la aprobaci\xF3n (satlas approve)"),
1049
+ nextAction: presented ? next(`satlas approve ${change.slug} --by "<nombre>"`, "Firmar la aprobaci\xF3n (la presentaci\xF3n ya est\xE1 generada)") : next(`satlas present ${change.slug}`, "Presentar la propuesta y firmar la aprobaci\xF3n (satlas approve)"),
1049
1050
  progress
1050
1051
  };
1051
1052
  }
@@ -1280,6 +1281,8 @@ async function loadChange(root, slug, relDir) {
1280
1281
  if (await exists(planFile)) change.planPath = planFile;
1281
1282
  const reviewFile = path4.join(dir, "review.md");
1282
1283
  if (await exists(reviewFile)) change.reviewPath = reviewFile;
1284
+ const presentationFile = path4.join(dir, "presentation", "index.html");
1285
+ if (await exists(presentationFile)) change.presentationPath = presentationFile;
1283
1286
  const tasksFile = path4.join(dir, "tasks.md");
1284
1287
  const tasksRaw = await readTextIfExists(tasksFile);
1285
1288
  if (tasksRaw !== void 0) {
@@ -3864,6 +3867,75 @@ async function specHashOf(filePath) {
3864
3867
  if (content === void 0) return void 0;
3865
3868
  return artifactHash(content);
3866
3869
  }
3870
+
3871
+ // src/gate.ts
3872
+ import path18 from "path";
3873
+ var UI_DOMAINS2 = /* @__PURE__ */ new Set(["frontend", "mobile", "fullstack"]);
3874
+ function count(name, diagnostics) {
3875
+ return {
3876
+ name,
3877
+ errors: diagnostics.filter((d) => d.severity === "error").length,
3878
+ warnings: diagnostics.filter((d) => d.severity === "warning").length
3879
+ };
3880
+ }
3881
+ function livingRequirementsMap(specs) {
3882
+ const map = /* @__PURE__ */ new Map();
3883
+ for (const spec of specs) {
3884
+ for (const requirement of spec.spec.requirements) map.set(requirement.id, requirement);
3885
+ }
3886
+ return map;
3887
+ }
3888
+ async function runCiGate(opts) {
3889
+ const root = path18.resolve(opts.root);
3890
+ const { workspace, config } = await loadWorkspace(root);
3891
+ const diagnostics = [];
3892
+ const checks = [];
3893
+ const specFindings = workspace.specs.flatMap((spec) => lintSpec(spec.spec, { language: config.spec.language, businessOnly: config.spec.business_only }));
3894
+ diagnostics.push(...specFindings);
3895
+ checks.push(count("specs vivas", specFindings));
3896
+ const living = livingRequirementsMap(workspace.specs);
3897
+ let changesErrors = 0;
3898
+ let changesWarnings = 0;
3899
+ for (const change of workspace.changes) {
3900
+ const lintFindings = change.delta ? lintDelta(change.delta, living, path18.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
3901
+ const trace = checkTrace({
3902
+ specs: workspace.specs,
3903
+ change,
3904
+ requireEvidence: config.gates.verify.mode !== "off" && config.gates.verify.require_evidence
3905
+ });
3906
+ const changeDiags = [...lintFindings, ...trace.findings];
3907
+ if (change.tasks && change.tasks.counts.total > 0) {
3908
+ const plan = planWaves(change.tasks, { maxParallel: config.waves.max_parallel });
3909
+ changeDiags.push(...plan.blocks.flatMap((block) => block.diagnostics));
3910
+ }
3911
+ if (UI_DOMAINS2.has(change.meta?.domain ?? "")) {
3912
+ const mockups = await checkMockups(root, change.slug, change);
3913
+ changeDiags.push(...mockups.findings);
3914
+ }
3915
+ if (config.packs.length > 0) {
3916
+ const resolved = await resolvePacks(workspace.sddDir, config);
3917
+ const evaluations = evaluatePacks(resolved.packs, change, config);
3918
+ changeDiags.push(...packFindings(evaluations, change));
3919
+ }
3920
+ diagnostics.push(...changeDiags);
3921
+ const entry = count(change.slug, changeDiags);
3922
+ changesErrors += entry.errors;
3923
+ changesWarnings += entry.warnings;
3924
+ }
3925
+ checks.push({ name: "cambios (lint + trace + waves + mockups)", errors: changesErrors, warnings: changesWarnings });
3926
+ const doctor = await runDoctor(root);
3927
+ const doctorErrors = doctor.findings.filter((d) => d.severity === "error");
3928
+ diagnostics.push(...doctorErrors);
3929
+ checks.push({ name: "doctor", errors: doctorErrors.length, warnings: 0 });
3930
+ for (const extra of opts.extra ?? []) {
3931
+ diagnostics.push(...extra.diagnostics);
3932
+ checks.push(count(extra.name, extra.diagnostics));
3933
+ }
3934
+ const errors = diagnostics.filter((d) => d.severity === "error").length;
3935
+ const warnings = diagnostics.filter((d) => d.severity === "warning").length;
3936
+ const failed = errors > 0 || (opts.strict ?? false) && warnings > 0;
3937
+ return { checks, diagnostics, errors, warnings, failed };
3938
+ }
3867
3939
  export {
3868
3940
  BLOCK_HEAD_RE,
3869
3941
  BUILTIN_PACKS,
@@ -3951,6 +4023,7 @@ export {
3951
4023
  listDir,
3952
4024
  listDirs,
3953
4025
  listRuns,
4026
+ livingRequirementsMap,
3954
4027
  loadActiveProfile,
3955
4028
  loadApprovals,
3956
4029
  loadChange,
@@ -3994,6 +4067,7 @@ export {
3994
4067
  renderTemplate,
3995
4068
  resolvePacks,
3996
4069
  runAnalyze,
4070
+ runCiGate,
3997
4071
  runDoctor,
3998
4072
  runProcess,
3999
4073
  sha256,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specatlas/core",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Kernel determinista de SpecAtlas: parsing, validación, trazabilidad, olas y ciclo de vida",
5
5
  "license": "MIT",
6
6
  "type": "module",