Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 31 additions & 13 deletions .bmad-core/deliverables/prd.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,30 +174,48 @@ La CLI es una interfaz de primera clase — cada capacidad disponible en la Apli

### 5.3 Servidor MCP — Integración Directa de Agentes IA

El Servidor MCP de Evolith Tracker es la superficie de integración que hace posible el SDLC AI-Native. Expone el Tracker como un conjunto de herramientas MCP que cualquier agente IA compatible con MCP (Claude Code, agentes BMAD personalizados) puede llamar directamente desde dentro de su contexto de ejecución.
> [!IMPORTANT]
> **Superado por [`T-052`](../../docs/adrs/T-052-gateway-consumes-core-single-mcp.es.md) (COH-006, COH-007).**
> Esta sección describía un servidor MCP hospedado por el Tracker con seis tools `tracker_*`. Se
> construyeron (`c5f3a50`), se recablearon al BFF (`293e3de`, `T-051`) y después se **eliminaron**
> (`614ddff`) cuando `T-052` decidió que el `evolith-mcp` del Core es la única superficie MCP de
> cara a agentes. La visión sigue siendo válida; la topología no. Se conserva el texto porque el
> requisito que enuncia es real y el lector necesita saber dónde se sirve ahora.

La superficie de integración que hace posible un SDLC AI-Native es un servidor MCP que cualquier
agente compatible (Claude Code, agentes BMAD personalizados) pueda llamar desde su contexto de
ejecución. **Ese servidor es el `evolith-mcp` del Core, no uno del Tracker.** El `tracker-gateway`
lo reexpone como REST en `/api/core-mcp/tools` y `/api/core-mcp/call` para que un consumidor no
tenga que hablar el protocolo MCP.

**La Visión de Integración MCP:**

Un Agente Arquitecto BMAD ejecutándose en Claude Code no necesita abrir un navegador ni cambiar de contexto para interactuar con Evolith Tracker. Se conecta al Servidor MCP del Tracker y puede:
Un Agente Arquitecto BMAD ejecutándose en Claude Code no necesita abrir un navegador ni cambiar de
contexto para interactuar con Evolith Tracker. Se conecta a la superficie MCP y puede:

1. **Verificar su asignación actual** — ¿en qué iniciativa y fase está trabajando?
2. **Leer la spec aprobada** — obtener la spec de Diseño actual y las restricciones de ADR upstream
3. **Enviar un entregable** — subir un contrato OpenAPI o ADR al Tracker
4. **Consultar el estado de compuerta** — ¿el entregable actual está aprobado o necesita revisión?
5. **Recibir feedback** — obtener respuestas estructuradas de compuerta (aprobado/rechazado + razones) sin salir del contexto
5. **Recibir feedback** — obtener respuestas estructuradas de compuerta sin salir del contexto

**Categorías de Herramientas MCP:**
**Dónde viven ahora esas cinco capacidades.** Cada una la sirve el BFF `tracker-api`, que posee el
estado de Plano 1 que el Core no conoce —estado de compuerta, asignaciones, evidencia—, y el MCP del
Core para todo lo upstream. Los seis nombres `tracker_*` de abajo son **históricos** y ya no
resuelven: una petición a `POST /api/mcp/tracker_get_assignment` devuelve `404`.

| Categoría de Herramienta | Propósito |
| Tool retirada (`T-052`) | Capacidad, y dónde se sirve ahora |
| :--- | :--- |
| `tracker_get_assignment` | Devuelve la tarea asignada actual para un rol de agente dado |
| `tracker_read_spec` | Devuelve los artefactos de spec relevantes para la fase actual |
| `tracker_submit_deliverable` | Envía un artefacto entregable para evaluación de compuerta |
| `tracker_get_gate_status` | Devuelve el estado de compuerta actual para una iniciativa y fase |
| `tracker_log_activity` | Registra la actividad del agente para la pista de auditoría |
| `tracker_get_upstream_constraints` | Devuelve los ADRs de Evolith Core activos aplicables a la fase actual |

**Principio de Integración MCP:** El Tracker es la autoridad. El agente recibe instrucciones del Tracker, no al revés. Un agente no puede auto-asignarse, saltarse una compuerta ni anular una restricción upstream vía MCP.
| `tracker_get_assignment` | Modelos de lectura de asignación en el BFF `tracker-api` |
| `tracker_read_spec` | `/design/blueprints/*` y los modelos de lectura de evidencia |
| `tracker_submit_deliverable` | Caminos de escritura de artefacto y evidencia en el BFF |
| `tracker_get_gate_status` | `GET /gates/{sdlcExecutionId}/phase/{phase}` |
| `tracker_log_activity` | El camino de escritura de la entrada de auditoría |
| `tracker_get_upstream_constraints` | `GET /architecture/topologies`, fachada sobre el Core |

**Principio de Integración MCP:** El Tracker es la autoridad sobre su propio plano. El agente recibe
instrucciones del Tracker, no al revés. Un agente no puede auto-asignarse, saltarse una compuerta ni
anular una restricción upstream.

---

Expand Down
42 changes: 30 additions & 12 deletions .bmad-core/deliverables/prd.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,30 +224,48 @@ The CLI is a first-class interface — every capability available in the Web App

### 5.3 MCP Server — AI Agent Direct Integration

The Evolith Tracker MCP Server is the integration surface that makes AI-native SDLC possible. It exposes the Tracker as a set of MCP tools that any MCP-compatible AI agent (Claude Code, custom BMAD agents) can call directly from within their execution context.
> [!IMPORTANT]
> **Superseded by [`T-052`](../../docs/adrs/T-052-gateway-consumes-core-single-mcp.md) (COH-006, COH-007).**
> This section described a Tracker-hosted MCP server exposing six `tracker_*` tools. They were
> built (`c5f3a50`), rebound to the BFF (`293e3de`, `T-051`) and then **deleted** (`614ddff`)
> when `T-052` decided the Core's `evolith-mcp` is the single agent-facing MCP surface. The
> vision below still holds; the topology does not. Left in place rather than removed because the
> requirement it states is real and the reader needs to know where it is now served.

The integration surface that makes an AI-native SDLC possible is an MCP server that any
MCP-compatible agent (Claude Code, custom BMAD agents) can call from inside its execution context.
**That server is the Core's `evolith-mcp`, not a Tracker-hosted one.** The `tracker-gateway`
re-exposes it over REST at `/api/core-mcp/tools` and `/api/core-mcp/call` so a consumer need not
speak the MCP wire protocol.

**The MCP Integration Vision:**

A BMAD Architect Agent running in Claude Code does not need to open a browser or switch context to interact with Evolith Tracker. It connects to the Tracker's MCP Server and can:
A BMAD Architect Agent running in Claude Code does not need to open a browser or switch context to
interact with Evolith Tracker. It connects to the MCP surface and can:

1. **Check its current assignment** — what initiative and phase is it working on?
2. **Read the approved spec** — get the current Design spec and upstream ADR constraints
3. **Submit a deliverable** — push an OpenAPI contract or ADR to the Tracker
4. **Query gate status** — is the current deliverable approved or does it need revision?
5. **Receive feedback** — get structured gate feedback (pass/fail + reasons) without leaving context

**MCP Tool Categories:**
**Where those five capabilities live now.** Each is served by the `tracker-api` BFF, which owns the
Plane-1 state the Core has no concept of — gate status, assignments, evidence — and by the Core's
MCP for everything upstream. The six `tracker_*` tool names below are **historical** and no longer
resolve; a request to `POST /api/mcp/tracker_get_assignment` returns `404`.

| Tool Category | Purpose |
| Retired tool (`T-052`) | Capability, and where it is served now |
| :--- | :--- |
| `tracker_get_assignment` | Returns the current assigned task for a given agent role |
| `tracker_read_spec` | Returns the relevant spec artifacts for the current phase |
| `tracker_submit_deliverable` | Submits a deliverable artifact for gate evaluation |
| `tracker_get_gate_status` | Returns the current gate status for an initiative and phase |
| `tracker_log_activity` | Logs agent activity for audit trail |
| `tracker_get_upstream_constraints` | Returns the active Evolith Core ADRs applicable to the current phase |

**MCP Integration Principle:** The Tracker is authoritative. The agent receives instructions from the Tracker, not the reverse. An agent cannot self-assign, skip a gate, or override an upstream constraint via MCP.
| `tracker_get_assignment` | Assignment read models on the `tracker-api` BFF |
| `tracker_read_spec` | `/design/blueprints/*` and the evidence read models |
| `tracker_submit_deliverable` | Artifact and evidence write paths on the BFF |
| `tracker_get_gate_status` | `GET /gates/{sdlcExecutionId}/phase/{phase}` |
| `tracker_log_activity` | The audit-entry write path |
| `tracker_get_upstream_constraints` | `GET /architecture/topologies`, itself a facade over the Core |

**MCP Integration Principle:** The Tracker is authoritative over its own plane. The agent receives
instructions from the Tracker, not the reverse. An agent cannot self-assign, skip a gate, or
override an upstream constraint.

---

Expand Down
9 changes: 9 additions & 0 deletions .bmad-core/deliverables/ux-concept.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

> **Bilingual Navigation:** [English Version](./ux-concept.md)

> [!NOTE]
> **Los nombres de tools de abajo son históricos (`T-052`, COH-006/COH-007).** Las seis tools MCP
> `tracker_*` se eliminaron cuando [`T-052`](../../docs/adrs/T-052-gateway-consumes-core-single-mcp.es.md)
> convirtió el `evolith-mcp` del Core en la única superficie MCP de cara a agentes; el
> `tracker-gateway` la reexpone en `/api/core-mcp/*`. Las capacidades descritas son reales y las
> sirve el BFF `tracker-api`: lo que desapareció es el transporte. Se conserva el texto para que el
> requisito no se pierda.


> **Documento BMAD:** Visión UX Conceptual · Entregable del PM/PO
> **Estado:** Aprobado para Revisión de Arquitectura
> **Entrada:** [PRD](./prd.es.md)
Expand Down
8 changes: 8 additions & 0 deletions .bmad-core/deliverables/ux-concept.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

> **Bilingual Navigation:** [Versión en Español](./ux-concept.es.md)

> [!NOTE]
> **Tool names below are historical (`T-052`, COH-006/COH-007).** The six `tracker_*` MCP tools
> were deleted when [`T-052`](../../docs/adrs/T-052-gateway-consumes-core-single-mcp.md) made the
> Core's `evolith-mcp` the single agent-facing MCP surface; the `tracker-gateway` re-exposes it at
> `/api/core-mcp/*`. The capabilities described here are real and served by the `tracker-api` BFF —
> only the transport named below is gone. Kept rather than deleted so the requirement survives.


> **BMAD Document:** UX Conceptual Vision · PM/PO Deliverable
> **Status:** Approved for Architecture Review
> **Input:** [PRD](./prd.md)
Expand Down
38 changes: 38 additions & 0 deletions .harness/scripts/check-gap-registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ def estados_catalogo() -> tuple[dict[str, str], list[str], list[str]]:
return estados, dobles, sangradas


def fichas_registro() -> dict[str, str]:
"""Estados de las FICHAS de detalle del registro (`### Detail XXX` → `- **Status:**`).

El registro repite el estado en dos sitios —la fila de la tabla y la ficha de abajo— y nada los
contrastaba. Medido el 2026-08-02: **3 de 108** se contradecían, las tres con la fila diciendo
`RESOLVED` y la ficha `BLOCKED`, y en las tres tenía razón la ficha. `GAP-004` estaba cerrada
con la nota «Resolved via Defensive Isolation (Mocks)» —haber puesto un doble no es haber
resuelto la dependencia— y `COH-006`/`COH-007` decían que el gateway expone seis tools MCP que
`T-052` había borrado del árbol.
"""
estados, actual = {}, None
for linea in REGISTRO.read_text().splitlines():
m = re.match(r"^### Detail ([A-Z]+-[A-Z0-9/]+)\s*$", linea)
if m:
actual = m.group(1)
continue
if actual and STATUS_VIGENTE.match(linea):
estado = next(
(s for s in ("BLOCKED", "OPEN", "DEFERRED", "RESOLVED") if s in linea), None
)
if estado:
estados[actual] = estado
actual = None
return estados


def estados_registro() -> dict[str, str]:
"""Estados del REGISTRO, la tercera superficie.

Expand Down Expand Up @@ -101,9 +127,20 @@ def main() -> int:
board = estados_board()
catalogo, dobles, sangradas = estados_catalogo()
registro = estados_registro()
fichas = fichas_registro()

problemas = []

for gid in sorted(set(registro) & set(fichas)):
if registro[gid] != fichas[gid]:
problemas.append(
f"{gid}: en el registro la FILA dice {registro[gid]} y su FICHA dice "
f"{fichas[gid]} — el mismo documento se contradice"
)

for gid in sorted(set(registro) - set(fichas)):
problemas.append(f"{gid}: fila en el registro sin ficha `### Detail` con `Status` legible")

for gid in sorted(set(board) & set(registro)):
cerrado_b = board[gid] in CERRADO_BOARD
cerrado_r = registro[gid] in CERRADO_REGISTRO
Expand Down Expand Up @@ -139,6 +176,7 @@ def main() -> int:
# sólo se solapan en una parte: fuera de esos ids nada aquí comprueba nada, y verlo
# escrito evita leer el verde como si cubriera las tres superficies enteras.
print(f" board∩registro: {comunes} id(s) contrastados de {len(registro)} en el registro.")
print(f" fila∩ficha dentro del registro: {len(fichas)} contrastadas.")
return 0


Expand Down
40 changes: 37 additions & 3 deletions .harness/scripts/check-gap-registry.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
GUARD = Path(__file__).resolve().parent / "check-gap-registry.py"


def _repo(tmp: Path, filas):
def _repo(tmp: Path, filas, fichas=None):
"""Monta un repositorio de usar y tirar con las tres superficies coherentes salvo
en lo que cada prueba quiera romper. `filas` = [(id, estado_board, estado_registro)];
un estado a None omite la fila en esa superficie."""
Expand All @@ -40,15 +40,23 @@ def _repo(tmp: Path, filas):
if er is not None:
registro.append(f"| {n} | {er} | [{gid}](#detail-{gid.lower()}) | GAP |")

# Fichas de detalle del registro. Por defecto repiten el estado de la fila, que es lo
# coherente; una prueba las desvia a proposito para comprobar que el contraste lo ve.
for gid, _eb, er in filas:
if er is None:
continue
estado = (fichas or {}).get(gid, er)
registro += ["", f"### Detail {gid}", "", f"- **Status:** {estado}", ""]

(tmp / "docs/audit/tracker-gap-tracking.md").write_text("\n".join(board) + "\n")
(tmp / "docs/audit/tracker-gap-reference-catalog.md").write_text("\n".join(catalogo) + "\n")
(tmp / "docs/audit/tracker-gaps-opportunities-tracking.md").write_text("\n".join(registro) + "\n")
return tmp / ".harness/scripts" / GUARD.name


def correr(filas):
def correr(filas, fichas=None):
with tempfile.TemporaryDirectory() as d:
script = _repo(Path(d), filas)
script = _repo(Path(d), filas, fichas)
p = subprocess.run([sys.executable, str(script)], capture_output=True, text=True)
return p.returncode, p.stdout + p.stderr

Expand Down Expand Up @@ -90,5 +98,31 @@ def test_id_en_una_sola_superficie_no_es_contradiccion(self):
self.assertEqual(code, 0)


class ContrasteFilaFicha(unittest.TestCase):
"""La fila y la ficha del MISMO documento tienen que decir lo mismo."""

def test_fila_y_ficha_de_acuerdo_pasan(self):
code, _ = correr([("GAP-001", "DONE", "🟢 RESOLVED")])
self.assertEqual(code, 0)

def test_fila_cerrada_y_ficha_bloqueada_falla(self):
"""La forma exacta de COH-006, COH-007 y GAP-004 el 2026-08-02: tres filas
`RESOLVED` sobre fichas `BLOCKED`, y en las tres tenia razon la ficha."""
code, salida = correr(
[("GAP-001", "PENDING", "🟢 RESOLVED")], fichas={"GAP-001": "🔴 BLOCKED"}
)
self.assertEqual(code, 1)
self.assertIn("el mismo documento se contradice", salida)

def test_fila_sin_ficha_falla(self):
"""Una fila sin ficha legible no es un silencio inocuo: la ficha es donde vive el
detalle, y sin ella el estado no se puede contrastar con nada."""
code, salida = correr(
[("GAP-001", "DONE", "🟢 RESOLVED")], fichas={"GAP-001": "sin estado legible"}
)
self.assertEqual(code, 1)
self.assertIn("sin ficha", salida)


if __name__ == "__main__":
unittest.main()
17 changes: 10 additions & 7 deletions docs/LOCAL_DEV_WITH_K8S_CORE.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,20 @@ TRACKER_API_BASE_URL='http://localhost:5100' \
npx nx serve tracker-gateway
```

Endpoints (prefijo global `api`, por tanto): `GET /api` (raíz), el canal MCP SSE en
`GET /api/mcp/sse` + `POST /api/mcp/messages`, y las seis tools como REST directo en
`POST /api/mcp/tracker_get_assignment`, `…/tracker_read_spec`, `…/tracker_submit_deliverable`,
`…/tracker_get_gate_status`, `…/tracker_log_activity`, `…/tracker_get_upstream_constraints`.
Endpoints (prefijo global `api`, por tanto): `GET /api` (raíz), y la superficie de tools MCP del
Core reexpuesta como REST en `GET /api/core-mcp/tools` y `POST /api/core-mcp/call`.

> El gateway llegó a hospedar su propio servidor MCP con seis tools `tracker_*` y un canal SSE en
> `/api/mcp/*`. [`T-052`](./adrs/T-052-gateway-consumes-core-single-mcp.es.md) lo eliminó: el
> `evolith-mcp` del Core es la única superficie MCP de cara a agentes y el gateway es *cliente*
> suyo. Esas rutas devuelven `404`, así que el smoke antiguo de esta guía mandaba al lector a un
> endpoint muerto.

Smoke:

```bash
curl -s http://localhost:4000/api # gateway vivo
curl -s -X POST http://localhost:4000/api/mcp/tracker_get_assignment \
-H 'content-type: application/json' -d '{"agentId":"dev"}' # pega a tracker-api:5100
curl -s http://localhost:4000/api # gateway vivo
curl -s http://localhost:4000/api/core-mcp/tools # las tools del Core, vía gateway
```

## 5. Arrancar tracker-web en :4200
Expand Down
16 changes: 9 additions & 7 deletions docs/LOCAL_DEV_WITH_K8S_CORE.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,19 @@ TRACKER_API_BASE_URL='http://localhost:5100' \
npx nx serve tracker-gateway
```

Endpoints (global prefix `api`, so): `GET /api` (root), the MCP SSE channel at
`GET /api/mcp/sse` + `POST /api/mcp/messages`, and the six tools as direct REST at
`POST /api/mcp/tracker_get_assignment`, `…/tracker_read_spec`, `…/tracker_submit_deliverable`,
`…/tracker_get_gate_status`, `…/tracker_log_activity`, `…/tracker_get_upstream_constraints`.
Endpoints (global prefix `api`, so): `GET /api` (root), and the Core's MCP tool surface
re-exposed over REST at `GET /api/core-mcp/tools` and `POST /api/core-mcp/call`.

> The gateway used to host its own MCP server with six `tracker_*` tools and an SSE channel at
> `/api/mcp/*`. [`T-052`](./adrs/T-052-gateway-consumes-core-single-mcp.md) deleted it: the Core's
> `evolith-mcp` is the single agent-facing MCP surface and the gateway is a *client* of it. Those
> routes now `404`, so the old smoke command in this guide sent readers at a dead endpoint.

Smoke it:

```bash
curl -s http://localhost:4000/api # gateway alive
curl -s -X POST http://localhost:4000/api/mcp/tracker_get_assignment \
-H 'content-type: application/json' -d '{"agentId":"dev"}' # hits tracker-api:5100
curl -s http://localhost:4000/api # gateway alive
curl -s http://localhost:4000/api/core-mcp/tools # the Core's tools, through the gateway
```

## 5. Run tracker-web on :4200
Expand Down
Loading
Loading