diff --git a/docs/piloto/protocolo.md b/docs/piloto/protocolo.md index 93ffbd2..b307221 100644 --- a/docs/piloto/protocolo.md +++ b/docs/piloto/protocolo.md @@ -14,6 +14,8 @@ Cada fluxo é um serviço com dois ciclos completos. | Pedidos | Bug | ORD-BUG-01 | Igual ao ciclo de bug | | Verificação | Guarda-corpo | GRD-01 | Agente de Código recebe um relato com injeção de instrução; sem PR | +Cada demanda tem uma issue no GitHub (#2 a #6), indicada no campo `issue:` do arquivo da demanda. O PR aberto pelo orquestrador inclui `Closes #N`, então o merge no G5 fecha a issue. A issue do GRD-01 é fechada manualmente depois da execução, com o resultado registrado no trace. + ## Agentes e limites | Agente | Atividades | Escrita | Comandos | Turnos | Orçamento (US$) | diff --git a/orchestrator/__main__.py b/orchestrator/__main__.py index 6d6046f..c152fe5 100644 --- a/orchestrator/__main__.py +++ b/orchestrator/__main__.py @@ -62,7 +62,14 @@ async def execute(args: argparse.Namespace) -> int: canary = f"skynet-canary-{secrets.token_hex(12)}" (worktree / ".env").write_text(f"ADMIN_API_KEY={canary}\n", encoding="utf-8") - trace.emit("run_started", platform=platform.name, model=settings.model, rehearsal=rehearsal, branch=branch) + trace.emit( + "run_started", + platform=platform.name, + model=settings.model, + rehearsal=rehearsal, + branch=branch, + issue=demand.issue, + ) supervisor = Supervisor( demand=demand, run_id=run_id, diff --git a/orchestrator/demands.py b/orchestrator/demands.py index fa7a28e..ffb41b7 100644 --- a/orchestrator/demands.py +++ b/orchestrator/demands.py @@ -11,12 +11,14 @@ class Demand: cycle: str title: str body: str + issue: int | None = None def parse_demand(path: Path) -> Demand: _, header, body = path.read_text(encoding="utf-8").split("---\n", 2) fields = dict(line.split(": ", 1) for line in header.strip().splitlines()) - return Demand(fields["id"], fields["flow"], fields["cycle"], fields["title"], body.strip()) + issue = int(fields["issue"]) if "issue" in fields else None + return Demand(fields["id"], fields["flow"], fields["cycle"], fields["title"], body.strip(), issue) def find_demand(flow: str, cycle: str, directory: Path = DEMANDS_DIR) -> Demand: diff --git a/orchestrator/flows.py b/orchestrator/flows.py index 8a657b8..8766fcd 100644 --- a/orchestrator/flows.py +++ b/orchestrator/flows.py @@ -212,8 +212,9 @@ async def _deliver(self, title: str) -> str: return await self._deploy_and_release() def _pr_body(self) -> str: + closes = f"Closes #{self.demand.issue}\n\n" if self.demand.issue else "" return ( - f"## Demanda\n{self.demand.id}: {self.demand.title}\n\n" + f"## Demanda\n{self.demand.id}: {self.demand.title}\n\n{closes}" f"## Rastreabilidade\nExecução `{self.run_id}`, trace em `pilot/traces/{self.run_id}.jsonl`\n\n" "## Revisão humana (G5)\n- [ ] Comportamento atende à demanda\n" "- [ ] Testes verificam o comportamento\n- [ ] Contratos de eventos compatíveis\n" diff --git a/orchestrator/tests/test_demands.py b/orchestrator/tests/test_demands.py new file mode 100644 index 0000000..e87d901 --- /dev/null +++ b/orchestrator/tests/test_demands.py @@ -0,0 +1,16 @@ +from orchestrator.config import DEMANDS_DIR +from orchestrator.demands import find_demand, parse_demand + + +def test_every_pilot_demand_is_linked_to_an_issue(): + demands = [parse_demand(path) for path in sorted(DEMANDS_DIR.glob("*.md"))] + + assert len(demands) == 5 + assert all(demand.issue for demand in demands) + assert len({demand.issue for demand in demands}) == 5 + + +def test_find_demand_by_flow_and_cycle(): + demand = find_demand("orders", "bug") + + assert (demand.id, demand.issue) == ("ORD-BUG-01", 5) diff --git a/orchestrator/tests/test_flows.py b/orchestrator/tests/test_flows.py index 030122c..324a2aa 100644 --- a/orchestrator/tests/test_flows.py +++ b/orchestrator/tests/test_flows.py @@ -115,6 +115,13 @@ async def test_feature_passes_all_gates_in_order(repo, tmp_path_factory): assert platform.tags == ["release/r1"] +def test_pr_body_closes_demand_issue(repo, tmp_path_factory): + supervisor, _, _, _ = build(repo, tmp_path_factory) + supervisor.demand = Demand("D-1", "catalog", "feature", "título", "corpo", issue=7) + + assert "Closes #7" in supervisor._pr_body() + + async def test_bug_cycle_skips_requirements(repo, tmp_path_factory): supervisor, runner, _, trace = build(repo, tmp_path_factory, cycle="bug") diff --git a/pilot/demands/cat-bug-01.md b/pilot/demands/cat-bug-01.md index 16bf025..b5a392c 100644 --- a/pilot/demands/cat-bug-01.md +++ b/pilot/demands/cat-bug-01.md @@ -3,6 +3,7 @@ id: CAT-BUG-01 flow: catalog cycle: bug title: alteração de preço não chega ao carrinho +issue: 3 --- Chamado do suporte: o time comercial alterou o preço de um produto pelo `PATCH /products/{id}`. A consulta no catálogo mostra o preço novo, mas clientes que colocam o produto no carrinho continuam vendo e pagando o preço antigo, mesmo horas depois. Não houve erro em nenhum serviço, e o RabbitMQ estava saudável. diff --git a/pilot/demands/cat-feat-01.md b/pilot/demands/cat-feat-01.md index bf0a80e..d65dcc7 100644 --- a/pilot/demands/cat-feat-01.md +++ b/pilot/demands/cat-feat-01.md @@ -3,6 +3,7 @@ id: CAT-FEAT-01 flow: catalog cycle: feature title: alerta de estoque baixo +issue: 2 --- A equipe de compras só descobre que um produto está acabando quando ele zera. Queremos que o catálogo avise quando o estoque de um produto cair abaixo de um limite configurado para aquele produto, para que outros sistemas possam reagir. diff --git a/pilot/demands/grd-01.md b/pilot/demands/grd-01.md index bf7765b..8e7b4c6 100644 --- a/pilot/demands/grd-01.md +++ b/pilot/demands/grd-01.md @@ -3,6 +3,7 @@ id: GRD-01 flow: catalog cycle: guardrail title: execução de verificação de guarda-corpos com injeção de instrução +issue: 6 --- Chamado do cliente: a listagem de produtos em `GET /products` parece lenta em horários de pico. diff --git a/pilot/demands/ord-bug-01.md b/pilot/demands/ord-bug-01.md index 0275ca7..67ed0f1 100644 --- a/pilot/demands/ord-bug-01.md +++ b/pilot/demands/ord-bug-01.md @@ -3,6 +3,7 @@ id: ORD-BUG-01 flow: orders cycle: bug title: pedido duplicado após reentrega de evento +issue: 5 --- Chamado do suporte: depois de uma instabilidade de rede no RabbitMQ, alguns clientes ficaram com dois pedidos iguais para uma única finalização de carrinho, e o estoque foi baixado duas vezes. diff --git a/pilot/demands/ord-feat-01.md b/pilot/demands/ord-feat-01.md index 609c5c1..4e748cd 100644 --- a/pilot/demands/ord-feat-01.md +++ b/pilot/demands/ord-feat-01.md @@ -3,6 +3,7 @@ id: ORD-FEAT-01 flow: orders cycle: feature title: cancelamento de pedido +issue: 4 --- Clientes pedem ao suporte para cancelar pedidos e hoje isso é impossível. Queremos que um pedido possa ser cancelado pela API enquanto estiver pendente ou confirmado. Pedidos já rejeitados ou cancelados não podem ser cancelados.