@specatlas/core 0.1.25 → 0.1.27

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
@@ -195,6 +195,17 @@ interface MockupManifest {
195
195
  tokens?: string;
196
196
  screens: MockupScreen[];
197
197
  }
198
+ interface ClarifyItem {
199
+ text: string;
200
+ line: number;
201
+ answer?: string;
202
+ }
203
+ interface ClarifyFile {
204
+ path: string;
205
+ open: ClarifyItem[];
206
+ resolved: ClarifyItem[];
207
+ diagnostics: Diagnostic[];
208
+ }
198
209
  interface Change {
199
210
  slug: string;
200
211
  dir: string;
@@ -204,6 +215,9 @@ interface Change {
204
215
  verify?: VerifyFile;
205
216
  fix?: VerifyFile;
206
217
  fixCovers?: string[];
218
+ clarify?: ClarifyFile;
219
+ clarifyPath?: string;
220
+ docsPaths?: string[];
207
221
  planPath?: string;
208
222
  reviewPath?: string;
209
223
  presentationPath?: string;
@@ -295,6 +309,20 @@ declare const atlasConfigSchema: z.ZodObject<{
295
309
  }, {
296
310
  mode?: "off" | "advisory" | "blocking" | undefined;
297
311
  }>>;
312
+ clarify: z.ZodDefault<z.ZodObject<{
313
+ mode: z.ZodDefault<z.ZodEnum<["off", "advisory", "blocking"]>>;
314
+ }, "strip", z.ZodTypeAny, {
315
+ mode: "off" | "advisory" | "blocking";
316
+ }, {
317
+ mode?: "off" | "advisory" | "blocking" | undefined;
318
+ }>>;
319
+ docs: z.ZodDefault<z.ZodObject<{
320
+ mode: z.ZodDefault<z.ZodEnum<["off", "advisory", "blocking"]>>;
321
+ }, "strip", z.ZodTypeAny, {
322
+ mode: "off" | "advisory" | "blocking";
323
+ }, {
324
+ mode?: "off" | "advisory" | "blocking" | undefined;
325
+ }>>;
298
326
  mockup: z.ZodDefault<z.ZodObject<{
299
327
  require_approval: z.ZodDefault<z.ZodBoolean>;
300
328
  compare_in_verify: z.ZodDefault<z.ZodBoolean>;
@@ -319,6 +347,12 @@ declare const atlasConfigSchema: z.ZodObject<{
319
347
  review: {
320
348
  mode: "off" | "advisory" | "blocking";
321
349
  };
350
+ clarify: {
351
+ mode: "off" | "advisory" | "blocking";
352
+ };
353
+ docs: {
354
+ mode: "off" | "advisory" | "blocking";
355
+ };
322
356
  mockup: {
323
357
  require_approval: boolean;
324
358
  compare_in_verify: boolean;
@@ -337,6 +371,12 @@ declare const atlasConfigSchema: z.ZodObject<{
337
371
  review?: {
338
372
  mode?: "off" | "advisory" | "blocking" | undefined;
339
373
  } | undefined;
374
+ clarify?: {
375
+ mode?: "off" | "advisory" | "blocking" | undefined;
376
+ } | undefined;
377
+ docs?: {
378
+ mode?: "off" | "advisory" | "blocking" | undefined;
379
+ } | undefined;
340
380
  mockup?: {
341
381
  require_approval?: boolean | undefined;
342
382
  compare_in_verify?: boolean | undefined;
@@ -452,6 +492,12 @@ declare const atlasConfigSchema: z.ZodObject<{
452
492
  review: {
453
493
  mode: "off" | "advisory" | "blocking";
454
494
  };
495
+ clarify: {
496
+ mode: "off" | "advisory" | "blocking";
497
+ };
498
+ docs: {
499
+ mode: "off" | "advisory" | "blocking";
500
+ };
455
501
  mockup: {
456
502
  require_approval: boolean;
457
503
  compare_in_verify: boolean;
@@ -515,6 +561,12 @@ declare const atlasConfigSchema: z.ZodObject<{
515
561
  review?: {
516
562
  mode?: "off" | "advisory" | "blocking" | undefined;
517
563
  } | undefined;
564
+ clarify?: {
565
+ mode?: "off" | "advisory" | "blocking" | undefined;
566
+ } | undefined;
567
+ docs?: {
568
+ mode?: "off" | "advisory" | "blocking" | undefined;
569
+ } | undefined;
518
570
  mockup?: {
519
571
  require_approval?: boolean | undefined;
520
572
  compare_in_verify?: boolean | undefined;
@@ -803,6 +855,8 @@ declare function parseApprovals(raw: string, filePath: string): {
803
855
  diagnostics: Diagnostic[];
804
856
  };
805
857
 
858
+ declare function parseClarify(content: string, filePath: string): ClarifyFile;
859
+
806
860
  interface GlossaryTerm {
807
861
  term: string;
808
862
  definition: string;
@@ -910,7 +964,7 @@ declare function planWaves(tasks: TasksFile, opts?: {
910
964
  }): WavePlan;
911
965
  declare function planBlock(block: TaskBlock, maxParallel: number): BlockWavePlan;
912
966
 
913
- type ChangeState = 'draft' | 'spec_draft' | 'awaiting_mockups' | 'awaiting_approval' | 'approved' | 'planned' | 'building' | 'built' | 'verified' | 'ready' | 'archived';
967
+ type ChangeState = 'draft' | 'spec_draft' | 'awaiting_mockups' | 'awaiting_approval' | 'approved' | 'planned' | 'building' | 'built' | 'verified' | 'reviewed' | 'ready' | 'archived';
914
968
  interface NextAction {
915
969
  command: string;
916
970
  description: string;
@@ -946,6 +1000,9 @@ interface DeriveInput {
946
1000
  specContent?: string;
947
1001
  }
948
1002
  declare function requiresMockups(meta: ChangeMeta | undefined, cfg: AtlasConfig): boolean;
1003
+ declare function docsReady(change: Change): boolean;
1004
+ declare function clarifyAdvisory(change: Change, cfg: AtlasConfig): Diagnostic[];
1005
+ declare function docsAdvisory(change: Change, cfg: AtlasConfig): Diagnostic[];
949
1006
  declare function deriveState(input: DeriveInput): DerivedState;
950
1007
  declare function stateLabel(state: ChangeState): string;
951
1008
 
@@ -1125,6 +1182,8 @@ interface TemplateSet {
1125
1182
  tasks: string;
1126
1183
  verify: string;
1127
1184
  fix: string;
1185
+ docTecnica: string;
1186
+ docManual: string;
1128
1187
  }
1129
1188
  declare function templatesFor(language: Language): TemplateSet;
1130
1189
  declare function renderTemplate(template: string, vars: Record<string, string>): string;
@@ -1804,10 +1863,11 @@ interface LivingFix {
1804
1863
  file: string;
1805
1864
  date: string;
1806
1865
  result: string;
1807
- domain?: string;
1808
- title?: string;
1809
1866
  covers: string[];
1810
1867
  content: string;
1868
+ source: 'living' | 'archive';
1869
+ domain?: string;
1870
+ title?: string;
1811
1871
  }
1812
1872
  declare function parseFixCovers(content: string): string[];
1813
1873
  declare function parseLivingFix(content: string, file: string): LivingFix;
@@ -1828,6 +1888,27 @@ interface WriteLivingFixResult {
1828
1888
  }
1829
1889
  declare function writeLivingFix(root: string, input: WriteLivingFixInput): Promise<WriteLivingFixResult>;
1830
1890
 
1891
+ type DocsTipo = 'tecnica' | 'manual' | 'all';
1892
+ declare const DOCS_MARKER_START = "<!-- specatlas:generado:inicio -->";
1893
+ declare const DOCS_MARKER_END = "<!-- specatlas:generado:fin -->";
1894
+ interface GenerateDocsOptions {
1895
+ root: string;
1896
+ slug: string;
1897
+ tipo?: DocsTipo;
1898
+ now?: Date;
1899
+ }
1900
+ interface GeneratedDoc {
1901
+ tipo: 'tecnica' | 'manual';
1902
+ path: string;
1903
+ created: boolean;
1904
+ }
1905
+ interface GenerateDocsResult {
1906
+ slug: string;
1907
+ files: GeneratedDoc[];
1908
+ diagnostics: Diagnostic[];
1909
+ }
1910
+ declare function generateDocs(opts: GenerateDocsOptions): Promise<GenerateDocsResult>;
1911
+
1831
1912
  declare const SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
1832
1913
  declare const SARIF_VERSION = "2.1.0";
1833
1914
  declare const TOOL_NAME = "SpecAtlas";
@@ -1927,4 +2008,4 @@ declare function livingRequirementsMap(specs: Array<{
1927
2008
  }>): Map<string, Requirement>;
1928
2009
  declare function runCiGate(opts: CiOptions): Promise<CiResult>;
1929
2010
 
1930
- 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, LIVING_FIXES_DIR, type Lane, type Language, type LinkedIssue, type LintOptions, type LivingFix, 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, type WriteLivingFixInput, type WriteLivingFixResult, 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, loadLivingFixes, loadProfileFile, loadProfilesFromDir, loadProjectPacks, loadSpecs, loadWorkspace, localCompact, localDate, localMonth, localOffset, localStamp, matchGlob, mockupManifestSchema, mockupsDir, mockupsReady, moveDirectory, packFindings, parseApprovals, parseChangeMeta, parseConfig, parseDelta, parseFixCovers, parseFrontmatter, parseGitHubRemote, parseGlossary, parseIssueUrl, parseLivingFix, 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, writeLivingFix, writeMockupManifest, writeMockupPlan, writeText };
2011
+ 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 ClarifyFile, type ClarifyItem, DOCS_MARKER_END, DOCS_MARKER_START, type Delta, type DeltaOp, type DeriveInput, type DerivedState, type DetectionResult, type Diagnostic, type DocsTipo, type DoctorReport, type Domain, type Evidence, type EvidenceMethod, type EvidenceResult, type ExecOptions, type ExecResult, type FoldOutcome, type FrontmatterResult, type GenerateDocsOptions, type GenerateDocsResult, type GeneratedDoc, type GhRunner, type GitHubRepo, type GlossaryTerm, type ImpactEvidence, type ImpactReport, type ImpactTask, type InitOptions, type InitResult, type IssueBodyInput, LIVING_FIXES_DIR, type Lane, type Language, type LinkedIssue, type LintOptions, type LivingFix, 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, type WriteLivingFixInput, type WriteLivingFixResult, adoptWorkspace, appendRunEvent, applyUpgrade, approvalsSchema, approveFromGithub, archiveChange, artifactHash, atlasConfigSchema, buildTraceGraph, canonicalizeMarkdown, captureMockups, changeMarker, changeMetaSchema, changeMetaYaml, checkMockups, checkTrace, clarifyAdvisory, collectMetrics, collectVersionedArtifacts, commentIssue, compareTaskIds, computeInputsHash, configToYaml, copyFile, countBySeverity, createChange, createIssue, createRun, defaultConfig, deriveState, detectProfiles, detectRepo, detectionToYaml, diag, docsAdvisory, docsReady, editIssue, emitFrontmatter, ensureDir, ensureSddDirs, esc, evaluatePacks, evidenceSummary, exists, findLinkedIssue, findWorkspaceRoot, firstToken, foldDelta, generateDocs, 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, loadLivingFixes, loadProfileFile, loadProfilesFromDir, loadProjectPacks, loadSpecs, loadWorkspace, localCompact, localDate, localMonth, localOffset, localStamp, matchGlob, mockupManifestSchema, mockupsDir, mockupsReady, moveDirectory, packFindings, parseApprovals, parseChangeMeta, parseClarify, parseConfig, parseDelta, parseFixCovers, parseFrontmatter, parseGitHubRemote, parseGlossary, parseIssueUrl, parseLivingFix, 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, writeLivingFix, writeMockupManifest, writeMockupPlan, writeText };
package/dist/index.js CHANGED
@@ -61,6 +61,8 @@ var atlasConfigSchema = z.object({
61
61
  analyze: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("blocking"), min_severity: z.enum(["low", "medium", "high"]).default("medium") }).default({}),
62
62
  verify: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("blocking"), require_evidence: z.boolean().default(true) }).default({}),
63
63
  review: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("advisory") }).default({}),
64
+ clarify: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("advisory") }).default({}),
65
+ docs: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("blocking") }).default({}),
64
66
  mockup: z.object({ require_approval: z.boolean().default(false), compare_in_verify: z.boolean().default(false) }).default({})
65
67
  }).default({}),
66
68
  trace: z.object({ mode: z.enum(["off", "advisory", "blocking"]).default("blocking"), prefix: z.string().default("REQ") }).default({}),
@@ -640,6 +642,35 @@ function findScenarioHeading(lines, blockLineIndex) {
640
642
  return void 0;
641
643
  }
642
644
 
645
+ // src/parse/clarify.ts
646
+ var OPEN_RE = /^\s*-\s*\[\s\]\s+(.+?)\s*$/;
647
+ var DONE_RE = /^\s*-\s*\[[xX]\]\s+(.+?)\s*$/;
648
+ var ANSWER_SEPARATOR = " \u2014 ";
649
+ function splitAnswer(raw) {
650
+ const index = raw.indexOf(ANSWER_SEPARATOR);
651
+ if (index === -1) return { text: raw.trim() };
652
+ return { text: raw.slice(0, index).trim(), answer: raw.slice(index + ANSWER_SEPARATOR.length).trim() };
653
+ }
654
+ function parseClarify(content, filePath) {
655
+ const fm = parseFrontmatter(content, filePath);
656
+ const lines = fm.body.replace(/\r\n?/g, "\n").split("\n");
657
+ const open = [];
658
+ const resolved = [];
659
+ for (let i = 0; i < lines.length; i += 1) {
660
+ const line = lines[i] ?? "";
661
+ const lineNo = fm.bodyStartLine + i;
662
+ const done = DONE_RE.exec(line);
663
+ if (done) {
664
+ const item = splitAnswer(done[1] ?? "");
665
+ resolved.push({ text: item.text, line: lineNo, ...item.answer !== void 0 ? { answer: item.answer } : {} });
666
+ continue;
667
+ }
668
+ const pending = OPEN_RE.exec(line);
669
+ if (pending) open.push({ text: pending[1] ?? "", line: lineNo });
670
+ }
671
+ return { path: filePath, open, resolved, diagnostics: [] };
672
+ }
673
+
643
674
  // src/parse/glossary.ts
644
675
  var HEADER_KEYS = /* @__PURE__ */ new Set(["t\xE9rmino", "termino", "term", "definici\xF3n", "definicion", "definition", "sin\xF3nimos", "sinonimos", "synonyms"]);
645
676
  var SEPARATOR_RE = /^:?-{2,}:?$/;
@@ -794,14 +825,14 @@ function wordBoundary(text, term) {
794
825
  const re = new RegExp(`(?<![\\p{L}\\p{N}])${escaped}(?![\\p{L}\\p{N}])`, "iu");
795
826
  return re.test(text);
796
827
  }
797
- function lintText(text, code, terms, label, path22, line) {
828
+ function lintText(text, code, terms, label, path23, line) {
798
829
  const out = [];
799
830
  const lower = text.toLowerCase();
800
831
  for (const term of terms) {
801
832
  if (wordBoundary(lower, term)) {
802
833
  out.push(
803
834
  diag(code, "error", `${label}: "${term}"`, {
804
- path: path22,
835
+ path: path23,
805
836
  line,
806
837
  suggestion: "La especificaci\xF3n es funcional y de negocio: describe comportamiento, no tecnolog\xEDa ni adjetivos vagos"
807
838
  })
@@ -810,7 +841,7 @@ function lintText(text, code, terms, label, path22, line) {
810
841
  }
811
842
  return out;
812
843
  }
813
- function lintRequirement(req, path22, opts = {}) {
844
+ function lintRequirement(req, path23, opts = {}) {
814
845
  const out = [];
815
846
  const vague = opts.language === "en" ? VAGUE_EN : VAGUE_ES;
816
847
  const tech = opts.language === "en" ? TECH_EN : TECH_ES;
@@ -821,32 +852,32 @@ function lintRequirement(req, path22, opts = {}) {
821
852
  ...req.scenarios.flatMap((s) => [...s.when.map((w) => ({ text: w, line: s.line })), ...s.then.map((t) => ({ text: t, line: s.line }))])
822
853
  ];
823
854
  for (const part of parts) {
824
- out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path22, part.line));
855
+ out.push(...lintText(part.text, "LINT-BIZ-002", vague, "Palabra vaga en la especificaci\xF3n", path23, part.line));
825
856
  if (opts.businessOnly !== false) {
826
- out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path22, part.line));
857
+ out.push(...lintText(part.text, "LINT-BIZ-001", tech, "Jerga t\xE9cnica en la especificaci\xF3n de negocio", path23, part.line));
827
858
  }
828
859
  }
829
860
  if (req.scenarios.length === 0) {
830
- out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path22, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
861
+ out.push(diag("TRACE-001", "error", `El requisito ${req.id} no tiene ning\xFAn escenario`, { path: path23, line: req.line, suggestion: "A\xF1ade al menos un escenario CUANDO/ENTONCES" }));
831
862
  }
832
863
  return out;
833
864
  }
834
- function lintDelta(delta, livingRequirements, path22, opts = {}) {
865
+ function lintDelta(delta, livingRequirements, path23, opts = {}) {
835
866
  const out = [...delta.diagnostics];
836
867
  for (const req of [...delta.added, ...delta.modified]) {
837
- out.push(...lintRequirement(req, path22, opts));
868
+ out.push(...lintRequirement(req, path23, opts));
838
869
  }
839
870
  for (const req of delta.modified) {
840
871
  const living = livingRequirements.get(req.id);
841
872
  if (!living) {
842
- out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path22, line: req.line }));
873
+ out.push(diag("TRACE-007", "error", `MODIFIED ${req.id} no existe en la spec viva; usa ADDED`, { path: path23, line: req.line }));
843
874
  continue;
844
875
  }
845
876
  for (const existing of living.scenarios) {
846
877
  if (!req.scenarios.some((s) => s.id === existing.id)) {
847
878
  out.push(
848
879
  diag("TRACE-007", "error", `MODIFIED ${req.id} pierde el escenario ${existing.id}: copia el bloque completo`, {
849
- path: path22,
880
+ path: path23,
850
881
  line: req.line,
851
882
  suggestion: "Copia el bloque completo de la spec viva y ed\xEDtalo; para quitarlo, decl\xE1ralo en REMOVED"
852
883
  })
@@ -857,15 +888,15 @@ function lintDelta(delta, livingRequirements, path22, opts = {}) {
857
888
  for (const req of delta.removed) {
858
889
  const living = livingRequirements.get(req.id);
859
890
  if (!living) {
860
- out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path22, line: req.line }));
891
+ out.push(diag("TRACE-007", "error", `REMOVED ${req.id} no existe en la spec viva`, { path: path23, line: req.line }));
861
892
  }
862
893
  }
863
894
  for (const rename2 of delta.renamed) {
864
895
  if (rename2.from.id !== rename2.to.id) {
865
- out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path22, line: rename2.line }));
896
+ out.push(diag("LINT-DLT-003", "error", `RENAMED cambia el id (${rename2.from.id} \u2192 ${rename2.to.id}); los ids son inmutables`, { path: path23, line: rename2.line }));
866
897
  }
867
898
  if (!livingRequirements.has(rename2.from.id)) {
868
- out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path22, line: rename2.line }));
899
+ out.push(diag("TRACE-007", "error", `RENAMED ${rename2.from.id} no existe en la spec viva`, { path: path23, line: rename2.line }));
869
900
  }
870
901
  }
871
902
  return out;
@@ -895,7 +926,7 @@ var MERMAID_KEYWORDS = [
895
926
  "architecture-beta"
896
927
  ];
897
928
  var MERMAID_BLOCKS = /^\s*(alt|loop|opt|par|rect|critical|break|subgraph)\b/;
898
- function lintPlan(planText, path22) {
929
+ function lintPlan(planText, path23) {
899
930
  const out = [];
900
931
  const blocks = [...planText.matchAll(/```mermaid\r?\n([\s\S]*?)```/g)];
901
932
  for (const [index, block] of blocks.entries()) {
@@ -905,7 +936,7 @@ function lintPlan(planText, path22) {
905
936
  if (!MERMAID_KEYWORDS.some((keyword) => first.startsWith(keyword))) {
906
937
  out.push(
907
938
  diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: la primera l\xEDnea debe declarar el tipo (${MERMAID_KEYWORDS.slice(0, 5).join(", ")}\u2026) y empieza por "${first.slice(0, 30)}"`, {
908
- path: path22,
939
+ path: path23,
909
940
  suggestion: "Corrige el tipo del diagrama o elimina el bloque"
910
941
  })
911
942
  );
@@ -919,7 +950,7 @@ function lintPlan(planText, path22) {
919
950
  if (open !== 0) {
920
951
  out.push(
921
952
  diag("LINT-PLN-002", "error", `Diagrama mermaid ${index + 1}: faltan ${Math.abs(open)} \`end\` (bloques alt/loop/subgraph sin cerrar)`, {
922
- path: path22,
953
+ path: path23,
923
954
  suggestion: "Cierra cada bloque alt/loop/opt/par/rect/subgraph con `end`"
924
955
  })
925
956
  );
@@ -931,7 +962,7 @@ function lintPlan(planText, path22) {
931
962
  if (message.includes(";")) {
932
963
  out.push(
933
964
  diag("LINT-PLN-003", "error", `Diagrama mermaid ${index + 1}: el mensaje "${message.trim().slice(0, 40)}\u2026" usa \`;\` y mermaid lo interpreta como fin de sentencia`, {
934
- path: path22,
965
+ path: path23,
935
966
  suggestion: "Sustituye `;` por `\xB7` o `,` en los mensajes de sequenceDiagram"
936
967
  })
937
968
  );
@@ -1240,6 +1271,29 @@ function requiresMockups(meta, cfg) {
1240
1271
  function mockupOverride(change) {
1241
1272
  return (change.meta?.overrides ?? []).some((override) => override.gate === "mockup");
1242
1273
  }
1274
+ function docsReady(change) {
1275
+ const paths = change.docsPaths ?? [];
1276
+ return paths.some((p) => p.endsWith("tecnica.md")) && paths.some((p) => p.endsWith("manual.md"));
1277
+ }
1278
+ function clarifyAdvisory(change, cfg) {
1279
+ const open = change.clarify?.open.length ?? 0;
1280
+ if (cfg.gates.clarify.mode !== "advisory" || open === 0) return [];
1281
+ return [
1282
+ diag("ATLAS-CLARIFY-001", "warning", `El cambio "${change.slug}" tiene ${open} pregunta(s) sin aclarar`, {
1283
+ ...change.clarifyPath !== void 0 ? { path: change.clarifyPath } : {},
1284
+ suggestion: `Aclara antes de planificar: /satlas.clarify ${change.slug} (o satlas clarify ${change.slug})`
1285
+ })
1286
+ ];
1287
+ }
1288
+ function docsAdvisory(change, cfg) {
1289
+ const lane = change.meta?.lane ?? cfg.lanes.default;
1290
+ if (lane !== "full" || cfg.gates.docs.mode !== "advisory" || docsReady(change)) return [];
1291
+ return [
1292
+ diag("ATLAS-DOCS-001", "warning", `El cambio "${change.slug}" (carril completo) no tiene su documentaci\xF3n t\xE9cnica y manual`, {
1293
+ suggestion: `Genera la documentaci\xF3n: satlas docs ${change.slug} (o /satlas.docs ${change.slug})`
1294
+ })
1295
+ ];
1296
+ }
1243
1297
  function deriveState(input) {
1244
1298
  const { change, cfg, approval, blockingFindings } = input;
1245
1299
  const lane = change.meta?.lane ?? cfg.lanes.default;
@@ -1302,6 +1356,16 @@ function deriveState(input) {
1302
1356
  };
1303
1357
  }
1304
1358
  if (!change.planPath && !change.tasks) {
1359
+ const openQuestions = change.clarify?.open.length ?? 0;
1360
+ if (openQuestions > 0 && cfg.gates.clarify.mode === "blocking") {
1361
+ blockedBy.push(`aclaraci\xF3n pendiente (${openQuestions})`);
1362
+ return {
1363
+ state: "approved",
1364
+ blockedBy,
1365
+ nextAction: next(`/satlas.clarify ${change.slug}`, `Aclarar ${openQuestions} pregunta(s) antes de planificar`, true),
1366
+ progress
1367
+ };
1368
+ }
1305
1369
  return { state: "approved", blockedBy, nextAction: next(`/satlas.plan ${change.slug}`, "Crear el plan t\xE9cnico y las tareas", true), progress };
1306
1370
  }
1307
1371
  if (tasksTotal > 0 && tasksDone < tasksTotal) {
@@ -1315,6 +1379,10 @@ function deriveState(input) {
1315
1379
  blockedBy.push("review pendiente");
1316
1380
  return { state: "verified", blockedBy, nextAction: next(`/satlas.review ${change.slug}`, "Revisi\xF3n de c\xF3digo", true), progress };
1317
1381
  }
1382
+ if (lane === "full" && cfg.gates.docs.mode === "blocking" && !docsReady(change)) {
1383
+ blockedBy.push("documentaci\xF3n pendiente");
1384
+ return { state: "reviewed", blockedBy, nextAction: next(`/satlas.docs ${change.slug}`, "Generar la documentaci\xF3n t\xE9cnica y manual del cambio", true), progress };
1385
+ }
1318
1386
  return { state: "ready", blockedBy, nextAction: next(`satlas archive ${change.slug}`, "Archivar el cambio y plegar los deltas"), progress };
1319
1387
  }
1320
1388
  function stateLabel(state) {
@@ -1328,6 +1396,7 @@ function stateLabel(state) {
1328
1396
  building: "construyendo",
1329
1397
  built: "construido",
1330
1398
  verified: "verificado",
1399
+ reviewed: "revisado",
1331
1400
  ready: "listo para archivar",
1332
1401
  archived: "archivado"
1333
1402
  };
@@ -1513,12 +1582,43 @@ function parseLivingFix(content, file) {
1513
1582
  date: typeof data["date"] === "string" ? data["date"] : "",
1514
1583
  result: typeof data["result"] === "string" ? data["result"] : "pass",
1515
1584
  covers: coversOf(data["covers"]),
1516
- content: fm.body.trimStart()
1585
+ content: fm.body.trimStart(),
1586
+ source: "living"
1517
1587
  };
1518
1588
  if (typeof data["domain"] === "string") fix.domain = data["domain"];
1519
1589
  if (typeof data["title"] === "string") fix.title = data["title"];
1520
1590
  return fix;
1521
1591
  }
1592
+ async function archivedFixes(root, known) {
1593
+ const archiveDir = path4.join(root, ".sdd", "changes", "archive");
1594
+ const fixes = [];
1595
+ for (const entry of await listDirs(archiveDir)) {
1596
+ const dir = path4.join(archiveDir, entry);
1597
+ const fixFile = path4.join(dir, "fix.md");
1598
+ const fixRaw = await readTextIfExists(fixFile);
1599
+ if (fixRaw === void 0) continue;
1600
+ const metaFile = path4.join(dir, "meta.yaml");
1601
+ const metaRaw = await readTextIfExists(metaFile);
1602
+ const meta = metaRaw !== void 0 ? parseChangeMeta(metaRaw, metaFile).meta : void 0;
1603
+ if (meta?.lane !== "fix") continue;
1604
+ const slug = entry.replace(/^\d{4}-\d{2}-/, "");
1605
+ if (known.has(slug)) continue;
1606
+ const evidence = parseVerifyFile(fixRaw, fixFile).evidence;
1607
+ const fix = {
1608
+ slug,
1609
+ file: fixFile,
1610
+ date: /^(\d{4}-\d{2})/.exec(entry)?.[1] ?? "",
1611
+ result: evidence.some((e) => e.result === "pass") ? "pass" : evidence.length > 0 ? "fail" : "pending",
1612
+ covers: parseFixCovers(fixRaw),
1613
+ content: parseFrontmatter(fixRaw, fixFile).body.trimStart(),
1614
+ source: "archive"
1615
+ };
1616
+ if (meta.domain !== void 0) fix.domain = meta.domain;
1617
+ if (meta.title !== void 0) fix.title = meta.title;
1618
+ fixes.push(fix);
1619
+ }
1620
+ return fixes;
1621
+ }
1522
1622
  async function loadLivingFixes(root) {
1523
1623
  const dir = path4.join(root, LIVING_FIXES_DIR);
1524
1624
  const entries = (await listDir(dir)).filter((entry) => entry.toLowerCase().endsWith(".md")).sort();
@@ -1529,6 +1629,7 @@ async function loadLivingFixes(root) {
1529
1629
  if (content === void 0) continue;
1530
1630
  fixes.push(parseLivingFix(content, file));
1531
1631
  }
1632
+ fixes.push(...await archivedFixes(root, new Set(fixes.map((fix) => fix.slug))));
1532
1633
  return fixes.sort((a, b) => b.date.localeCompare(a.date) || b.slug.localeCompare(a.slug));
1533
1634
  }
1534
1635
  async function writeLivingFix(root, input) {
@@ -1636,6 +1737,19 @@ async function loadChange(root, slug, relDir) {
1636
1737
  diagnostics.push(...change.fix.diagnostics);
1637
1738
  change.fixCovers = parseFixCovers(fixRaw);
1638
1739
  }
1740
+ const clarifyFile = path5.join(dir, "clarify.md");
1741
+ const clarifyRaw = await readTextIfExists(clarifyFile);
1742
+ if (clarifyRaw !== void 0) {
1743
+ change.clarify = parseClarify(clarifyRaw, clarifyFile);
1744
+ change.clarifyPath = clarifyFile;
1745
+ diagnostics.push(...change.clarify.diagnostics);
1746
+ }
1747
+ const docsPaths = [];
1748
+ for (const name of ["tecnica.md", "manual.md"]) {
1749
+ const docFile = path5.join(dir, "docs", name);
1750
+ if (await exists(docFile)) docsPaths.push(docFile);
1751
+ }
1752
+ if (docsPaths.length > 0) change.docsPaths = docsPaths;
1639
1753
  const mockupManifest = path5.join(dir, "mockups", "manifest.yaml");
1640
1754
  if (await exists(mockupManifest)) change.mockupManifestPath = mockupManifest;
1641
1755
  return change;
@@ -1852,6 +1966,44 @@ satlas verify <slug> --file fix --scenario REQ-DOMINIO-001-S1 --command "<comand
1852
1966
  o, si es manual:
1853
1967
  satlas verify <slug> --file fix --scenario REQ-DOMINIO-001-S1 --method manual --result pass --by "<nombre>" --notes "<c\xF3mo se comprob\xF3>"
1854
1968
  -->
1969
+ `,
1970
+ docTecnica: `# Documentaci\xF3n t\xE9cnica \u2014 {{TITLE}}
1971
+
1972
+ ## Resumen del cambio
1973
+
1974
+ - **Cambio**: \`{{SLUG}}\` \xB7 dominio \`{{DOMAIN}}\` \xB7 carril \`{{LANE}}\`
1975
+ - **Actualizado**: {{DATE}}
1976
+ - **Tareas**: {{TASKS}}
1977
+
1978
+ ## Requisitos y escenarios
1979
+
1980
+ {{REQUIREMENTS}}
1981
+
1982
+ ## Evidencia registrada
1983
+
1984
+ {{EVIDENCE}}
1985
+
1986
+ ## Pendiente de evidencia
1987
+
1988
+ {{PENDING}}
1989
+ `,
1990
+ docManual: `# Manual \u2014 {{TITLE}}
1991
+
1992
+ ## Qu\xE9 hace este cambio
1993
+
1994
+ {{TITLE}} \u2014 dominio \`{{DOMAIN}}\` (cambio \`{{SLUG}}\`, carril \`{{LANE}}\`).
1995
+
1996
+ ## C\xF3mo se usa
1997
+
1998
+ {{SCENARIOS}}
1999
+
2000
+ ## C\xF3mo se comprob\xF3
2001
+
2002
+ {{EVIDENCE}}
2003
+
2004
+ ## Pendiente de comprobar
2005
+
2006
+ {{PENDING}}
1855
2007
  `
1856
2008
  };
1857
2009
  var EN = {
@@ -1977,6 +2129,44 @@ Cubre: REQ-DOMAIN-001
1977
2129
  <!-- Register real evidence with:
1978
2130
  satlas verify <slug> --file fix --scenario REQ-DOMAIN-001-S1 --command "<command>" --by "<name>"
1979
2131
  -->
2132
+ `,
2133
+ docTecnica: `# Technical documentation \u2014 {{TITLE}}
2134
+
2135
+ ## Change summary
2136
+
2137
+ - **Change**: \`{{SLUG}}\` \xB7 domain \`{{DOMAIN}}\` \xB7 lane \`{{LANE}}\`
2138
+ - **Updated**: {{DATE}}
2139
+ - **Tasks**: {{TASKS}}
2140
+
2141
+ ## Requirements and scenarios
2142
+
2143
+ {{REQUIREMENTS}}
2144
+
2145
+ ## Recorded evidence
2146
+
2147
+ {{EVIDENCE}}
2148
+
2149
+ ## Pending evidence
2150
+
2151
+ {{PENDING}}
2152
+ `,
2153
+ docManual: `# Manual \u2014 {{TITLE}}
2154
+
2155
+ ## What this change does
2156
+
2157
+ {{TITLE}} \u2014 domain \`{{DOMAIN}}\` (change \`{{SLUG}}\`, lane \`{{LANE}}\`).
2158
+
2159
+ ## How to use it
2160
+
2161
+ {{SCENARIOS}}
2162
+
2163
+ ## How it was verified
2164
+
2165
+ {{EVIDENCE}}
2166
+
2167
+ ## Pending verification
2168
+
2169
+ {{PENDING}}
1980
2170
  `
1981
2171
  };
1982
2172
  function templatesFor(language) {
@@ -4568,8 +4758,89 @@ async function upgradeAdvisory(root) {
4568
4758
  return { plan, diagnostics };
4569
4759
  }
4570
4760
 
4571
- // src/sarif.ts
4761
+ // src/docs.ts
4572
4762
  import path19 from "path";
4763
+ var DOCS_MARKER_START = "<!-- specatlas:generado:inicio -->";
4764
+ var DOCS_MARKER_END = "<!-- specatlas:generado:fin -->";
4765
+ function docData(change, language, now) {
4766
+ const es = language !== "en";
4767
+ const requirements = [...change.delta?.added ?? [], ...change.delta?.modified ?? []];
4768
+ const evidence = change.verify?.evidence ?? [];
4769
+ const passed = new Set(evidence.filter((entry) => entry.result === "pass").map((entry) => entry.scenario));
4770
+ const requirementsText = requirements.length === 0 ? es ? "_Sin requisitos en el delta._" : "_No requirements in the delta._" : requirements.map((requirement) => {
4771
+ const lines = [`### ${requirement.id} \u2014 ${requirement.title}`, ""];
4772
+ for (const scenario of requirement.scenarios) lines.push(`- \`${scenario.id}\` \u2014 ${scenario.title}`);
4773
+ return lines.join("\n");
4774
+ }).join("\n\n");
4775
+ const scenarioList = requirements.flatMap((requirement) => requirement.scenarios);
4776
+ const scenariosText = scenarioList.length === 0 ? es ? "_Sin escenarios._" : "_No scenarios._" : scenarioList.map((scenario) => `- \`${scenario.id}\` \u2014 ${scenario.title}`).join("\n");
4777
+ const evidenceText = evidence.length === 0 ? es ? "_Sin evidencia registrada._" : "_No evidence recorded._" : evidence.map((entry) => `- \`${entry.scenario}\` \u2014 ${entry.method} \xB7 ${entry.result}${entry.date ? ` \xB7 ${entry.date}` : ""}`).join("\n");
4778
+ const pending = scenarioList.filter((scenario) => !passed.has(scenario.id));
4779
+ const pendingText = pending.length === 0 ? es ? "_Nada pendiente: todos los escenarios tienen evidencia en pass._" : "_Nothing pending: every scenario has passing evidence._" : pending.map((scenario) => `- \`${scenario.id}\` \u2014 ${scenario.title}`).join("\n");
4780
+ return {
4781
+ TITLE: change.meta?.title ?? change.slug,
4782
+ SLUG: change.slug,
4783
+ DOMAIN: change.meta?.domain ?? "\u2014",
4784
+ LANE: change.meta?.lane ?? "standard",
4785
+ DATE: localDate(now),
4786
+ TASKS: change.tasks ? `${change.tasks.counts.done}/${change.tasks.counts.total}` : es ? "sin tareas" : "no tasks",
4787
+ REQUIREMENTS: requirementsText,
4788
+ SCENARIOS: scenariosText,
4789
+ EVIDENCE: evidenceText,
4790
+ PENDING: pendingText
4791
+ };
4792
+ }
4793
+ function mergeManaged(existing, block, language) {
4794
+ const managed = `${DOCS_MARKER_START}
4795
+ ${block.trimEnd()}
4796
+ ${DOCS_MARKER_END}`;
4797
+ if (existing === void 0) {
4798
+ const notes = language === "en" ? "## Notes\n\n(Write here whatever you want to keep across regenerations.)" : "## Notas\n\n(Escribe aqu\xED lo que quieras conservar entre regeneraciones.)";
4799
+ return `${managed}
4800
+
4801
+ ${notes}
4802
+ `;
4803
+ }
4804
+ const start = existing.indexOf(DOCS_MARKER_START);
4805
+ const end = existing.indexOf(DOCS_MARKER_END);
4806
+ if (start >= 0 && end > start) {
4807
+ const before = existing.slice(0, start);
4808
+ const after = existing.slice(end + DOCS_MARKER_END.length);
4809
+ return `${before}${managed}${after}`;
4810
+ }
4811
+ return `${managed}
4812
+
4813
+ ${existing}`;
4814
+ }
4815
+ async function generateDocs(opts) {
4816
+ const root = path19.resolve(opts.root);
4817
+ const { config } = await loadWorkspace(root);
4818
+ const change = await loadChange(root, opts.slug);
4819
+ if (!change.meta) {
4820
+ return {
4821
+ slug: opts.slug,
4822
+ files: [],
4823
+ diagnostics: [diag("ATLAS-DOCS-002", "error", `No existe el cambio "${opts.slug}"`, { suggestion: "Comprueba el nombre del cambio" })]
4824
+ };
4825
+ }
4826
+ const language = config.project.language;
4827
+ const tipo = opts.tipo ?? "all";
4828
+ const tipos = tipo === "all" ? ["tecnica", "manual"] : [tipo];
4829
+ const templates = templatesFor(language);
4830
+ const data = docData(change, language, opts.now ?? /* @__PURE__ */ new Date());
4831
+ const files = [];
4832
+ for (const current of tipos) {
4833
+ const file = path19.join(change.dir, "docs", `${current}.md`);
4834
+ const block = renderTemplate(current === "tecnica" ? templates.docTecnica : templates.docManual, data);
4835
+ const existing = await readTextIfExists(file);
4836
+ await writeText(file, mergeManaged(existing, block, language));
4837
+ files.push({ tipo: current, path: file, created: existing === void 0 });
4838
+ }
4839
+ return { slug: opts.slug, files, diagnostics: [] };
4840
+ }
4841
+
4842
+ // src/sarif.ts
4843
+ import path20 from "path";
4573
4844
  var SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
4574
4845
  var SARIF_VERSION = "2.1.0";
4575
4846
  var TOOL_NAME = "SpecAtlas";
@@ -4607,7 +4878,7 @@ function resultOf(diagnostic, root) {
4607
4878
  message: { text: diagnostic.message }
4608
4879
  };
4609
4880
  if (diagnostic.path) {
4610
- const rel = path19.relative(root, diagnostic.path);
4881
+ const rel = path20.relative(root, diagnostic.path);
4611
4882
  if (rel !== "") {
4612
4883
  const physicalLocation = {
4613
4884
  artifactLocation: { uri: toPosix(rel) },
@@ -4657,7 +4928,7 @@ function toSarifText(opts) {
4657
4928
  }
4658
4929
 
4659
4930
  // src/doctor.ts
4660
- import path20 from "path";
4931
+ import path21 from "path";
4661
4932
  async function runDoctor(root) {
4662
4933
  const findings = [];
4663
4934
  const { workspace, config } = await loadWorkspace(root);
@@ -4665,7 +4936,7 @@ async function runDoctor(root) {
4665
4936
  const approvals = await loadApprovals(workspace.sddDir);
4666
4937
  findings.push(...approvals.diagnostics);
4667
4938
  for (const change of workspace.changes) {
4668
- const deltaPath = path20.join(change.dir, "spec.md");
4939
+ const deltaPath = path21.join(change.dir, "spec.md");
4669
4940
  const deltaContent = await readTextIfExists(deltaPath);
4670
4941
  const approval = verifyApproval(change, approvals.byArtifact, config, deltaContent ?? void 0);
4671
4942
  if ((change.planPath || change.tasks) && (approval.status === "missing" || approval.status === "stale")) {
@@ -4687,7 +4958,7 @@ async function runDoctor(root) {
4687
4958
  }
4688
4959
  for (const override of change.meta?.overrides ?? []) {
4689
4960
  if (!override.reason.trim() || !override.by.trim()) {
4690
- findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path20.join(change.dir, "meta.yaml") }));
4961
+ findings.push(diag("ATLAS-LIFECYCLE-004", "error", `Override del gate "${override.gate}" sin motivo o autor`, { path: path21.join(change.dir, "meta.yaml") }));
4691
4962
  }
4692
4963
  }
4693
4964
  }
@@ -4710,7 +4981,7 @@ async function specHashOf(filePath) {
4710
4981
  }
4711
4982
 
4712
4983
  // src/gate.ts
4713
- import path21 from "path";
4984
+ import path22 from "path";
4714
4985
  var UI_DOMAINS2 = /* @__PURE__ */ new Set(["frontend", "mobile", "fullstack"]);
4715
4986
  function count(name, diagnostics) {
4716
4987
  return {
@@ -4727,7 +4998,7 @@ function livingRequirementsMap(specs) {
4727
4998
  return map;
4728
4999
  }
4729
5000
  async function runCiGate(opts) {
4730
- const root = path21.resolve(opts.root);
5001
+ const root = path22.resolve(opts.root);
4731
5002
  const { workspace, config } = await loadWorkspace(root);
4732
5003
  const diagnostics = [];
4733
5004
  const checks = [];
@@ -4738,7 +5009,7 @@ async function runCiGate(opts) {
4738
5009
  let changesErrors = 0;
4739
5010
  let changesWarnings = 0;
4740
5011
  for (const change of workspace.changes) {
4741
- const lintFindings = change.delta ? lintDelta(change.delta, living, path21.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
5012
+ const lintFindings = change.delta ? lintDelta(change.delta, living, path22.join(change.dir, "spec.md"), { language: config.spec.language }) : [];
4742
5013
  const trace = checkTrace({
4743
5014
  specs: workspace.specs,
4744
5015
  change,
@@ -4788,6 +5059,8 @@ export {
4788
5059
  BUILTIN_PACKS,
4789
5060
  CONFIG_FILE,
4790
5061
  CORE_VERSION,
5062
+ DOCS_MARKER_END,
5063
+ DOCS_MARKER_START,
4791
5064
  LIVING_FIXES_DIR,
4792
5065
  REQ_HEAD_RE,
4793
5066
  REQ_ID_RE,
@@ -4822,6 +5095,7 @@ export {
4822
5095
  changeMetaYaml,
4823
5096
  checkMockups,
4824
5097
  checkTrace,
5098
+ clarifyAdvisory,
4825
5099
  collectMetrics,
4826
5100
  collectVersionedArtifacts,
4827
5101
  commentIssue,
@@ -4840,6 +5114,8 @@ export {
4840
5114
  detectRepo,
4841
5115
  detectionToYaml,
4842
5116
  diag,
5117
+ docsAdvisory,
5118
+ docsReady,
4843
5119
  editIssue,
4844
5120
  emitFrontmatter,
4845
5121
  ensureDir,
@@ -4852,6 +5128,7 @@ export {
4852
5128
  findWorkspaceRoot,
4853
5129
  firstToken,
4854
5130
  foldDelta,
5131
+ generateDocs,
4855
5132
  generatePresentation,
4856
5133
  generateRunId,
4857
5134
  getNumber,
@@ -4907,6 +5184,7 @@ export {
4907
5184
  packFindings,
4908
5185
  parseApprovals,
4909
5186
  parseChangeMeta,
5187
+ parseClarify,
4910
5188
  parseConfig,
4911
5189
  parseDelta,
4912
5190
  parseFixCovers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specatlas/core",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Kernel determinista de SpecAtlas: parsing, validación, trazabilidad, olas y ciclo de vida",
5
5
  "license": "MIT",
6
6
  "type": "module",