specatlas 0.1.24 → 0.1.25

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.
Files changed (2) hide show
  1. package/dist/bin.js +670 -53
  2. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -1585,7 +1585,7 @@ var require_core = __commonJS({
1585
1585
  });
1586
1586
 
1587
1587
  // src/cli.ts
1588
- import path39 from "path";
1588
+ import path41 from "path";
1589
1589
 
1590
1590
  // ../core/dist/index.js
1591
1591
  import path from "path";
@@ -9593,6 +9593,146 @@ function findScenarioHeading(lines, blockLineIndex) {
9593
9593
  }
9594
9594
  return void 0;
9595
9595
  }
9596
+ var HEADER_KEYS = /* @__PURE__ */ new Set(["t\xE9rmino", "termino", "term", "definici\xF3n", "definicion", "definition", "sin\xF3nimos", "sinonimos", "synonyms"]);
9597
+ var SEPARATOR_RE = /^:?-{2,}:?$/;
9598
+ function parseGlossary(md, filePath) {
9599
+ const diagnostics = [];
9600
+ const terms = [];
9601
+ const lines = md.replace(/\r\n?/g, "\n").split("\n");
9602
+ for (let i = 0; i < lines.length; i += 1) {
9603
+ const raw = lines[i] ?? "";
9604
+ if (!raw.trim().startsWith("|")) continue;
9605
+ const inner = raw.trim().replace(/^\|/, "").replace(/\|$/, "");
9606
+ const cells = inner.split("|").map((c) => c.trim());
9607
+ if (cells.length < 2) {
9608
+ diagnostics.push(diag("LINT-GLO-001", "warning", "Fila del glosario sin columnas suficientes", { path: filePath, line: i + 1 }));
9609
+ continue;
9610
+ }
9611
+ const first = cells[0] ?? "";
9612
+ if (HEADER_KEYS.has(first.toLowerCase()) || SEPARATOR_RE.test(first)) continue;
9613
+ const term = first;
9614
+ const definition = cells[1] ?? "";
9615
+ if (term === "" || definition === "") {
9616
+ diagnostics.push(diag("LINT-GLO-002", "warning", "T\xE9rmino o definici\xF3n vac\xEDos en el glosario", { path: filePath, line: i + 1 }));
9617
+ continue;
9618
+ }
9619
+ const synonyms = (cells[2] ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9620
+ terms.push({ term, definition, synonyms });
9621
+ }
9622
+ return { terms, diagnostics };
9623
+ }
9624
+ function normalizePath(p) {
9625
+ return toPosix(p.trim()).replace(/^\.\//, "").replace(/\/+$/, "").toLowerCase();
9626
+ }
9627
+ function fileMatches(query, candidate) {
9628
+ if (query === candidate) return true;
9629
+ return candidate.endsWith(`/${query}`);
9630
+ }
9631
+ function scenarioToReq(workspace) {
9632
+ const map = /* @__PURE__ */ new Map();
9633
+ for (const spec of workspace.specs) {
9634
+ for (const req of spec.spec.requirements) {
9635
+ for (const sc of req.scenarios) map.set(sc.id, req.id);
9636
+ }
9637
+ }
9638
+ for (const change of workspace.changes) {
9639
+ for (const req of [...change.delta?.added ?? [], ...change.delta?.modified ?? []]) {
9640
+ for (const sc of req.scenarios) map.set(sc.id, req.id);
9641
+ }
9642
+ }
9643
+ return map;
9644
+ }
9645
+ function emptyReport(target, kind) {
9646
+ return { target, kind, exists: false, scenarios: [], tasks: [], requirements: [], changes: [], files: [], evidence: [] };
9647
+ }
9648
+ function collect(workspace, report, covers, reqOf) {
9649
+ const requirements = /* @__PURE__ */ new Set();
9650
+ for (const change of workspace.changes) {
9651
+ let changeMatches = false;
9652
+ for (const block of change.tasks?.blocks ?? []) {
9653
+ for (const task of block.tasks) {
9654
+ const hit = task.covers.some((c) => covers.has(c.toUpperCase()));
9655
+ if (!hit) continue;
9656
+ changeMatches = true;
9657
+ report.tasks.push({ id: task.id, change: change.slug, text: task.text, files: task.files, covers: task.covers });
9658
+ for (const c of task.covers) {
9659
+ const upper = c.toUpperCase();
9660
+ if (covers.has(upper)) {
9661
+ const req = reqOf.get(upper) ?? upper;
9662
+ requirements.add(req);
9663
+ }
9664
+ }
9665
+ for (const f of task.files) report.files.push(f);
9666
+ }
9667
+ }
9668
+ if (changeMatches) report.changes.push(change.slug);
9669
+ for (const ev of change.verify?.evidence ?? []) {
9670
+ if (covers.has(ev.scenario.toUpperCase())) {
9671
+ report.evidence.push({ scenario: ev.scenario, result: ev.result, change: change.slug });
9672
+ }
9673
+ }
9674
+ }
9675
+ report.requirements = [...requirements].sort();
9676
+ report.changes = [...new Set(report.changes)].sort();
9677
+ report.files = [...new Set(report.files)].sort();
9678
+ report.evidence.sort((a, b) => a.scenario.localeCompare(b.scenario));
9679
+ }
9680
+ function impactOfRequirement(workspace, reqId) {
9681
+ const id = reqId.toUpperCase();
9682
+ const scenarios = /* @__PURE__ */ new Set();
9683
+ let exists2 = false;
9684
+ for (const spec of workspace.specs) {
9685
+ for (const req of spec.spec.requirements) {
9686
+ if (req.id !== id) continue;
9687
+ exists2 = true;
9688
+ for (const sc of req.scenarios) scenarios.add(sc.id);
9689
+ }
9690
+ }
9691
+ for (const change of workspace.changes) {
9692
+ for (const req of [...change.delta?.added ?? [], ...change.delta?.modified ?? []]) {
9693
+ if (req.id !== id) continue;
9694
+ exists2 = true;
9695
+ for (const sc of req.scenarios) scenarios.add(sc.id);
9696
+ }
9697
+ if ((change.delta?.removed ?? []).some((r) => r.id === id)) exists2 = true;
9698
+ if ((change.delta?.renamed ?? []).some((r) => r.from.id === id || r.to.id === id)) exists2 = true;
9699
+ }
9700
+ const report = emptyReport(id, "requirement");
9701
+ if (!exists2) return report;
9702
+ report.exists = true;
9703
+ report.scenarios = [...scenarios].sort();
9704
+ const reqOf = scenarioToReq(workspace);
9705
+ collect(workspace, report, /* @__PURE__ */ new Set([id, ...scenarios]), reqOf);
9706
+ if (!report.requirements.includes(id)) report.requirements.unshift(id);
9707
+ return report;
9708
+ }
9709
+ function impactOfFile(workspace, file) {
9710
+ const query = normalizePath(file);
9711
+ const report = emptyReport(file, "file");
9712
+ const covers = /* @__PURE__ */ new Set();
9713
+ const matched = /* @__PURE__ */ new Set();
9714
+ const changes = /* @__PURE__ */ new Set();
9715
+ for (const change of workspace.changes) {
9716
+ for (const block of change.tasks?.blocks ?? []) {
9717
+ for (const task of block.tasks) {
9718
+ if (!task.files.some((f) => fileMatches(query, normalizePath(f)))) continue;
9719
+ changes.add(change.slug);
9720
+ for (const f of task.files) matched.add(f);
9721
+ for (const c of task.covers) covers.add(c.toUpperCase());
9722
+ report.tasks.push({ id: task.id, change: change.slug, text: task.text, files: task.files, covers: task.covers });
9723
+ }
9724
+ }
9725
+ }
9726
+ if (matched.size === 0) return report;
9727
+ report.exists = true;
9728
+ report.files = [...matched].sort();
9729
+ report.changes = [...changes].sort();
9730
+ const reqOf = scenarioToReq(workspace);
9731
+ const requirements = /* @__PURE__ */ new Set();
9732
+ for (const c of covers) requirements.add(reqOf.get(c) ?? c);
9733
+ report.requirements = [...requirements].sort();
9734
+ return report;
9735
+ }
9596
9736
  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"];
9597
9737
  var VAGUE_EN = ["fast", "quick", "easy", "several", "optimal", "robust", "adequate", "efficient", "friendly", "modern", "better", "best", "simple", "intuitive", "appropriate", "sufficient", "reasonable", "nice", "clean"];
9598
9738
  var TECH_ES = ["api", "endpoint", "tabla", "base de datos", "sql", "json", "xml", "frontend", "backend", "react", "vue", "angular", "node", "npm", "docker", "kubernetes", "servidor", "columna", "query", "script", "framework", "librer\xEDa", "microservicio", "cache", "cach\xE9", "deploy", "despliegue", "commit", "merge", "rama", "branch", "endpoint", "crud", "rest", "http", "sdk", "webhook", "orm", "job", "cron", "websocket", "graphql", "api rest", "tablas", "\xEDndice", "primary key", "foreign key"];
@@ -10055,6 +10195,17 @@ function deriveState(input) {
10055
10195
  }
10056
10196
  if (blockingFindings > 0) {
10057
10197
  blockedBy.push(`${blockingFindings} hallazgo(s) bloqueante(s)`);
10198
+ if (tasksTotal > 0 && tasksDone < tasksTotal) {
10199
+ return {
10200
+ state: "building",
10201
+ blockedBy,
10202
+ nextAction: next(`/satlas.build ${change.slug}`, `Construir en olas (${tasksDone}/${tasksTotal} tareas) \xB7 ${blockingFindings} hallazgo(s) pendientes`, true),
10203
+ progress
10204
+ };
10205
+ }
10206
+ if (tasksTotal > 0) {
10207
+ return { state: "built", blockedBy, nextAction: next(`satlas verify ${change.slug}`, "Registrar evidencia por escenario"), progress };
10208
+ }
10058
10209
  return { state: "spec_draft", blockedBy, nextAction: next(`satlas validate --change ${change.slug}`, "Corregir los hallazgos de la especificaci\xF3n"), progress };
10059
10210
  }
10060
10211
  if ((approval.status === "missing" || approval.status === "stale") && requiresMockups(change.meta, cfg) && input.mockupsReady !== true && !mockupOverride(change)) {
@@ -13029,6 +13180,7 @@ var CATALOG = [
13029
13180
  { name: "mockup", description: "Planifica, valida o captura los mockups del cambio", flags: ["plan", "check", "capture", "require", "json"], usage: "satlas mockup <slug> [--plan|--check|--capture|--require]" },
13030
13181
  { name: "present", description: "Genera el paquete de propuesta para el stakeholder (HTML autocontenido)", flags: ["json"], usage: "satlas present <slug>" },
13031
13182
  { name: "ci", description: "Gate de pipeline: specs + cambios + doctor + adaptadores (sin agente)", flags: ["strict", "json"], usage: "satlas ci [--strict]" },
13183
+ { name: "mcp", description: "V\xEDa de consulta de solo lectura para asistentes (protocolo MCP sobre entrada/salida est\xE1ndar)", flags: [], usage: "satlas mcp" },
13032
13184
  { name: "metrics", description: "M\xE9tricas locales del workspace (progreso, WIP, throughput, evidencia)", flags: ["json"], usage: "satlas metrics [--json]" },
13033
13185
  { name: "run", description: "Persiste runs y eventos de ejecuci\xF3n (auditor\xEDa y reanudaci\xF3n)", flags: ["phase", "inputs", "data", "slug", "status", "json"], usage: "satlas run start|event|status|show|list" },
13034
13186
  { name: "hash", description: "Calcula el sha256 de un archivo o texto (para evidencia)", flags: ["text", "json"], usage: 'satlas hash <archivo> | satlas hash --text "salida"' },
@@ -14206,8 +14358,504 @@ async function runMockup(ctx) {
14206
14358
  return { exitCode: 0, diagnostics: [], data: { plan, planFile: path31.relative(ctx.cwd, planFile), manifestFile: path31.relative(ctx.cwd, manifestFile) }, text: lines };
14207
14359
  }
14208
14360
 
14209
- // src/commands/packs.ts
14361
+ // src/mcp/server.ts
14362
+ import { promises as fs2 } from "fs";
14363
+ import path34 from "path";
14364
+ import { createInterface } from "readline/promises";
14365
+
14366
+ // src/mcp/protocol.ts
14367
+ var JSONRPC_VERSION = "2.0";
14368
+ var PARSE_ERROR = -32700;
14369
+ var INVALID_REQUEST = -32600;
14370
+ var METHOD_NOT_FOUND = -32601;
14371
+ var INTERNAL_ERROR = -32603;
14372
+ function isObject(value) {
14373
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14374
+ }
14375
+ function makeResult(id, result) {
14376
+ return { jsonrpc: JSONRPC_VERSION, id, result };
14377
+ }
14378
+ function makeError(id, code, message, data) {
14379
+ const error = { code, message };
14380
+ if (data !== void 0) error.data = data;
14381
+ return { jsonrpc: JSONRPC_VERSION, id, error };
14382
+ }
14383
+ function parseMessage(line) {
14384
+ let value;
14385
+ try {
14386
+ value = JSON.parse(line);
14387
+ } catch {
14388
+ return { error: { code: PARSE_ERROR, message: "El mensaje no es JSON v\xE1lido" } };
14389
+ }
14390
+ if (!isObject(value) || value["jsonrpc"] !== JSONRPC_VERSION) {
14391
+ return { error: { code: INVALID_REQUEST, message: "Mensaje no es una petici\xF3n JSON-RPC 2.0 v\xE1lida" } };
14392
+ }
14393
+ if (typeof value["method"] !== "string") {
14394
+ return { error: { code: INVALID_REQUEST, message: "El mensaje no declara un m\xE9todo" } };
14395
+ }
14396
+ const idValue = value["id"] ?? null;
14397
+ const id = typeof idValue === "number" || typeof idValue === "string" || idValue === null ? idValue : null;
14398
+ const request = { jsonrpc: JSONRPC_VERSION, id, method: value["method"] };
14399
+ if (value["params"] !== void 0) request.params = value["params"];
14400
+ return { request };
14401
+ }
14402
+ function jsonResult(data) {
14403
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
14404
+ }
14405
+ function errorResult(code, message, extra) {
14406
+ return {
14407
+ isError: true,
14408
+ content: [{ type: "text", text: JSON.stringify({ error: code, message, ...extra }, null, 2) }]
14409
+ };
14410
+ }
14411
+ function noWorkspaceResult() {
14412
+ return errorResult(
14413
+ "ATLAS-MCP-WS-001",
14414
+ "El proyecto no est\xE1 inicializado: no se encontr\xF3 el estado del proyecto en esta carpeta.",
14415
+ { action: "satlas init" }
14416
+ );
14417
+ }
14418
+ function stringArg(args, key) {
14419
+ if (!isObject(args)) return void 0;
14420
+ const value = args[key];
14421
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
14422
+ }
14423
+
14424
+ // src/mcp/tools/glossary.ts
14210
14425
  import path32 from "path";
14426
+ async function runAtlasGlossary(_args, host) {
14427
+ const resolved = await host.getWorkspace();
14428
+ if (!resolved) return noWorkspaceResult();
14429
+ const { root, config } = resolved;
14430
+ const glossaryPath = path32.resolve(root, config.spec.glossary);
14431
+ const raw = await readTextIfExists(glossaryPath);
14432
+ if (raw === void 0) {
14433
+ return jsonResult({
14434
+ terms: [],
14435
+ message: "El glosario no tiene t\xE9rminos definidos.",
14436
+ action: "Define los t\xE9rminos del negocio en el glosario del proyecto."
14437
+ });
14438
+ }
14439
+ const parsed = parseGlossary(raw, glossaryPath);
14440
+ if (parsed.terms.length === 0) {
14441
+ return jsonResult({
14442
+ terms: [],
14443
+ message: "El glosario no tiene t\xE9rminos definidos.",
14444
+ action: "Define los t\xE9rminos del negocio en el glosario del proyecto."
14445
+ });
14446
+ }
14447
+ return jsonResult({ terms: parsed.terms });
14448
+ }
14449
+
14450
+ // src/mcp/tools/impact.ts
14451
+ var REQ_TARGET_RE = /^REQ-[A-Z0-9]+(?:-[A-Z0-9]+)*-\d{3}(?:-S\d+)?$/i;
14452
+ async function runAtlasImpact(args, host) {
14453
+ const target = stringArg(args, "target");
14454
+ if (!target) {
14455
+ return errorResult("ATLAS-MCP-IMPACT-001", "Falta el requisito o el archivo a consultar", {
14456
+ action: "atlas_impact con el argumento target (REQ-\u2026 o ruta de archivo)"
14457
+ });
14458
+ }
14459
+ const resolved = await host.getWorkspace();
14460
+ if (!resolved) return noWorkspaceResult();
14461
+ const { workspace } = resolved;
14462
+ let report;
14463
+ if (REQ_TARGET_RE.test(target)) {
14464
+ const reqId = target.toUpperCase().replace(/-S\d+$/, "");
14465
+ report = impactOfRequirement(workspace, reqId);
14466
+ } else {
14467
+ report = impactOfFile(workspace, target);
14468
+ }
14469
+ if (!report.exists) {
14470
+ return jsonResult({ target, exists: false, message: `Sin relaciones registradas para "${target}".` });
14471
+ }
14472
+ return jsonResult(report);
14473
+ }
14474
+
14475
+ // src/evaluate.ts
14476
+ import path33 from "path";
14477
+ function livingRequirementsMap2(specs) {
14478
+ const map = /* @__PURE__ */ new Map();
14479
+ for (const spec of specs) {
14480
+ for (const req of spec.spec.requirements) map.set(req.id, req);
14481
+ }
14482
+ return map;
14483
+ }
14484
+ async function evaluateChange(workspace, config, change, approvals) {
14485
+ const deltaPath = path33.join(change.dir, "spec.md");
14486
+ const deltaContent = await readTextIfExists(deltaPath);
14487
+ const approval = verifyApproval(change, approvals, config, deltaContent ?? void 0);
14488
+ const living = livingRequirementsMap2(workspace.specs);
14489
+ const lintFindings = change.delta ? lintDelta(change.delta, living, deltaPath, { language: config.spec.language }) : [];
14490
+ const trace = checkTrace({
14491
+ specs: workspace.specs,
14492
+ change,
14493
+ requireEvidence: config.gates.verify.mode !== "off" && config.gates.verify.require_evidence
14494
+ });
14495
+ const blocking = lintFindings.filter((d) => d.severity === "error").length + trace.findings.filter((d) => d.severity === "error").length;
14496
+ const mockupsAreReady = requiresMockups(change.meta, config) ? await mockupsReady(workspace.root, change.slug, change) : void 0;
14497
+ const state = deriveState({
14498
+ change,
14499
+ cfg: config,
14500
+ approval,
14501
+ blockingFindings: change.delta ? blocking : 0,
14502
+ ...mockupsAreReady !== void 0 ? { mockupsReady: mockupsAreReady } : {}
14503
+ });
14504
+ return { change, approval, lintFindings, trace, blocking, state };
14505
+ }
14506
+
14507
+ // src/mcp/tools/next.ts
14508
+ var HUMAN_COMMANDS = [/^satlas approve/, /^satlas archive/, /^satlas resume/, /^satlas present/];
14509
+ function requiresPerson(nextCommand, state) {
14510
+ if (state === "awaiting_approval" || state === "ready") return true;
14511
+ return HUMAN_COMMANDS.some((re) => re.test(nextCommand));
14512
+ }
14513
+ async function runAtlasNext(args, host) {
14514
+ const slug = stringArg(args, "slug");
14515
+ if (!slug) {
14516
+ return errorResult("ATLAS-MCP-NEXT-001", "Falta el nombre del cambio", { action: "atlas_next con el argumento slug" });
14517
+ }
14518
+ const resolved = await host.getWorkspace();
14519
+ if (!resolved) return noWorkspaceResult();
14520
+ const { workspace, config, approvals } = resolved;
14521
+ const change = workspace.changes.find((c) => c.slug === slug);
14522
+ if (!change) {
14523
+ return errorResult("ATLAS-MCP-NEXT-002", `El cambio "${slug}" no existe`, {
14524
+ available: workspace.changes.map((c) => c.slug)
14525
+ });
14526
+ }
14527
+ const evaluation = await evaluateChange(workspace, config, change, approvals);
14528
+ const { state, nextAction, blockedBy } = evaluation.state;
14529
+ const humanRequired = requiresPerson(nextAction.command, state);
14530
+ return jsonResult({
14531
+ slug,
14532
+ state,
14533
+ label: stateLabel(state),
14534
+ next: { command: nextAction.command, description: nextAction.description, requiresAgent: nextAction.requiresAgent },
14535
+ requiresPerson: humanRequired,
14536
+ blockedBy
14537
+ });
14538
+ }
14539
+
14540
+ // src/mcp/tools/status.ts
14541
+ async function runAtlasStatus(args, host) {
14542
+ const resolved = await host.getWorkspace();
14543
+ if (!resolved) return noWorkspaceResult();
14544
+ const { workspace, config, approvals } = resolved;
14545
+ const slug = stringArg(args, "slug");
14546
+ if (slug && !workspace.changes.some((c) => c.slug === slug)) {
14547
+ return errorResult("ATLAS-MCP-STATUS-001", `El cambio "${slug}" no existe`, {
14548
+ available: workspace.changes.map((c) => c.slug)
14549
+ });
14550
+ }
14551
+ const changes = [];
14552
+ for (const change of workspace.changes) {
14553
+ if (slug && change.slug !== slug) continue;
14554
+ const evaluation = await evaluateChange(workspace, config, change, approvals);
14555
+ const { state, progress, nextAction, blockedBy } = evaluation.state;
14556
+ changes.push({
14557
+ slug: change.slug,
14558
+ lane: change.meta?.lane ?? config.lanes.default,
14559
+ domain: change.meta?.domain,
14560
+ state,
14561
+ label: stateLabel(state),
14562
+ progress,
14563
+ blocking: evaluation.blocking,
14564
+ blockedBy,
14565
+ next: { command: nextAction.command, description: nextAction.description, requiresAgent: nextAction.requiresAgent }
14566
+ });
14567
+ }
14568
+ if (changes.length === 0) {
14569
+ return jsonResult({
14570
+ changes: [],
14571
+ specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length })),
14572
+ message: "Sin cambios activos.",
14573
+ action: "satlas new <slug>"
14574
+ });
14575
+ }
14576
+ return jsonResult({
14577
+ changes,
14578
+ specs: workspace.specs.map((s) => ({ domain: s.domain, requirements: s.spec.requirements.length }))
14579
+ });
14580
+ }
14581
+
14582
+ // src/mcp/tools/trace.ts
14583
+ async function runAtlasTrace(args, host) {
14584
+ const resolved = await host.getWorkspace();
14585
+ if (!resolved) return noWorkspaceResult();
14586
+ const { workspace } = resolved;
14587
+ const slug = stringArg(args, "slug");
14588
+ const changes = slug ? workspace.changes.filter((c) => c.slug === slug) : workspace.changes;
14589
+ const result = [];
14590
+ for (const change of changes) {
14591
+ const trace = checkTrace({ specs: workspace.specs, change, requireEvidence: false });
14592
+ const coversByTask = /* @__PURE__ */ new Map();
14593
+ for (const block of change.tasks?.blocks ?? []) {
14594
+ for (const task of block.tasks) coversByTask.set(task.id, task.covers.map((c) => c.toUpperCase()));
14595
+ }
14596
+ const evidenceByScenario = /* @__PURE__ */ new Map();
14597
+ for (const ev of change.verify?.evidence ?? []) {
14598
+ const known = evidenceByScenario.get(ev.scenario);
14599
+ if (known === void 0 || ev.result === "pass") evidenceByScenario.set(ev.scenario, ev.result);
14600
+ }
14601
+ const scenarios = [];
14602
+ for (const req of [...change.delta?.added ?? [], ...change.delta?.modified ?? []]) {
14603
+ for (const sc of req.scenarios) {
14604
+ const parent = sc.reqId.toUpperCase();
14605
+ const coveredBy = [...coversByTask.entries()].filter(([, covers]) => covers.includes(sc.id.toUpperCase()) || covers.includes(parent)).map(([taskId]) => taskId);
14606
+ const evidence = evidenceByScenario.get(sc.id) ?? null;
14607
+ scenarios.push({ id: sc.id, title: sc.title, coveredBy, evidence });
14608
+ }
14609
+ }
14610
+ const gaps = trace.findings.filter((f) => f.code === "TRACE-002").map((f) => String(f.target ?? f.message));
14611
+ result.push({ slug: change.slug, scenarios, gaps, summary: { errors: trace.summary.errors, warnings: trace.summary.warnings } });
14612
+ }
14613
+ if (changes.length === 0) {
14614
+ return jsonResult({ changes: [], message: "Sin cambios activos.", action: "satlas new <slug>" });
14615
+ }
14616
+ return jsonResult({ changes: result });
14617
+ }
14618
+
14619
+ // src/mcp/tools/validate.ts
14620
+ async function runAtlasValidate(args, host) {
14621
+ const slug = stringArg(args, "slug");
14622
+ if (!slug) {
14623
+ return errorResult("ATLAS-MCP-VALIDATE-001", "Falta el nombre del cambio", { action: "atlas_validate con el argumento slug" });
14624
+ }
14625
+ const resolved = await host.getWorkspace();
14626
+ if (!resolved) return noWorkspaceResult();
14627
+ const { workspace, config, approvals } = resolved;
14628
+ const change = workspace.changes.find((c) => c.slug === slug);
14629
+ if (!change) {
14630
+ return errorResult("ATLAS-MCP-VALIDATE-002", `El cambio "${slug}" no existe`, {
14631
+ available: workspace.changes.map((c) => c.slug)
14632
+ });
14633
+ }
14634
+ const evaluation = await evaluateChange(workspace, config, change, approvals);
14635
+ const findings = [...evaluation.lintFindings, ...evaluation.trace.findings].map((d) => ({
14636
+ code: d.code,
14637
+ severity: d.severity,
14638
+ message: d.message,
14639
+ path: d.path,
14640
+ line: d.line,
14641
+ suggestion: d.suggestion
14642
+ }));
14643
+ if (findings.length === 0) {
14644
+ return jsonResult({ slug, conforming: true, message: "Sin hallazgos: el cambio est\xE1 conforme.", findings: [] });
14645
+ }
14646
+ return jsonResult({
14647
+ slug,
14648
+ conforming: findings.every((f) => f.severity !== "error"),
14649
+ findings,
14650
+ summary: evaluation.trace.summary
14651
+ });
14652
+ }
14653
+
14654
+ // src/mcp/tools/index.ts
14655
+ var HANDLERS = {
14656
+ atlas_status: runAtlasStatus,
14657
+ atlas_next: runAtlasNext,
14658
+ atlas_validate: runAtlasValidate,
14659
+ atlas_trace: runAtlasTrace,
14660
+ atlas_impact: runAtlasImpact,
14661
+ atlas_glossary: runAtlasGlossary
14662
+ };
14663
+ async function runToolByName(name, args, host) {
14664
+ const handler = HANDLERS[name];
14665
+ if (!handler) {
14666
+ return errorResult("ATLAS-MCP-TOOL-002", `Operaci\xF3n desconocida: ${name}`, { available: Object.keys(HANDLERS).sort() });
14667
+ }
14668
+ return handler(args, host);
14669
+ }
14670
+
14671
+ // src/mcp/tools/list.ts
14672
+ function listTools() {
14673
+ return [
14674
+ {
14675
+ name: "atlas_status",
14676
+ description: "Estado del proyecto: cambios activos con fase, avance de tareas y evidencia, bloqueos y siguiente paso. Opcional: nombre de un cambio.",
14677
+ inputSchema: {
14678
+ type: "object",
14679
+ properties: { slug: { type: "string", description: "Nombre del cambio (opcional): si se indica, solo se informa de ese cambio" } },
14680
+ additionalProperties: false
14681
+ }
14682
+ },
14683
+ {
14684
+ name: "atlas_next",
14685
+ description: "Siguiente acci\xF3n recomendada para un cambio, con la indicaci\xF3n de si requiere una persona o la puede realizar el asistente.",
14686
+ inputSchema: {
14687
+ type: "object",
14688
+ properties: { slug: { type: "string", description: "Nombre del cambio" } },
14689
+ required: ["slug"],
14690
+ additionalProperties: false
14691
+ }
14692
+ },
14693
+ {
14694
+ name: "atlas_validate",
14695
+ description: "Hallazgos vigentes de un cambio (validaci\xF3n y trazabilidad), id\xE9nticos a los que reporta la herramienta. Nunca modifica nada.",
14696
+ inputSchema: {
14697
+ type: "object",
14698
+ properties: { slug: { type: "string", description: "Nombre del cambio" } },
14699
+ required: ["slug"],
14700
+ additionalProperties: false
14701
+ }
14702
+ },
14703
+ {
14704
+ name: "atlas_trace",
14705
+ description: "Cobertura de trazabilidad: por escenario, qu\xE9 tarea lo cubre y su evidencia, y los huecos vigentes.",
14706
+ inputSchema: {
14707
+ type: "object",
14708
+ properties: { slug: { type: "string", description: "Nombre del cambio (opcional)" } },
14709
+ additionalProperties: false
14710
+ }
14711
+ },
14712
+ {
14713
+ name: "atlas_impact",
14714
+ description: "Impacto registrado de un requisito (REQ-\u2026) o de un archivo: escenarios, tareas, cambios, archivos y evidencia relacionados. Solo relaciones registradas.",
14715
+ inputSchema: {
14716
+ type: "object",
14717
+ properties: { target: { type: "string", description: "Id de requisito (por ejemplo REQ-AUTH-001) o ruta de archivo" } },
14718
+ required: ["target"],
14719
+ additionalProperties: false
14720
+ }
14721
+ },
14722
+ {
14723
+ name: "atlas_glossary",
14724
+ description: "T\xE9rminos del glosario del negocio con su definici\xF3n vigente y sin\xF3nimos aceptados. No inventa definiciones.",
14725
+ inputSchema: {
14726
+ type: "object",
14727
+ properties: {},
14728
+ additionalProperties: false
14729
+ }
14730
+ }
14731
+ ];
14732
+ }
14733
+
14734
+ // src/mcp/server.ts
14735
+ var PROTOCOL_VERSION = "2024-11-05";
14736
+ var SERVER_NAME = "specatlas";
14737
+ var WorkspaceCache = class {
14738
+ entries = /* @__PURE__ */ new Map();
14739
+ get(root) {
14740
+ return this.entries.get(root);
14741
+ }
14742
+ set(root, mtimeMs, value) {
14743
+ this.entries.set(root, { mtimeMs, value });
14744
+ }
14745
+ };
14746
+ async function workspaceStamp(root) {
14747
+ const sddDir = path34.join(root, ".sdd");
14748
+ const markers = [sddDir, path34.join(sddDir, "config.yaml"), path34.join(sddDir, "changes"), path34.join(sddDir, "specs"), path34.join(sddDir, "approvals.yaml")];
14749
+ let stamp = 0;
14750
+ for (const marker of markers) {
14751
+ try {
14752
+ const st = await fs2.stat(marker);
14753
+ stamp = Math.max(stamp, st.mtimeMs);
14754
+ } catch {
14755
+ }
14756
+ }
14757
+ return stamp;
14758
+ }
14759
+ async function resolveMcpWorkspace(cwd, cache) {
14760
+ const root = await findWorkspaceRoot(cwd);
14761
+ if (!root) return void 0;
14762
+ const stamp = await workspaceStamp(root);
14763
+ const cached2 = cache.get(root);
14764
+ if (cached2 && cached2.mtimeMs === stamp) return cached2.value;
14765
+ const { workspace, config } = await loadWorkspace(root);
14766
+ const approvals = await loadApprovals(path34.join(root, ".sdd"));
14767
+ const value = { root, workspace, config, approvals: approvals.byArtifact };
14768
+ cache.set(root, stamp, value);
14769
+ return value;
14770
+ }
14771
+ function createHost(cwd, cache) {
14772
+ return {
14773
+ cwd,
14774
+ language: "es",
14775
+ getWorkspace: () => resolveMcpWorkspace(cwd, cache)
14776
+ };
14777
+ }
14778
+ async function handleRequest(req, host) {
14779
+ const id = req.id;
14780
+ if (req.method === "initialize") {
14781
+ return makeResult(id, {
14782
+ protocolVersion: PROTOCOL_VERSION,
14783
+ capabilities: { tools: { listChanged: false } },
14784
+ serverInfo: { name: SERVER_NAME, version: cliVersion() }
14785
+ });
14786
+ }
14787
+ if (req.method === "ping") {
14788
+ return makeResult(id, {});
14789
+ }
14790
+ if (req.method === "tools/list") {
14791
+ return makeResult(id, { tools: listTools() });
14792
+ }
14793
+ if (req.method === "tools/call") {
14794
+ const params = isObject(req.params) ? req.params : {};
14795
+ const name = typeof params["name"] === "string" ? params["name"] : "";
14796
+ if (name === "") {
14797
+ return makeResult(id, {
14798
+ content: [{ type: "text", text: JSON.stringify({ error: "ATLAS-MCP-TOOL-001", message: "Falta el nombre de la operaci\xF3n" }, null, 2) }],
14799
+ isError: true
14800
+ });
14801
+ }
14802
+ try {
14803
+ const result = await runToolByName(name, params["arguments"], host);
14804
+ return makeResult(id, result);
14805
+ } catch (err) {
14806
+ return makeError(id, INTERNAL_ERROR, "Error interno al ejecutar la operaci\xF3n", { message: err instanceof Error ? err.message : String(err) });
14807
+ }
14808
+ }
14809
+ if (req.method.startsWith("notifications/")) {
14810
+ return void 0;
14811
+ }
14812
+ return makeError(id, METHOD_NOT_FOUND, `M\xE9todo desconocido: ${req.method}`);
14813
+ }
14814
+ function log(stderr, message) {
14815
+ stderr.write(`[specatlas mcp] ${message}
14816
+ `);
14817
+ }
14818
+ async function runMcpServer(opts) {
14819
+ const { cwd, stdin, stdout, stderr } = opts;
14820
+ const cache = new WorkspaceCache();
14821
+ const host = createHost(cwd, cache);
14822
+ log(stderr, "v\xEDa de consulta iniciada (solo lectura)");
14823
+ const rl = createInterface({ input: stdin, crlfDelay: Infinity });
14824
+ for await (const line of rl) {
14825
+ if (line.trim() === "") continue;
14826
+ const parsed = parseMessage(line);
14827
+ if (parsed.error) {
14828
+ const response = makeError(null, parsed.error.code, parsed.error.message);
14829
+ stdout.write(`${JSON.stringify(response)}
14830
+ `);
14831
+ continue;
14832
+ }
14833
+ const request = parsed.request;
14834
+ try {
14835
+ const response = await handleRequest(request, host);
14836
+ if (response) stdout.write(`${JSON.stringify(response)}
14837
+ `);
14838
+ } catch (err) {
14839
+ log(stderr, err instanceof Error ? err.message : String(err));
14840
+ stdout.write(`${JSON.stringify(makeError(request.id, INTERNAL_ERROR, "Error interno"))}
14841
+ `);
14842
+ }
14843
+ }
14844
+ }
14845
+
14846
+ // src/commands/mcp.ts
14847
+ async function runMcp(ctx) {
14848
+ await runMcpServer({
14849
+ cwd: ctx.cwd,
14850
+ stdin: process.stdin,
14851
+ stdout: process.stdout,
14852
+ stderr: process.stderr
14853
+ });
14854
+ return { exitCode: 0, diagnostics: [], data: { served: true } };
14855
+ }
14856
+
14857
+ // src/commands/packs.ts
14858
+ import path35 from "path";
14211
14859
  async function runPacks(ctx) {
14212
14860
  const { root, config, workspace } = await requireWorkspace(ctx);
14213
14861
  const slug = flagString(ctx.flags, "check");
@@ -14265,14 +14913,14 @@ async function runPacks(ctx) {
14265
14913
  data: {
14266
14914
  slug,
14267
14915
  packs: evaluations.map((evaluation) => ({ id: evaluation.pack.id, status: evaluation.status, passed: evaluation.passed, failed: evaluation.failed, results: evaluation.results })),
14268
- analyzePath: path32.posix.join(".sdd", "changes", slug, "analyze.md")
14916
+ analyzePath: path35.posix.join(".sdd", "changes", slug, "analyze.md")
14269
14917
  },
14270
14918
  text: lines
14271
14919
  };
14272
14920
  }
14273
14921
 
14274
14922
  // src/commands/new.ts
14275
- import path33 from "path";
14923
+ import path36 from "path";
14276
14924
  async function runNew(ctx) {
14277
14925
  const slug = ctx.positionals[0];
14278
14926
  if (!slug) {
@@ -14298,50 +14946,18 @@ async function runNew(ctx) {
14298
14946
  msg("new.title", ctx.language),
14299
14947
  "",
14300
14948
  msg("new.done", ctx.language),
14301
- ...result.files.map((f) => ` + ${path33.relative(ctx.cwd, f)}`),
14949
+ ...result.files.map((f) => ` + ${path36.relative(ctx.cwd, f)}`),
14302
14950
  "",
14303
14951
  `Siguiente: /satlas.specify ${result.slug} \u2014 escribe la especificaci\xF3n 100% funcional y de negocio.`
14304
14952
  ];
14305
14953
  return {
14306
14954
  exitCode: hasErrors2 ? 1 : 0,
14307
14955
  diagnostics: result.diagnostics,
14308
- data: { slug: result.slug, files: result.files.map((f) => path33.relative(ctx.cwd, f)) },
14956
+ data: { slug: result.slug, files: result.files.map((f) => path36.relative(ctx.cwd, f)) },
14309
14957
  text: lines
14310
14958
  };
14311
14959
  }
14312
14960
 
14313
- // src/evaluate.ts
14314
- import path34 from "path";
14315
- function livingRequirementsMap2(specs) {
14316
- const map = /* @__PURE__ */ new Map();
14317
- for (const spec of specs) {
14318
- for (const req of spec.spec.requirements) map.set(req.id, req);
14319
- }
14320
- return map;
14321
- }
14322
- async function evaluateChange(workspace, config, change, approvals) {
14323
- const deltaPath = path34.join(change.dir, "spec.md");
14324
- const deltaContent = await readTextIfExists(deltaPath);
14325
- const approval = verifyApproval(change, approvals, config, deltaContent ?? void 0);
14326
- const living = livingRequirementsMap2(workspace.specs);
14327
- const lintFindings = change.delta ? lintDelta(change.delta, living, deltaPath, { language: config.spec.language }) : [];
14328
- const trace = checkTrace({
14329
- specs: workspace.specs,
14330
- change,
14331
- requireEvidence: config.gates.verify.mode !== "off" && config.gates.verify.require_evidence
14332
- });
14333
- const blocking = lintFindings.filter((d) => d.severity === "error").length + trace.findings.filter((d) => d.severity === "error").length;
14334
- const mockupsAreReady = requiresMockups(change.meta, config) ? await mockupsReady(workspace.root, change.slug, change) : void 0;
14335
- const state = deriveState({
14336
- change,
14337
- cfg: config,
14338
- approval,
14339
- blockingFindings: change.delta ? blocking : 0,
14340
- ...mockupsAreReady !== void 0 ? { mockupsReady: mockupsAreReady } : {}
14341
- });
14342
- return { change, approval, lintFindings, trace, blocking, state };
14343
- }
14344
-
14345
14961
  // src/commands/next.ts
14346
14962
  async function runNext(ctx) {
14347
14963
  const { workspace, config, approvals } = await requireWorkspace(ctx);
@@ -14372,7 +14988,7 @@ async function runNext(ctx) {
14372
14988
  }
14373
14989
 
14374
14990
  // src/commands/present.ts
14375
- import path35 from "path";
14991
+ import path37 from "path";
14376
14992
  async function runPresentCommand(ctx) {
14377
14993
  const { root } = await requireWorkspace(ctx);
14378
14994
  const slug = ctx.positionals[0];
@@ -14384,7 +15000,7 @@ async function runPresentCommand(ctx) {
14384
15000
  if (result.path) {
14385
15001
  lines.push("Propuesta generada");
14386
15002
  lines.push("");
14387
- lines.push(` archivo: ${path35.relative(ctx.cwd, result.path)}`);
15003
+ lines.push(` archivo: ${path37.relative(ctx.cwd, result.path)}`);
14388
15004
  if (result.hash) lines.push(` hash de la spec: ${shortHash(result.hash)}`);
14389
15005
  lines.push("");
14390
15006
  lines.push("\xC1brela en el navegador y comp\xE1rtela con el stakeholder.");
@@ -14394,7 +15010,7 @@ async function runPresentCommand(ctx) {
14394
15010
  return {
14395
15011
  exitCode: hasErrors2 ? 1 : 0,
14396
15012
  diagnostics: result.diagnostics,
14397
- data: result.path ? { path: path35.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
15013
+ data: result.path ? { path: path37.relative(ctx.cwd, result.path), hash: result.hash } : void 0,
14398
15014
  text: lines
14399
15015
  };
14400
15016
  }
@@ -14512,7 +15128,7 @@ async function runStatus(ctx) {
14512
15128
  }
14513
15129
 
14514
15130
  // src/commands/trace.ts
14515
- import path36 from "path";
15131
+ import path38 from "path";
14516
15132
  async function runTrace(ctx) {
14517
15133
  const { workspace, config } = await requireWorkspace(ctx);
14518
15134
  const slug = flagString(ctx.flags, "change");
@@ -14532,7 +15148,7 @@ async function runTrace(ctx) {
14532
15148
  data.push({ slug: change.slug, nodes: result.graph.nodes.length, edges: result.graph.edges.length, summary: result.summary, findings: result.findings });
14533
15149
  lines.push(` ${change.slug}: ${result.graph.nodes.length} nodos, ${result.graph.edges.length} aristas, ${errors} errores, ${result.summary.warnings} avisos`);
14534
15150
  for (const finding of result.findings) {
14535
- const location = finding.path ? ` (${path36.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
15151
+ const location = finding.path ? ` (${path38.relative(ctx.cwd, finding.path)}${finding.line ? `:${finding.line}` : ""})` : "";
14536
15152
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code} \u2014 ${finding.message}${location}`);
14537
15153
  }
14538
15154
  }
@@ -14542,7 +15158,7 @@ async function runTrace(ctx) {
14542
15158
  }
14543
15159
 
14544
15160
  // src/commands/validate.ts
14545
- import path37 from "path";
15161
+ import path39 from "path";
14546
15162
  async function runValidate(ctx) {
14547
15163
  const { workspace, config, approvals } = await requireWorkspace(ctx);
14548
15164
  const slug = flagString(ctx.flags, "change");
@@ -14568,7 +15184,7 @@ async function runValidate(ctx) {
14568
15184
  for (const finding of evaluation.lintFindings) {
14569
15185
  lines.push(` ${finding.severity.toUpperCase()} ${finding.code}${finding.line ? `:${finding.line}` : ""} \u2014 ${finding.message}`);
14570
15186
  }
14571
- lines.push(` archivo: ${path37.relative(ctx.cwd, path37.join(change.dir, "spec.md"))}`);
15187
+ lines.push(` archivo: ${path39.relative(ctx.cwd, path39.join(change.dir, "spec.md"))}`);
14572
15188
  }
14573
15189
  if (targets.length === 0) lines.push("Sin cambios activos.");
14574
15190
  const errors = diagnostics.filter((d) => d.severity === "error").length;
@@ -14583,7 +15199,7 @@ async function runValidate(ctx) {
14583
15199
  }
14584
15200
 
14585
15201
  // src/commands/verify.ts
14586
- import path38 from "path";
15202
+ import path40 from "path";
14587
15203
  async function runVerify(ctx) {
14588
15204
  const { root, workspace } = await requireWorkspace(ctx);
14589
15205
  const slug = ctx.positionals[0];
@@ -14626,7 +15242,7 @@ async function runVerify(ctx) {
14626
15242
  return { exitCode: 2, diagnostics: [{ code: "ATLAS-VERIFY-001", severity: "error", message: "Falta --by <nombre> (o define meta.owner)" }] };
14627
15243
  }
14628
15244
  const profilesDir = await resolveProfilesDir();
14629
- const profile = await loadActiveProfile(path38.join(root, ".sdd"), [profilesDir ?? "", path38.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
15245
+ const profile = await loadActiveProfile(path40.join(root, ".sdd"), [profilesDir ?? "", path40.join(root, ".sdd", "profiles", "custom")].filter(Boolean));
14630
15246
  const record = await recordEvidence({
14631
15247
  root,
14632
15248
  slug,
@@ -14648,7 +15264,7 @@ async function runVerify(ctx) {
14648
15264
  if (record.evidence.command) lines.push(` comando: ${record.evidence.command}`);
14649
15265
  lines.push(` resultado: ${record.evidence.result}`);
14650
15266
  if (record.evidence.outputHash) lines.push(` hash: ${record.evidence.outputHash}`);
14651
- lines.push(` archivo: ${path38.relative(ctx.cwd, record.path)}`);
15267
+ lines.push(` archivo: ${path40.relative(ctx.cwd, record.path)}`);
14652
15268
  if (record.stdout) {
14653
15269
  lines.push("");
14654
15270
  lines.push(" salida (\xFAltimas l\xEDneas):");
@@ -14663,7 +15279,7 @@ async function runVerify(ctx) {
14663
15279
  return {
14664
15280
  exitCode: record.exitCode === 0 ? 0 : 1,
14665
15281
  diagnostics: record.diagnostics,
14666
- data: { evidence: record.evidence, path: path38.relative(ctx.cwd, record.path), exitCode: record.exitCode },
15282
+ data: { evidence: record.evidence, path: path40.relative(ctx.cwd, record.path), exitCode: record.exitCode },
14667
15283
  text: lines
14668
15284
  };
14669
15285
  }
@@ -14708,7 +15324,7 @@ El cambio ${slug} no tiene tareas todav\xEDa.`] };
14708
15324
  }
14709
15325
 
14710
15326
  // src/cli.ts
14711
- var HANDLERS = {
15327
+ var HANDLERS2 = {
14712
15328
  init: runInit,
14713
15329
  adopt: runAdopt,
14714
15330
  new: runNew,
@@ -14729,6 +15345,7 @@ var HANDLERS = {
14729
15345
  mockup: runMockup,
14730
15346
  present: runPresentCommand,
14731
15347
  ci: runCi,
15348
+ mcp: runMcp,
14732
15349
  metrics: runMetrics,
14733
15350
  run: runRun,
14734
15351
  archive: runArchive,
@@ -14736,7 +15353,7 @@ var HANDLERS = {
14736
15353
  help: (ctx) => runHelp(ctx)
14737
15354
  };
14738
15355
  var COMMANDS = Object.fromEntries(
14739
- CATALOG.map((spec) => [spec.name, { description: spec.description, flags: spec.flags, handler: HANDLERS[spec.name] }])
15356
+ CATALOG.map((spec) => [spec.name, { description: spec.description, flags: spec.flags, handler: HANDLERS2[spec.name] }])
14740
15357
  );
14741
15358
  function toEnvelope(command, result) {
14742
15359
  const warnings = result.diagnostics.filter((d) => d.severity !== "error").map(stripSeverity);
@@ -14761,7 +15378,7 @@ async function requireWorkspace(ctx) {
14761
15378
  throw new CliError("ATLAS-WS-001", msg("cli.noWorkspace", ctx.language), 2);
14762
15379
  }
14763
15380
  const { workspace, config } = await loadWorkspace(root);
14764
- const approvals = await loadApprovals(path39.join(root, ".sdd"));
15381
+ const approvals = await loadApprovals(path41.join(root, ".sdd"));
14765
15382
  return { root, workspace, config, approvals: approvals.byArtifact };
14766
15383
  }
14767
15384
  var CliError = class extends Error {
@@ -14828,7 +15445,7 @@ function printResult(command, result, json2, language) {
14828
15445
  }
14829
15446
  const lines = [...result.text ?? []];
14830
15447
  for (const d of result.diagnostics) {
14831
- const location = d.path ? ` ${path39.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
15448
+ const location = d.path ? ` ${path41.relative(process.cwd(), d.path)}${d.line ? `:${d.line}` : ""}` : "";
14832
15449
  lines.push(`${d.severity === "error" ? "ERROR" : d.severity === "warning" ? "AVISO" : "NOTA"} ${d.code}${location} \u2014 ${d.message}`);
14833
15450
  if (d.suggestion) lines.push(` \u21B3 ${d.suggestion}`);
14834
15451
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specatlas",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "SpecAtlas: kernel determinista de Spec-Driven Development (CLI)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@specatlas/adapters": "0.1.1",
43
- "@specatlas/core": "0.1.21"
43
+ "@specatlas/core": "0.1.22"
44
44
  },
45
45
  "scripts": {
46
46
  "typecheck": "tsc --noEmit",