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
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ O CI (`.github/workflows/ci.yml`) roda os três. O teste de concorrência (`inte

- `cmd/orq`: `main`.
- `internal/cli`: comandos cobra e saída em texto.
- `internal/central`: store SQLite, migrations embutidas (`migrations/NNNN_*.sql`, aplicadas por `PRAGMA user_version`) e regras (status, onda, próxima US, pendências).
- `internal/central`: store SQLite, migrations embutidas (`migrations/NNNN_*.sql`, aplicadas por `PRAGMA user_version`) e regras (status, onda, próxima US, pendências), mais o cache dos enriquecedores (`cache.go`).
- `internal/enriquecer`: consultas ao Herdr, às forjas e ao Taiga, tokens de sidebar, cliente do socket do Herdr e o laço do `orq daemon`.
- `internal/refs`: parse de `projeto/312`, `TG-312`, `TG-312#P2` e URL de MR.
- `internal/output`: envelope de erro `{"error":{"code","cause","recovery"}}` e códigos de saída.
- `docs/snapshot.md`: contrato do `orq snapshot`.

## Convenções

- Mensagens, docs e códigos de erro em português; saída JSON fora de terminal, texto num terminal (`--output` força).
- Toda escrita no banco é uma transação curta (`BEGIN IMMEDIATE` via `_txlock`) e grava o seu evento no diário na mesma transação.
- Toda escrita no banco é uma transação curta (`BEGIN IMMEDIATE` via `_txlock`) e grava o seu evento no diário na mesma transação. A exceção é o que não é estado da Central: cache, suspensão e tokens publicados dos enriquecedores. A mudança de estado de uma MR é estado e gera `mr.estado`.
- Ferramenta externa (`herdr`, `glab`, `gh`, `taiga`) é chamada por caminho absoluto (`enriquecer.Ferramentas`), nunca pelo PATH, e só para ler — o daemon escreve no Herdr apenas os tokens `orq` (pane) e `orq_mrs` (workspace), com `--source orq`. No Herdr o token é por nome, não por fonte: registre em `token_publicado` só depois de um `report-metadata` bem-sucedido, e limpe só se o Herdr ainda mostra o último valor do orq. Valor de outro produtor (ex. `mrs_prontas` da skill manual) não é adotado, sobrescrito nem limpo. Falha de subprocesso vira `Erro` no cache; não derruba o processo.
- A abertura do banco (criação, WAL, migrations) fica dentro do `flock` de `central.db.init.lock`: a troca para WAL num banco novo devolve `SQLITE_BUSY` na hora, sem passar pelo `busy_timeout`.
- Migration nova é arquivo novo; nunca edite uma já publicada.
- Mudança que remove ou muda o sentido de um campo do snapshot sobe `versao` e atualiza `docs/snapshot.md`.
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Central de US para o orquestrador multiagente: CLI `orq` e plugin do [Herdr](htt

Desenho e entregas: projeto `orq` no Taiga (https://agile.basis.com.br/project/orq).

Esta versão traz o estado da Central num SQLite e os comandos de registro. A consulta ao Herdr, ao GitLab e ao Taiga, o daemon, o TUI e o plugin chegam nas próximas entregas.
Esta versão traz o estado da Central num SQLite, os comandos de registro e o daemon que consulta o Herdr, as forjas (GitLab e GitHub) e o Taiga e publica o resumo na sidebar do Herdr. O TUI e o plugin chegam nas próximas entregas.

## Instalação

Expand Down Expand Up @@ -56,8 +56,27 @@ Em `onda list` e `pend list`, `--coord` filtra pelo coordenador dono da onda; em
| `orq pend list [--abertas] [--coord X] [--onda N]` | Lista as pendências. |
| `orq diario [--us <ref>] [--onda N] [--coord X] [--limite 50]` | Eventos gravados, do mais antigo ao mais novo (toda escrita gera um). |
| `orq snapshot [--incluir-encerradas] [--json]` | Estado consolidado em JSON versionado (`--json` é aceito, mas a saída já é sempre JSON); contrato em [`docs/snapshot.md`](docs/snapshot.md). |
| `orq atualizar [--fonte herdr\|mr\|taiga] [--forcar]` | Consulta as fontes uma vez (todas, sem `--fonte`; repetível ou `mr,taiga`), grava o cache e, com o Herdr entre elas, confere os tokens de sidebar. Cada chave respeita o TTL; `--forcar` ignora o TTL e a suspensão por autenticação. |
| `orq daemon [--tick 5s] [--intervalo-remoto 15s]` | Mantém o cache e os tokens vivos, em primeiro plano, até SIGTERM/SIGINT. Um por máquina. |
| `orq version` | Versão. |

### Daemon e enriquecedores

`orq daemon` roda em primeiro plano (o plugin do Herdr o inicia; o log vai para o stderr) e só existe um por diretório de estado: ele segura um `flock` em `daemon.lock`, e o segundo sai com `daemon_ativo` (exit 4) dizendo o pid do primeiro. A cada tick ele lê o Herdr (`herdr api snapshot`) e confere os tokens; MR e Taiga são olhados a cada `--intervalo-remoto`, cada chave com o seu TTL (MR 60 s, Taiga 5 min). Ele também assina `pane.agent_status_changed` dos panes das ondas abertas no socket do Herdr (`$HERDR_SOCKET_PATH`, a sessão de `$HERDR_SESSION` ou `~/.config/herdr/herdr.sock`) e republica na hora; em `events_lost`, reassina e reconcilia com uma leitura completa. Ao sair, limpa os tokens que publicou e que ainda têm o valor dele.

Só leituras nas forjas e no Taiga. Falha de uma consulta (binário ausente, autenticação, rede) fica no cache com o código e o snapshot mostra o último valor bom com o `erro`; nenhuma derruba o processo. Erro de autenticação (na taiga-cli, o exit 3) suspende a ferramenta por 30 min: o orq não tenta login nem define token. `--forcar` passa por cima de uma suspensão anterior, mas uma recusa na própria execução interrompe as chamadas seguintes da ferramenta.

As ferramentas são chamadas por caminho absoluto, porque num pane ou numa ação de plugin o PATH não tem `~/.local/bin`. A ordem: `ORQ_HERDR_BIN`, `ORQ_GLAB_BIN`, `ORQ_GH_BIN` e `ORQ_TAIGA_BIN`; para o herdr, `$HERDR_BIN_PATH`; e então `~/.local/bin`, `~/go/bin`, `~/bin`, `/home/linuxbrew/.linuxbrew/bin`, `/opt/homebrew/bin`, `/usr/local/bin`, `/usr/bin` e `/bin`. O PATH do processo não entra.

Tokens de sidebar (`herdr pane|workspace report-metadata --source orq`), conferidos a cada tick contra o que o Herdr mostra — os tokens somem quando o servidor do Herdr reinicia, e o daemon republica:

- `orq`, no pane de cada coordenador: as US das ondas abertas por status e as pendências abertas, ex. `2 Exec·1 Rev·1 pend`, com `⚠ ` na frente quando algum pane de US está `blocked`. Até 80 caracteres.
- `orq_mrs`, no workspace do coordenador (o registrado ou o do pane dele): as MRs `pronta`, `!32` no GitLab e `skills#29` no GitHub; o que não cabe em 80 caracteres vira `+N`.

Os nomes `orq` e `orq_mrs` são do orq; `mrs_prontas`, `story` e `fase` são da publicação manual da skill do orquestrador (`--source orquestrador`), que convive com o daemon até ser aposentada. O plugin configura as linhas da sidebar com os nomes do orq.

Só pane e workspace que existem recebem token. No Herdr o token é por nome, não por fonte, então a propriedade vem do próprio orq: ele registra um token só depois de publicá-lo com sucesso, e o token continua sendo dele enquanto o Herdr mostrar o último valor que ele publicou. Um token ausente é (re)publicado. Um valor de outro produtor no mesmo nome não é adotado, sobrescrito nem limpo; vira aviso (`avisos` em `orq atualizar`, uma linha no log do daemon). O que deixou de valer só é limpo (`--clear-token`) se ainda tem o valor do orq; na saída, o daemon relê o Herdr antes de limpar.

### Ondas e a próxima US

A onda declara até onde vai (`--ate`) e a ordem. `done` numa US quer dizer que ela chegou a esse limite: plano aprovado (`plano`), MR pronta para revisão humana, sem merge (`mr-pronta`), ou MR mesclada (`merge`). `orq onda proxima` devolve a autorização e o texto do limite junto com a US, para quem despacha saber até onde ir.
Expand Down
108 changes: 99 additions & 9 deletions docs/snapshot.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contrato do `orq snapshot` (versão 1)

`orq snapshot` imprime, sempre em JSON, o estado consolidado da Central: coordenadores → ondas → US (com MRs e pendências), mais a lista das pendências abertas. É o que o TUI e o daemon consomem; o resto da CLI pode mudar de forma, este documento não sem subir a versão.
`orq snapshot` imprime, sempre em JSON, o estado consolidado da Central: coordenadores → ondas → US (com MRs e pendências), mais a lista das pendências abertas e o estado vivo que os enriquecedores trazem do Herdr, das forjas e do Taiga. É o que o TUI e o daemon consomem; o resto da CLI pode mudar de forma, este documento não sem subir a versão.

- Por padrão, só as ondas abertas. `--incluir-encerradas` traz todas.
- `orq snapshot --json`, a forma do desenho, também vale: a flag é aceita e não muda nada. `--output text` é ignorado.
Expand All @@ -13,14 +13,59 @@
- Datas em RFC 3339, UTC, com segundos (`2026-10-11T14:03:40Z`).
- Ordem: coordenadores por nome; ondas por id; US pela posição na onda; MRs pela ordem em que foram ligadas; pendências pela abertura.

### Histórico

- Versão 1, US #3: o esqueleto.
- Ainda versão 1, US #4: os campos `herdr` (coordenador e US), `taiga` (US), `atualizado_em` e `erro` (MR) e `fontes` (raiz) entraram, e `MR.estado` passou a ser preenchido. Tudo aditivo: `estado` já estava declarado como `draft|aberta|pronta|mesclada|fechada`, e quem lia a versão 1 continua lendo.

## Estado vivo (US #4)

O snapshot não consulta nada: ele lê o cache que `orq daemon` (em laço) e `orq atualizar` (uma vez) gravam. Cada fonte guarda o último valor bom e o erro da última consulta, então um valor pode estar velho e com `erro` ao lado; quem desenha decide pelo `atualizado_em`.

| Fonte | O que lê | Cadência no daemon |
|---|---|---|
| `herdr` | `herdr api snapshot`: status do agente, nome e workspace de cada pane, tokens | a cada tick (5 s) e a cada `pane.agent_status_changed` |
| `mr` | `glab api` (GitLab) ou `gh api` (GitHub), só leitura | TTL de 60 s por MR |
| `taiga` | `taiga story get --output json`, só leitura | TTL de 5 min por US |

Estado da MR: `mesclada`, `fechada`, `draft`, `pronta` (aberta, não draft e mesclável: `detailed_merge_status` `mergeable` no GitLab, `mergeable_state` `clean` ou `has_hooks` no GitHub) ou `aberta` (o resto: conflito, pipeline, revisão obrigatória, ainda não calculado). Mudança de estado gera `mr.estado` no diário.

Erro de autenticação de `glab`, `gh` ou `taiga` suspende a ferramenta por 30 min: o orq não tenta login nem define token. Na taiga-cli, é o exit 3 (`auth_*`, `session_*`, `secret_*`, `keyring_*`, `*_secret_*`, `password_file_*`). `orq atualizar --forcar` passa por cima de uma suspensão anterior, mas uma recusa na própria execução interrompe as chamadas seguintes da ferramenta. A suspensão aparece em `fontes.<fonte>.erro`.

O snapshot não traz os tokens de sidebar. O daemon publica, com nomes próprios, `orq` no pane do coordenador e `orq_mrs` no workspace, e nunca toca token de outro produtor (README, "Daemon e enriquecedores").

## Esquema

```text
{
"versao": 1,
"gerado_em": data,
"coordenadores": [Coordenador],
"pendencias_abertas": [Pendencia] # todas as abertas, de todos os coordenadores
"pendencias_abertas": [Pendencia], # todas as abertas, de todos os coordenadores
"fontes": {"herdr": Fonte, "mr": Fonte, "taiga": Fonte}
}

Fonte {
"consultado_em": data|null, # última consulta, boa ou não
"atualizado_em": data|null, # último valor bom
"erro": Erro|null # o que vale para a fonte inteira: falha do Herdr ou ferramenta suspensa
}

Erro {
"codigo": "binario_ausente"|"autenticacao"|"nao_encontrado"|"tempo_esgotado"|"falha_comando"|"resposta_invalida",
"mensagem": string, # da ferramenta, cortada
"em": data # na fonte, o início da suspensão
}

Herdr { # estado vivo de um pane
"status": "idle"|"working"|"blocked"|"done"|"unknown"|"ausente"|null,
# unknown: pane sem agente; ausente: o Herdr respondeu e o pane não existe;
# null: o Herdr nunca respondeu
"agente": string|null, # "claude", "codex"
"nome": string|null, # nome do agente no Herdr
"workspace_id": string|null,
"atualizado_em": data|null,
"erro": Erro|null # a última consulta ao Herdr falhou: o resto é o último valor bom
}

Coordenador {
Expand All @@ -29,6 +74,7 @@ Coordenador {
"workspace_id": string|null,
"projeto_padrao": string|null, # slug do Taiga usado em TG-<n> sem projeto
"registrado_em": data, # último registro
"herdr": Herdr|null, # nulo sem pane_id
"ondas": [Onda]
}

Expand Down Expand Up @@ -57,16 +103,26 @@ US {
"rodada": int|null,
"atualizado_em": data,
"mrs": [MR],
"pendencias": [Pendencia] # abertas e resolvidas
"pendencias": [Pendencia], # abertas e resolvidas
"herdr": Herdr|null, # nulo sem pane_id
"taiga": {
"titulo": string|null,
"status": string|null, # nome do status no Taiga, "In progress"
"url": string|null,
"atualizado_em": data|null,
"erro": Erro|null
}
}

MR {
"url": string, # normalizada, sem query, fragmento ou subpágina
"projeto_path": string, # "basis/iac/plataforma" ou "BasisTI/orq"
"iid": int,
"iac": bool, # projeto sob basis/iac/
"estado": string|null, # nulo até os enriquecedores (US #4); depois draft|aberta|pronta|mesclada|fechada
"adicionada_em": data
"estado": "draft"|"aberta"|"pronta"|"mesclada"|"fechada"|null, # nulo até a primeira consulta boa
"adicionada_em": data,
"atualizado_em": data|null, # última consulta boa à forja
"erro": Erro|null # última consulta, se falhou
}

Pendencia {
Expand Down Expand Up @@ -100,6 +156,14 @@ Pendencia {
"workspace_id": null,
"projeto_padrao": "colaboradados",
"registrado_em": "2026-10-11T14:00:00Z",
"herdr": {
"status": "working",
"agente": "claude",
"nome": null,
"workspace_id": "wB",
"atualizado_em": "2026-10-11T14:09:58Z",
"erro": null
},
"ondas": [
{
"id": 1,
Expand Down Expand Up @@ -129,8 +193,10 @@ Pendencia {
"projeto_path": "basis/colaboradados/api",
"iid": 41,
"iac": false,
"estado": null,
"adicionada_em": "2026-10-11T14:05:00Z"
"estado": "pronta",
"adicionada_em": "2026-10-11T14:05:00Z",
"atualizado_em": "2026-10-11T14:09:30Z",
"erro": null
}
],
"pendencias": [
Expand All @@ -148,7 +214,22 @@ Pendencia {
"onda_id": 1,
"coordenador": "colaboradados"
}
]
],
"herdr": {
"status": "blocked",
"agente": "claude",
"nome": "tg215-exec",
"workspace_id": "wB",
"atualizado_em": "2026-10-11T14:09:58Z",
"erro": null
},
"taiga": {
"titulo": "Exportar a folha em CSV",
"status": "In progress",
"url": "https://agile.basis.com.br/project/colaboradados/us/215",
"atualizado_em": "2026-10-11T14:06:00Z",
"erro": null
}
}
]
}
Expand All @@ -170,6 +251,15 @@ Pendencia {
"onda_id": 1,
"coordenador": "colaboradados"
}
]
],
"fontes": {
"herdr": {"consultado_em": "2026-10-11T14:09:58Z", "atualizado_em": "2026-10-11T14:09:58Z", "erro": null},
"mr": {"consultado_em": "2026-10-11T14:09:30Z", "atualizado_em": "2026-10-11T14:09:30Z", "erro": null},
"taiga": {
"consultado_em": "2026-10-11T14:06:00Z",
"atualizado_em": "2026-10-11T14:06:00Z",
"erro": {"codigo": "autenticacao", "mensagem": "taiga suspenso: taiga: session_expired", "em": "2026-10-11T14:08:00Z"}
}
}
}
```
Loading
Loading