feat: cerebro reactivo del meta-orquestador (flow 0012, fase 2)
Primitivas (python/functions/infra): - drain_fleet_events: consume la cola del watcher (~/.claude/fleet/ events.jsonl) desde un cursor, agrupa por clasificacion, marca urgentes. 7 tests. - set_dod_contract: escribe el DoD-contrato fijo (dod_contract/dod_status) en el goal.json de un agente sin pisar el resto (escritura atomica). 5 tests. Skill /orquestador evolucionado (sin romper lo existente): vigila la flota por su DoD (no por 'esta vivo'). Nueva seccion 'Consumo de la cola de la flota': DoD-contrato obligatorio al lanzar, drenar la cola, politicas por clasificacion (RECLAMA escala / DICE_TERMINADO verifica / ESTANCADO nudge / MAL_LANZADO re-DoD), verificador independiente del ejecutor (lee el report vs dod_contract), splitter con tope de fan-out, y cadencia (drain al actuar + heartbeat). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""Tests para set_dod_contract."""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from .set_dod_contract import set_dod_contract
|
||||
|
||||
|
||||
def _read(path: str) -> dict:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def test_escribe_contrato_en_goal_json_existente_y_preserva_otros_campos(tmp_path):
|
||||
sid = "sess-existente"
|
||||
path = os.path.join(str(tmp_path), f"{sid}.json")
|
||||
existing = {
|
||||
"goal": "implementar la fase 2",
|
||||
"phase": "build",
|
||||
"dod": "tests verdes (volatil, lo escribe el hook)",
|
||||
"history": [{"turn": 1, "action": "init"}],
|
||||
"prompts": ["primer prompt"],
|
||||
"emojis": "🚀",
|
||||
}
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(existing, f)
|
||||
|
||||
res = set_dod_contract(sid, "criterio fijo de aceptacion", status="pending", goals_dir=str(tmp_path))
|
||||
|
||||
assert res["written"] is True
|
||||
assert res["session_id"] == sid
|
||||
assert res["path"] == path
|
||||
assert res["dod_contract"] == "criterio fijo de aceptacion"
|
||||
assert res["dod_status"] == "pending"
|
||||
|
||||
data = _read(path)
|
||||
# Las dos claves nuevas estan.
|
||||
assert data["dod_contract"] == "criterio fijo de aceptacion"
|
||||
assert data["dod_status"] == "pending"
|
||||
# TODO el resto se preserva intacto.
|
||||
assert data["goal"] == "implementar la fase 2"
|
||||
assert data["phase"] == "build"
|
||||
assert data["dod"] == "tests verdes (volatil, lo escribe el hook)"
|
||||
assert data["history"] == [{"turn": 1, "action": "init"}]
|
||||
assert data["prompts"] == ["primer prompt"]
|
||||
assert data["emojis"] == "🚀"
|
||||
|
||||
|
||||
def test_crea_el_archivo_si_no_existe(tmp_path):
|
||||
sid = "sess-nueva"
|
||||
path = os.path.join(str(tmp_path), f"{sid}.json")
|
||||
assert not os.path.exists(path)
|
||||
|
||||
res = set_dod_contract(sid, "contrato nuevo", goals_dir=str(tmp_path))
|
||||
|
||||
assert os.path.exists(path)
|
||||
assert res["dod_status"] == "pending" # default
|
||||
data = _read(path)
|
||||
assert data == {"dod_contract": "contrato nuevo", "dod_status": "pending"}
|
||||
|
||||
|
||||
def test_status_invalido_lanza_valueerror_sin_escribir(tmp_path):
|
||||
sid = "sess-bad-status"
|
||||
path = os.path.join(str(tmp_path), f"{sid}.json")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
set_dod_contract(sid, "un contrato", status="done", goals_dir=str(tmp_path))
|
||||
|
||||
assert not os.path.exists(path)
|
||||
|
||||
|
||||
def test_contract_vacio_lanza_valueerror_sin_escribir(tmp_path):
|
||||
sid = "sess-empty"
|
||||
path = os.path.join(str(tmp_path), f"{sid}.json")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
set_dod_contract(sid, "", goals_dir=str(tmp_path))
|
||||
with pytest.raises(ValueError):
|
||||
set_dod_contract(sid, " ", goals_dir=str(tmp_path))
|
||||
|
||||
assert not os.path.exists(path)
|
||||
|
||||
|
||||
def test_reescribir_actualiza_sin_duplicar(tmp_path):
|
||||
sid = "sess-reescribe"
|
||||
path = os.path.join(str(tmp_path), f"{sid}.json")
|
||||
|
||||
set_dod_contract(sid, "contrato v1", status="pending", goals_dir=str(tmp_path))
|
||||
set_dod_contract(sid, "contrato v2", status="met", goals_dir=str(tmp_path))
|
||||
|
||||
data = _read(path)
|
||||
assert data["dod_contract"] == "contrato v2"
|
||||
assert data["dod_status"] == "met"
|
||||
# Solo dos claves, ninguna duplicada ni residual.
|
||||
assert sorted(data.keys()) == ["dod_contract", "dod_status"]
|
||||
# El archivo es JSON valido unico (no concatenado).
|
||||
raw = open(path, "r", encoding="utf-8").read()
|
||||
assert raw.count('"dod_contract"') == 1
|
||||
assert raw.count('"dod_status"') == 1
|
||||
Reference in New Issue
Block a user