docs(plan): OPS108 + OPS109 + OPS110 — modo --auto nas skills, painel TUI de issues e sessões de agente persistentes (Related #1018, #1020, #1019)

This commit is contained in:
Francisco Solla 2026-09-15 07:05:26 -03:00
parent 45617356ee
commit 729703cee2
4 changed files with 696 additions and 0 deletions

View file

@ -0,0 +1,88 @@
# OPS108 — Modo autônomo por flag nas skills plan-issue e work-issue
Status: rascunho
Atualizado em: 2026-09-15
Issue: #1018
Priority: P2
Impeccable: A — N/A (arquivos de skills/comandos; sem UI de produto)
Rascunho UI: N/A — sem UI
Appetite: ~0,5–1 dia eng; uma flag com semântica única nas duas skills + comandos
Responsável: —
## Intenção
> "Possibilidade de rodar as skills `plan-issue` e `work-issue` de forma autônoma, passando flag que sinaliza que o agente não precisa esperar por confirmação do plano, e pode auto-aprovar o que ele próprio recomendar."
Hoje os dois fluxos param por definição: `plan-issue` fecha o lote num GATE que exige confirmação explícita antes de qualquer Issue/PR; `work-issue` escreve o plano de implementação e para até confirmação explícita antes de codar. Isso é o comportamento certo para colaborar, mas o dono do processo quer também poder **disparar os dois sem supervisão quando ele escolher** — deixar o agente seguir, auto-aprovar a própria recomendação e entregar (Issue registrada / execução até PR), reservando ao humano só o que o repo já exige (CI, auto-merge no servidor, aprovação de produção).
O pedido não é remover os gates — é tornar a pausa **opt-out por invocação**, com a flag explícita carregando a intenção. Sem flag, nada muda.
## Persona e fluxo
- **Persona / contexto:** o operador do paradigma (hoje o `fsolla`), de mesa, disparando um lote de ideias ou uma Issue já claimada quando não quer acompanhar passo a passo.
- **Job principal:** iniciar o fluxo e voltar depois com ele concluído até onde o repo permite — Issue registrada ou PR de entrega aberto.
- **Fluxo desejado:** invoca com a flag → o agente explora, escreve o plano, aprova a própria recomendação e segue (registro + PR no plan-issue; execução até PR no work-issue) → o humano revisa o resultado no fim, e só é interrompido antes se um invariante duro aparecer.
- **Anti-goals de produto:** não virar o default (autonomia é opt-in por invocação); não criar skill gêmea nem segundo fluxo; não mexer no contrato de claim, no pool dormente nem no launch de worktree; não auto-aprovar produção nem qualquer gate que o repo trata como humano.
## Objetivo e aceite
- Com a flag, `plan-issue` conduz o lote do overview até o registro (Issues + PR dos planos) **sem** esperar confirmação humana; sem a flag, o GATE atual permanece idêntico.
- Com a flag, `work-issue` cria/apresenta o plano de implementação, o marca como aprovado e executa até o PR **sem** pausa; sem a flag, a pausa atual permanece idêntica.
- **Pode auto-aprovar (recomendação do próprio agente, dentro do que a skill já decide):** conteúdo e fatiamento dos planos, prioridade, dependências, abordagem técnica, cortes de escopo já previstos nas skills e os literais de dados que o próprio plano recomenda (registrados como assumidos, para validação posterior).
- **Continua parando (invariantes duros do repo, valem mesmo com a flag):** Consent/LGPD fail-closed, migração de schema, contrato de URL público, shapes públicos e produção (aprovação humana do environment). Divergência material de produto → o agente para e reporta: no `work-issue` a Issue vira `blocked` (precedente do fluxo autônomo); no `plan-issue` o item não é registrado e fica fora do lote até decisão humana.
- Flag ausente ou desconhecida → comportamento atual preservado (fail-safe de compatibilidade).
- O claim continua sendo contrato do ambiente: o modo autônomo do `work-issue` não claima Issue nenhuma.
## Dados (intenção)
- **Vou apresentar dados?** Não — **Dados: N/A**: sem superfície de dados de campanha; o item muda só o fluxo das skills e dos comandos que as invocam.
- **Decisões desbloqueadas:** N/A — nenhuma decisão de campanha depende disso.
- **Forma:** N/A — nada a apresentar.
## Dados da decisão (literais)
- Flag única `--auto` nas duas skills (decidido no gate 2026-09-15). Ela remove a pausa das skills; nunca os invariantes duros. Atenção: o `--auto` do CLI do opencode é outro conceito (auto-aprovar permissões de ferramenta) — a skill deve deixar a distinção explícita no texto.
- Sem a flag, os dois GATEs atuais permanecem exatamente como estão (pausa explícita).
- Lista fechada de auto-aprovação: planos/fatiamento, prioridade, dependências, abordagem técnica, cortes já permitidos pelas skills, literais recomendados pelo próprio plano.
- Lista fechada de parada dura: Consent/LGPD fail-closed, migração de schema, contrato de URL público, shapes públicos, produção; divergência material de produto.
- Escopo: as duas skills + os dois comandos que as invocam; claim, pool e launch de worktree intocados.
- ID reservado **OPS108**; kind **chore**; sem UI (Impeccable A).
## Direção no codebase (hipótese)
- **Áreas prováveis:** os dois documentos de skill (`plan-issue` e `work-issue`) e os dois comandos que as invocam (`.opencode/commands/`) — a flag tende a viajar no texto já repassado hoje (o único flag documentado hoje é `--issue <N>`); os comandos ganham só a menção que a torna descobrível.
- **Precedente a olhar:** `agent-work-issue` (fluxo autônomo de ponta a ponta; único freio = divergência material de produto → `blocked`) e `engineering-audit` (modo primário autônomo; humano é gate final). `docs/plans/engineering-audit-v2-impl.md:41` lista o que fica fora da noite por construção — mesma lista de parada dura.
- **Risco de acoplamento:** editar o dono, não twinar (nada de skill nova). O claim é do ambiente (`worktree next`/`pnpm agent:claim`) e não pode ser puxado para a skill; `agent-work-issue` continua sendo o contrato do pool dormente.
## Dependências
- Nenhuma dura. Soft (fora deste item): o launch de worktree hoje monta `/work-issue --issue <N>` no prompt — um dia pode carregar a flag junto, mas isso é mudança de outro dono.
## Fora de escopo
- Mudar o contrato de claim, o pool dormente ou o launch de worktree (dependência futura citada acima).
- Tornar autonomia o default ou ter modo autônomo implícito por heurística.
- Um terceiro fluxo/skill gêmeo; espelhos `.claude/`/`.cursor/`.
- Auto-aprovar CI, auto-merge ou produção: os gates de servidor continuam como estão.
- Consolidar `agent-work-issue` com `work-issue --auto` (pergunta em aberto abaixo).
## Rabbit holes de produto
- **"Já que existe flag, deixa ligado sempre."** Autonomia silenciosa remove os gates de colaboração que protegem o lote em construção. **Corte neste item:** opt-in por invocação; sem flag, pausa idêntica à de hoje.
- **"A flag pula tudo, inclusive o que o repo trava."** Um modo que atravessa Consent/migração/URL/produção vira bypass de LGPD e de banco. **Corte neste item:** a lista de parada dura nunca é afetada pela flag.
- **"Faz uma skill só para autônomo."** Twin do mesmo fluxo cria drift entre dois donos. **Corte neste item:** a própria skill ganha o modo; nenhum fluxo paralelo.
## Questões em aberto (produto)
- **No `plan-issue` autônomo, divergência material de produto achada na exploração: registra bloqueada ou deixa fora?** **Opções:** A) não registra e reporta no resumo final (itens restantes seguem) · B) registra `blocked` com o motivo. **Recomendação:** A — preserva a regra "nada no tracker antes do gate" e mantém o item barato de retomar; a Issue nasce quando o humano decidir. _(assumido — validar)_
- **`work-issue --auto` vira o mesmo que `agent-work-issue`?** **Opções:** A) mantê-los separados neste item (contrato do pool vs. sessão humana) · B) consolidar num só. **Recomendação:** A agora; a convivência fica documentada e a consolidação é item futuro se os dois divergirem. _(assumido — validar)_
## Referências
- GitHub Issue #1018
- `.agents/skills/plan-issue/SKILL.md` — ciclo, "nada no tracker antes do gate", Passo 4 (GATE) e Passo 5 (registro)
- `.agents/skills/work-issue/SKILL.md` — Passo 3 (impl plan + GATE humano) e o "Proibido" que preserva claim/CI
- `.opencode/commands/plan-issue.md` e `.opencode/commands/work-issue.md` — wrappers que repassam `$ARGUMENTS`
- `.agents/skills/agent-work-issue/SKILL.md` — precedente autônomo (impl plan `aprovado`; divergência → `blocked`)
- `.agents/skills/engineering-audit/SKILL.md` — modo autônomo com humano como gate final
- `docs/plans/engineering-audit-v2-impl.md` (l.41) e `docs/AGENT-OPS.md` — o que o repo mantém humano por construção

View file

@ -0,0 +1,401 @@
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<title>UI draft — OPS109 · Painel de issues e planos no terminal</title>
</head>
<body class="bg-zinc-950 text-zinc-100 antialiased">
<main class="mx-auto max-w-[1400px] p-6 sm:p-8">
<h1 class="text-lg font-medium text-zinc-100">
UI draft — Painel de issues e planos no terminal (OPS109)
</h1>
<p class="mt-1 max-w-3xl text-sm text-zinc-400">
O mantenedor abre a TUI no terminal (mesa ou SSH do laptop/celular): lista de issues com
estado, prioridade e o que esperam dele; detalhe com plano de intenção e de implementação e
o status de cada um; leitor de markdown renderizado. Do detalhe também abre o rascunho UI no
browser da máquina e anexa à sessão do agente que está rodando aquela issue. Read-only sobre
tracker e planos.
</p>
<p class="mt-2 text-sm text-zinc-500">
Cena estática — sem interação, sem dados reais. Cinza = estrutura · acento
<span class="text-sky-400">sky</span> = seleção/foco · cores de estado = leitura funcional ·
barras cinza = conteúdo não especificado.
</p>
<!-- ============================================================ -->
<!-- Cena 1 · desktop (~100 colunas) — lista de issues -->
<!-- ============================================================ -->
<section class="mt-8">
<p class="text-xs tracking-wide text-zinc-500 uppercase">
Cena 1 · desktop (~100 colunas) — lista com filtro por estado
</p>
<div class="mt-3 w-full max-w-[1100px] overflow-x-auto">
<div
class="min-w-[900px] overflow-hidden rounded-lg border border-zinc-700 bg-zinc-950 font-mono text-[13px] leading-6"
>
<div class="flex items-center gap-3 border-b border-zinc-700 px-4 py-2">
<span class="font-medium text-zinc-200">teqo · issues</span>
<span class="text-zinc-500">|</span>
<span class="text-zinc-400">repo fsolla/teqo</span>
<span class="ml-auto text-zinc-500">última sync: 14:32</span>
</div>
<div class="flex flex-wrap gap-x-5 gap-y-1 border-b border-zinc-800 px-4 py-2">
<span class="bg-sky-500/15 px-2 text-sky-300">1 todas (43)</span>
<span class="px-2 text-zinc-400">2 em andamento (3)</span>
<span class="px-2 text-zinc-400">3 aguardando (12)</span>
<span class="px-2 text-zinc-400">4 concluídas (28)</span>
</div>
<div class="border-b border-zinc-800 px-4 py-1 text-xs text-zinc-500 uppercase">
<span class="inline-block w-28">estado</span>
<span class="inline-block w-20">id</span>
<span class="inline-block w-[380px]">título</span>
<span class="inline-block w-36">plano</span>
<span>prio</span>
</div>
<div class="bg-sky-500/15 text-sky-100">
<div class="px-4 py-1">
<span class="inline-block w-28 text-sky-300">● em andamento</span>
<span class="inline-block w-20">C163</span>
<span class="inline-block w-[380px]">Título de exemplo da issue em curso</span>
<span class="inline-block w-36">impl em execução</span>
<span>P1</span>
</div>
</div>
<div class="border-t border-zinc-900 text-zinc-300">
<div class="px-4 py-1">
<span class="inline-block w-28 text-amber-300">◌ aguardando</span>
<span class="inline-block w-20">OPS000</span>
<span class="inline-block w-[380px]">Título de exemplo aguardando aprovação</span>
<span class="inline-block w-36 text-amber-300">intenção: aguardando</span>
<span>P2</span>
</div>
</div>
<div class="border-t border-zinc-900 text-zinc-300">
<div class="px-4 py-1">
<span class="inline-block w-28 text-red-300">■ travada</span>
<span class="inline-block w-20">B000</span>
<span class="inline-block w-[380px]">Título de exemplo com dependência aberta</span>
<span class="inline-block w-36 text-zinc-400">depends OPS111</span>
<span>P2</span>
</div>
</div>
<div class="border-t border-zinc-900 text-zinc-300">
<div class="px-4 py-1">
<span class="inline-block w-28 text-zinc-400">○ pronta</span>
<span class="inline-block w-20">C000</span>
<span class="inline-block w-[380px]">Título de exemplo pronto para claim</span>
<span class="inline-block w-36 text-zinc-400">intenção: aprovado</span>
<span>P2</span>
</div>
</div>
<div class="border-t border-zinc-900 text-zinc-300">
<div class="px-4 py-1">
<span class="inline-block w-28 text-emerald-300">✓ concluída</span>
<span class="inline-block w-20">OPS097</span>
<span class="inline-block w-[380px]">Título de exemplo já entregue</span>
<span class="inline-block w-36 text-emerald-300">impl entregue</span>
<span>P2</span>
</div>
</div>
<div class="border-t border-zinc-900 text-zinc-300">
<div class="px-4 py-1">
<span class="inline-block w-28 text-zinc-400">○ pronta</span>
<span class="inline-block w-20">OPS000</span>
<span class="inline-block w-[380px]">Título de exemplo sem plano ligado</span>
<span class="inline-block w-36 text-zinc-500">sem plano</span>
<span>P3</span>
</div>
</div>
<div class="flex items-center gap-4 border-t border-zinc-800 px-4 py-2 text-zinc-500">
<span><span class="text-zinc-300">↑↓</span> navegar</span>
<span><span class="text-zinc-300">enter</span> abrir</span>
<span><span class="text-zinc-300">1-4</span> filtrar</span>
<span><span class="text-zinc-300">/</span> buscar</span>
<span><span class="text-zinc-300">r</span> recarregar</span>
<span><span class="text-zinc-300">q</span> sair</span>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- Cena 2 · desktop — detalhe da issue com os dois planos -->
<!-- ============================================================ -->
<section class="mt-8">
<p class="text-xs tracking-wide text-zinc-500 uppercase">
Cena 2 · desktop — detalhe da issue: resumo + planos e status
</p>
<div class="mt-3 w-full max-w-[1100px] overflow-x-auto">
<div
class="min-w-[900px] overflow-hidden rounded-lg border border-zinc-700 bg-zinc-950 font-mono text-[13px] leading-6"
>
<div class="flex items-center gap-3 border-b border-zinc-700 px-4 py-2">
<span class="text-zinc-200">OPS000 — Título de exemplo aguardando aprovação</span>
<span class="ml-auto text-amber-300">◌ aguardando</span>
</div>
<div class="border-b border-zinc-800 px-4 py-2 text-zinc-400">
Issue <span class="text-zinc-200">#0000</span> · prio
<span class="text-zinc-200">P2</span> · kind
<span class="text-zinc-200">feature</span> · depends
<span class="text-zinc-200">—</span> ·
<span class="text-sky-300 underline">github.com/fsolla/teqo/issues/0000</span>
</div>
<div class="border-b border-zinc-800 px-4 py-3 text-zinc-300">
<p class="text-zinc-500">resumo</p>
<p class="mt-1 max-w-[820px] text-zinc-300">
Duas linhas de exemplo do resumo de produto, na voz do humano que pediu o item. Sem
números reais e sem promessa que o plano não faça.
</p>
</div>
<div class="px-4 py-3">
<p class="text-zinc-500">planos</p>
<div class="mt-2 text-zinc-300">
<span class="text-emerald-300">✓ intenção</span>
<span class="mx-2 text-zinc-500">·</span>
<span>docs/plans/ops000-exemplo.md</span>
<span class="mx-2 text-zinc-500">·</span>
<span class="text-emerald-300">aprovado (gate humano 2026-09-10)</span>
<span class="mx-2 text-zinc-500">·</span>
<span class="text-sky-300">enter abre</span>
</div>
<div class="mt-1 text-zinc-300">
<span class="text-amber-300">◌ implementação</span>
<span class="mx-2 text-zinc-500">·</span>
<span>docs/plans/ops000-exemplo-impl.md</span>
<span class="mx-2 text-zinc-500">·</span>
<span class="text-amber-300">aguardando aprovação</span>
<span class="mx-2 text-zinc-500">·</span>
<span class="text-sky-300">enter abre</span>
</div>
</div>
<div class="border-t border-zinc-800 px-4 py-3">
<p class="text-zinc-500">ações</p>
<div class="mt-2 text-zinc-300">
<span class="text-sky-300">w</span>
<span class="mx-2">abrir rascunho UI no browser</span>
<span class="mx-2 text-zinc-500">·</span>
<span>docs/plans/ops000-exemplo-ui-draft.html</span>
</div>
<div class="mt-1 text-zinc-300">
<span class="text-sky-300">s</span>
<span class="mx-2">abrir sessão do agente</span>
<span class="mx-2 text-zinc-500">·</span>
<span class="text-emerald-300">run ativo</span>
<span class="mx-2 text-zinc-500">·</span>
<span>worktree do exemplo</span>
</div>
</div>
<div class="border-t border-zinc-800 px-4 py-2 text-zinc-500">
<span><span class="text-zinc-300">esc</span> voltar</span>
<span class="mx-4"><span class="text-zinc-300">enter</span> abrir plano</span>
<span class="mx-4"><span class="text-zinc-300">w</span> rascunho UI</span>
<span class="mx-4"><span class="text-zinc-300">s</span> sessão do agente</span>
<span><span class="text-zinc-300">o</span> abrir no GitHub</span>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- Cena 3 · desktop — leitor de plano (markdown renderizado) -->
<!-- ============================================================ -->
<section class="mt-8">
<p class="text-xs tracking-wide text-zinc-500 uppercase">
Cena 3 · desktop — leitor de plano: markdown renderizado no terminal
</p>
<div class="mt-3 w-full max-w-[1100px] overflow-x-auto">
<div
class="min-w-[900px] overflow-hidden rounded-lg border border-zinc-700 bg-zinc-950 font-mono text-[13px] leading-6"
>
<div class="flex items-center gap-3 border-b border-zinc-700 px-4 py-2">
<span class="text-zinc-200">docs/plans/ops000-exemplo-impl.md</span>
<span class="text-emerald-300">Status: aprovado</span>
<span class="ml-auto text-zinc-500">64% · ↑↓ rolar</span>
</div>
<div class="px-5 py-4 text-zinc-300">
<p class="text-base font-medium text-zinc-100"># Impl: OPS000 — Título de exemplo</p>
<p class="mt-3 max-w-[820px]">
Parágrafo de exemplo com <span class="font-medium text-zinc-100">negrito</span> e
alguma decisão de implementação já tomada — o leitor de terminal precisa distinguir
hierarquia sem browser.
</p>
<p class="mt-3 text-zinc-500">## Fases verificáveis</p>
<p class="mt-1">1. Tracer bullet — esqueleto navegável</p>
<p>2. UI — encaixe na tela existente</p>
<p>3. Gates — <span class="text-zinc-100">pnpm gate:fast</span>, push</p>
<p class="mt-3 text-zinc-500">## Alternativas rejeitadas</p>
<div class="mt-1">
<span class="text-zinc-500">|</span> Opção <span class="text-zinc-500">|</span> Por
que caiu <span class="text-zinc-500">|</span>
</div>
<div class="text-zinc-500">
<span>|</span>-------------<span>|</span>--------------------------------<span
>|</span
>
</div>
<div>
<span class="text-zinc-500">|</span> A <span class="text-zinc-500">|</span> exemplo
de motivo <span class="text-zinc-500">|</span>
</div>
<div>
<span class="text-zinc-500">|</span> B <span class="text-zinc-500">|</span> exemplo
de motivo <span class="text-zinc-500">|</span>
</div>
<p class="mt-3 text-zinc-500">```text</p>
<p class="bg-zinc-900 px-3 text-zinc-200">fluxo de exemplo → outcome</p>
<p class="text-zinc-500">```</p>
<p class="mt-3 border-l-2 border-zinc-600 pl-3 text-zinc-400">
&gt; citação de exemplo do plano
</p>
</div>
<div class="border-t border-zinc-800 px-4 py-2 text-zinc-500">
<span><span class="text-zinc-300">↑↓</span> rolar</span>
<span class="mx-4"><span class="text-zinc-300">pgup/pgdn</span> página</span>
<span><span class="text-zinc-300">esc</span> voltar à issue</span>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- Cena 4 · estados críticos — erro e vazio -->
<!-- ============================================================ -->
<section class="mt-8">
<p class="text-xs tracking-wide text-zinc-500 uppercase">
Cena 4 · estados críticos — sem token (erro claro), filtro vazio e sem ações
</p>
<div class="mt-3 grid max-w-[1100px] gap-4 lg:grid-cols-3">
<div class="overflow-hidden rounded-lg border border-zinc-700 bg-zinc-950">
<div class="border-b border-zinc-700 px-4 py-2 font-mono text-[13px] text-zinc-200">
teqo · issues
</div>
<div class="px-4 py-6 font-mono text-[13px] leading-6">
<p class="text-red-300">erro: GITHUB_TOKEN não definido no ambiente.</p>
<p class="mt-1 text-zinc-400">Nada foi alterado. Defina o token e rode de novo:</p>
<p class="mt-1 bg-zinc-900 px-3 text-zinc-200">GITHUB_TOKEN=… pnpm issues:tui</p>
</div>
<div class="border-t border-zinc-800 px-4 py-2 font-mono text-[13px] text-zinc-500">
<span><span class="text-zinc-300">r</span> tentar de novo</span>
<span class="mx-4"><span class="text-zinc-300">q</span> sair</span>
</div>
</div>
<div class="overflow-hidden rounded-lg border border-zinc-700 bg-zinc-950">
<div class="border-b border-zinc-700 px-4 py-2 font-mono text-[13px] text-zinc-200">
teqo · issues · aguardando
</div>
<div class="px-4 py-6 font-mono text-[13px] leading-6">
<p class="text-zinc-400">Nenhuma issue aguardando aprovação no momento.</p>
<p class="mt-1 text-zinc-500">
<span class="text-sky-300">1</span> ver todas ·
<span class="text-sky-300">2</span> em andamento ·
<span class="text-sky-300">4</span> concluídas
</p>
</div>
<div class="border-t border-zinc-800 px-4 py-2 font-mono text-[13px] text-zinc-500">
<span><span class="text-zinc-300">1-4</span> trocar filtro</span>
<span class="mx-4"><span class="text-zinc-300">q</span> sair</span>
</div>
</div>
<div class="overflow-hidden rounded-lg border border-zinc-700 bg-zinc-950">
<div class="border-b border-zinc-700 px-4 py-2 font-mono text-[13px] text-zinc-200">
teqo · issues · ações
</div>
<div class="px-4 py-6 font-mono text-[13px] leading-6">
<p class="text-zinc-400">sessão do agente: nenhuma ativa.</p>
<p class="mt-1 text-zinc-500">
O painel não inicia run — dispare pelo worktree/skill.
</p>
<p class="mt-2 text-zinc-400">rascunho UI: não tem (classe A).</p>
<p class="mt-1 text-zinc-500">Um caminho longo de exemplo é impresso no lugar.</p>
</div>
<div class="border-t border-zinc-800 px-4 py-2 font-mono text-[13px] text-zinc-500">
<span><span class="text-zinc-300">esc</span> voltar</span>
<span class="mx-4"><span class="text-zinc-300">q</span> sair</span>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- Cena 5 · celular (~390px) — terminal estreito (~46 colunas) -->
<!-- ============================================================ -->
<section class="mt-8">
<p class="text-xs tracking-wide text-zinc-500 uppercase">
Cena 5 · celular (~390px) — terminal estreito: lista e leitor
</p>
<div class="mt-3 flex flex-wrap gap-6">
<div
class="w-[390px] max-w-full overflow-hidden rounded-lg border border-zinc-700 bg-zinc-950"
>
<div class="border-b border-zinc-700 px-3 py-2 font-mono text-[12px] text-zinc-200">
teqo · issues
</div>
<div class="flex gap-3 border-b border-zinc-800 px-3 py-1 font-mono text-[12px]">
<span class="text-sky-300">todas</span>
<span class="text-zinc-500">andam.</span>
<span class="text-zinc-500">aguard.</span>
<span class="text-zinc-500">feitas</span>
</div>
<div class="bg-sky-500/15 py-1 font-mono text-[12px] leading-5 text-sky-100">
<p class="px-3"><span class="text-sky-300">● em andamento</span> · C163 · P1</p>
<p class="px-3 text-sky-200">Título de exemplo da issue em curv…</p>
<p class="px-3 text-sky-300">s abrir sessão (run ativo)</p>
</div>
<div
class="border-t border-zinc-900 py-1 font-mono text-[12px] leading-5 text-zinc-300"
>
<p class="px-3"><span class="text-amber-300">◌ aguardando</span> · OPS000 · P2</p>
<p class="px-3 text-zinc-400">Título de exemplo aguardando aprovaç…</p>
</div>
<div
class="border-t border-zinc-900 py-1 font-mono text-[12px] leading-5 text-zinc-300"
>
<p class="px-3"><span class="text-red-300">■ travada</span> · B000 · P2</p>
<p class="px-3 text-zinc-400">Título de exemplo com dependência ab…</p>
</div>
<div
class="border-t border-zinc-900 py-1 font-mono text-[12px] leading-5 text-zinc-300"
>
<p class="px-3"><span class="text-emerald-300">✓ concluída</span> · OPS097 · P2</p>
<p class="px-3 text-zinc-400">Título de exemplo já entregue</p>
</div>
<div
class="flex gap-3 border-t border-zinc-800 px-3 py-2 font-mono text-[12px] text-zinc-500"
>
<span><span class="text-zinc-300">↑↓</span> mover</span>
<span><span class="text-zinc-300">enter</span> abrir</span>
<span><span class="text-zinc-300">q</span> sair</span>
</div>
</div>
<div
class="w-[390px] max-w-full overflow-hidden rounded-lg border border-zinc-700 bg-zinc-950"
>
<div class="border-b border-zinc-700 px-3 py-2 font-mono text-[12px] leading-5">
<p class="text-zinc-200">ops000-exemplo-impl.md</p>
<p class="text-emerald-300">Status: aprovado · 64%</p>
</div>
<div class="px-3 py-3 font-mono text-[12px] leading-5 text-zinc-300">
<p class="font-medium text-zinc-100"># Impl: OPS000 —</p>
<p class="font-medium text-zinc-100">Título de exemplo</p>
<p class="mt-2">Parágrafo de exemplo com</p>
<p><span class="font-medium text-zinc-100">negrito</span> e decisão já tomada.</p>
<p class="mt-2 text-zinc-500">## Fases verificáveis</p>
<p>1. Tracer bullet — esqueleto</p>
<p>2. UI — encaixe na tela</p>
<p>3. Gates — <span class="text-zinc-100">gate:fast</span></p>
<p class="mt-2 text-zinc-500">## Alternativas</p>
<p>A · exemplo de motivo</p>
<p>B · exemplo de motivo</p>
</div>
<div class="border-t border-zinc-800 px-3 py-2 font-mono text-[12px] text-zinc-500">
<span><span class="text-zinc-300">↑↓</span> rolar</span>
<span class="mx-3"><span class="text-zinc-300">esc</span> voltar</span>
</div>
</div>
</div>
</section>
</main>
</body>
</html>

View file

@ -0,0 +1,114 @@
# OPS109 — Painel de issues e planos no terminal (via SSH)
Status: rascunho
Atualizado em: 2026-09-15
Issue: #1020
Priority: P2
Impeccable: C — fluxo novo (ferramenta de terminal; sem UI de produto)
Rascunho UI: docs/plans/ops109-painel-issues-no-terminal-ui-draft.html
Appetite: ~1–2 dias eng
Responsável: —
## Intenção
> _"Ferramenta local (TUI preferida) para visualizar o andamento de cada issue — em andamento / concluídas / aguardando —, abrir os planos de intenção e de implementação e ver se estão aprovados ou aguardando aprovação. Tem que funcionar via SSH de outro dispositivo (laptop/celular); exibir os planos (markdown) estilizados para leitura fácil."_
Hoje o andamento se lê em pedaços: `pnpm issue all` dá contadores e lista, `pnpm agent:status` dá fila e grafo, mas **nenhum** dos dois lê os planos — para saber se o plano de intenção ou o impl está aprovado é preciso abrir o arquivo no editor ou o GitHub no browser. Fora da mesa (laptop/celular via SSH), isso vira atrito: o mantenedor quer responder "o que anda, o que espera aprovação, o que travou, o que vem depois" em segundos, e ler o plano sem trocar de contexto. Prioridade **P2**: ferramenta de operação, sem incidente ou bloqueio de produto.
No gate, o humano somou duas ações à ferramenta: abrir os rascunhos UI do item no browser padrão da máquina e abrir a sessão do agente que está rodando aquela issue.
## Persona e fluxo
- **Persona / contexto:** o próprio mantenedor (fsolla), navegando de laptop/celular via SSH ou na mesa; pressa de conferir o estado antes de decidir o próximo passo.
- **Job principal:** saber em segundos o que está em andamento, o que espera aprovação, o que travou e o que a próxima Issue fará — e ler o plano sem sair do terminal.
- **Fluxo desejado:** abre o painel no terminal do dispositivo → lista de issues com estado/prioridade/kind e a ação pendente → filtra por estado (em andamento / concluídas / aguardando) → seleciona uma issue → vê o resumo e, para cada plano (intenção e impl), o link e o status (aprovado / aguardando aprovação) → abre o markdown renderizado (títulos, listas, tabelas, código, negrito) → se a issue tiver rascunho UI, abre o `.html` no browser padrão da máquina → se houver run de agente ativo para a issue, abre a sessão dele (attach) → rola e volta → sai.
- **Anti-goals de produto:** read-only sobre tracker e planos (não claima, não relabela, não edita Issue/plano; abrir no browser e anexar à sessão são navegação local, não escrita); não é web/server/browser de produto; não cria segunda fonte de verdade nem duplica `agent:status`; não inicia run novo (o abrir-sessão só anexa a um run existente); sem dashboard de vaidade — cada painel responde a uma decisão do operador.
### Esboço de fluxo (B/C/D)
```text
[SSH] → [lista: issues × estado/prio/kind/ação] → [filtro por estado]
→ [issue: resumo + planos (intenção/impl) + status] → [markdown renderizado]
→ [rascunho UI: abre no browser da máquina] → [sessão do agente: attach]
→ [rolar / voltar] → [sair]
cenários: issue sem plano · impl não criado · sem rascunho UI · sem sessão ativa ·
sem browser/display · terminal estreito · sem token/sem rede (erro claro)
```
### Rascunho UI (B/C/D)
- Rascunho UI (gate): `docs/plans/ops109-painel-issues-no-terminal-ui-draft.html` — mock do terminal com cenas estáticas (lista, detalhe com planos + ações de rascunho UI e sessão, leitor de markdown, vazio/sem token, terminal estreito).
## Objetivo e aceite
- O painel lista as issues rastreáveis com estado, prioridade e kind, mais a ação que esperam do operador (ex.: "aguardando aprovação", "em andamento", "travada").
- Filtrar por estado responde "em andamento / concluídas / aguardando" sem sair do painel.
- Issue selecionada mostra o plano de intenção e o de implementação, cada um com link e status legível de relance ("aprovado" vs "aguardando aprovação"); "sem plano" e "impl ainda não criado" se explicam sozinhos, com o link do GitHub como saída.
- Markdown do plano abre renderizado para leitura (títulos, listas, tabelas, código, negrito), com rolagem e volta.
- Issue com rascunho UI (`docs/plans/<slug>-ui-draft.html`) abre o arquivo no browser padrão da máquina; sem browser/display (SSH puro), imprime o caminho para abrir manualmente — nunca falha em silêncio. Sem rascunho, a ação se explica (é classe A / não tem UI).
- Issue com run de agente ativo mostra isso na lista e no detalhe e abre a sessão existente (attach); sem run, o estado diz "nenhuma sessão ativa" — o painel não inicia run. Depende do OPS110.
- Terminal estreito (celular) continua utilizável; token ausente ou rede fora vira mensagem clara — nunca stack trace/crash.
- Guardrail: read-only sobre tracker e planos de ponta a ponta; a fonte é a mesma camada de leitura de Issues usada por `pnpm issue`/`pnpm agent:status`.
## Dados (intenção)
- **Vou apresentar dados?** Sim, superfície neste item — o painel é a apresentação.
- **Decisões desbloqueadas:**
- Mantenedor escolhe a próxima Issue a trabalhar vendo estado + prioridade + o que vem na fila.
- Mantenedor vê o que está travado e decide o que destravar.
- Mantenedor vê se um plano (intenção ou impl) espera aprovação e decide aprovar ou não.
- Mantenedor revisa o rascunho UI da issue e decide se interfere no run em andamento — sem sair do painel.
- **Forma:** _adiada ao plano de implementação_ — restrições de produto: leitura de relance na lista, detalhe só sob seleção; sem métricas agregadas de vaidade.
## Dados da decisão (literais)
- Labels de estado da Issue: `ready`, `in-progress`, `blocked`, `done`, `in-prod`.
- Outros labels lidos: `prio:P0`, `prio:P1`, `prio:P2`, `prio:P3`, `kind:*`, `needs:*`.
- Status de plano (linha `Status:` dos `docs/plans/*.md` e `*-impl.md`): `rascunho`, `registrado`, `aprovado` (às vezes com sufixo `(gate humano <data>)`), `em execução`, `executado`, `entregue`, `blocked`.
- Link do plano no body da Issue: ``Plano: [`docs/plans/<slug>.md`](docs/plans/<slug>.md)``.
- Back-links: o plano de intenção tem `Issue: #N`; o `-impl.md` tem `Intenção: docs/plans/<slug>.md`.
- Rascunho UI é arquivo irmão do plano: `docs/plans/<slug>-ui-draft.html`.
- Sessão do agente da Issue: quem define como descobrir/endereçar é o OPS110 (dependência dura) — aqui só se consome; "nenhuma sessão ativa" é estado de primeira classe.
- Leitura de produto (assumida): `aprovado*` = aprovado; `rascunho`/`registrado`/`blocked` = aguardando; `em execução`/`executado`/`entregue` = já andou — valor cru do `Status:` sempre visível.
## Direção no codebase (hipótese)
- **Áreas prováveis:** scripts top-level (`scripts/*.mjs`) com exposição em `package.json` no padrão de `issue`/`agent:status`; leitura de Issues em `scripts/lib/github-api.mjs` (`listIssues`, exige `GITHUB_TOKEN`), parse de frontmatter/labels/grafo em `scripts/lib/agent-forgejo.mjs` (`parseFrontmatter`, `labelNames`, `issuesById`) e extração do path do plano em `scripts/lib/agent-pool-prompt.mjs` (`extractPlanPath`). Nenhuma lib TUI instalada hoje (Node 24 ESM).
- **Precedente a olhar:** `scripts/issue.mjs` (flags, `dieAgent`, saída compacta), `scripts/agent-status.mjs` (contadores/fila) e `docs/AGENT-OPS.md` (tabela de comandos e labels).
- **Risco de acoplamento:** reusar a camada de leitura existente (sem cache paralelo), read-only sobre tracker/planos, render de markdown de terminal (`react-markdown`/`remark-gfm` são de browser e não servem), sem servidor web — precisa funcionar num terminal SSH comum. Abrir o rascunho UI depende do browser da máquina (hipótese: `xdg-open`/`open`; sem display, imprimir o caminho); abrir a sessão depende do mecanismo do OPS110 (dependência dura, sem twinar).
## Dependências
- **OPS110 (dura)** — abrir a sessão do agente só existe depois que o run persistente/endereçável do OPS110 existir; até lá o painel mostra "nenhuma sessão ativa".
- Nenhuma outra.
## Fora de escopo
- Escrita no tracker/planos (claim/relabel/close/comentar/editar plano) — destino: comandos existentes.
- Iniciar run novo pelo painel — o abrir-sessão só anexa a um run existente; disparar run fica para o fluxo normal (worktree/skill).
- Servidor web, browser de produto, app mobile, `tmux` obrigatório — o terminal SSH puro é o contrato; o browser local só é chamado para exibir o rascunho `.html`.
- Notificações, watch/daemon em background, histórico/analytics — destino: outro item, se pedido.
- Gráficos de time/burndown — vaidade sem decisão nomeável.
- Substituir `pnpm issue`/`pnpm agent:status` — eles continuam.
## Rabbit holes de produto
- **"Já que lista issues, claima daqui."** Escrita puxa lock/labels e quebra o contrato read-only. **Corte neste item:** só visualizar.
- **"Já que renderiza markdown, edita o plano."** Editor concorrente é outro produto. **Corte neste item:** leitura e navegação.
- **"Expõe com ttyd/wetty no navegador do celular."** Traz servidor, porta e auth — o pedido é SSH. **Corte neste item:** roda no terminal do cliente.
- **"Cacheia local pra economizar token."** Vira segunda fonte que envelhece. **Corte neste item:** sempre a camada existente.
## Questões em aberto (produto)
- **Issue sem plano ou com impl não criado — como representar?** **Opções:** A) estado explícito "sem plano"/"impl não criado" + link do GitHub | B) omitir a seção. **Recomendação:** A — nunca célula vazia. _(assumido — validar no gate)_
- **"Aguardando aprovação" inclui `em execução`?** **Opções:** A) não, só `rascunho`/`registrado`/`blocked` | B) todo status não-`aprovado`. **Recomendação:** A. _(assumido)_
- **Onde o painel roda?** **Opções:** A) na máquina do repo (com `GITHUB_TOKEN`) acessada por SSH | B) homeserver. **Recomendação:** A — o token já vive lá e nada novo é hospedado. _(assumido)_
- **Sem browser/display (SSH puro) ao abrir o rascunho UI?** **Opções:** A) imprimir o caminho para abrir manualmente | B) só avisar que falhou. **Recomendação:** A — o caminho colado no terminal do cliente resolve. _(assumido)_
## Referências
- GitHub Issue #1020
- Rascunho UI (gate): `docs/plans/ops109-painel-issues-no-terminal-ui-draft.html`
- `docs/plans/ops110-attach-detach-runs-agent.md` — de onde vem a sessão endereçável (dependência dura)
- `scripts/lib/github-api.mjs`, `scripts/lib/agent-forgejo.mjs`, `scripts/lib/agent-pool-prompt.mjs` (`extractPlanPath`), `scripts/issue.mjs`, `scripts/agent-status.mjs` — pistas de leitura, não contrato
- `docs/AGENT-OPS.md` (comandos e labels) e `AGENTS.md` (convenções de scripts/CI)

View file

@ -0,0 +1,93 @@
# OPS110 — Acompanhar runs longos de agente por attach/detach, sem manter terminal aberto
Status: rascunho
Atualizado em: 2026-09-15
Issue: #1019
Priority: P2
Impeccable: A — N/A (ferramenta/ops; sem UI de produto)
Rascunho UI: N/A — sem UI
Appetite: ~1–2 dias eng
Responsável: —
## Intenção
Hoje cada run de agente exige presença: o opencode roda no terminal do workstation e, se o TUI fechar (ou a sessão SSH cair), o run para. O fsolla quer largar um `/work-issue` ou `/plan-issue` longo no workstation e ir embora — almoço, reunião, fim de expediente — acompanhando do laptop ou do celular, entrando e saindo da sessão quantas vezes quiser enquanto o agente trabalha.
Não é um pedido de "rodar agentes em background" solto: é o mesmo run de hoje, com o ciclo de vida desacoplado do terminal que o observa. O que os agentes fazem não muda; muda como o humano os acompanha. O painel de Issues (OPS109) também quer abrir a sessão do run daquela Issue — para isso este item precisa deixar a sessão **endereçável**, não só persistente.
## Persona e fluxo
- **Persona / contexto:** fsolla no workstation, com worktrees de agente ativos; longe dele, no laptop ou celular, por SSH ou rede confiável. Não quer vigiar em tempo real — quer poder espiar, sair e voltar.
- **Job principal:** iniciar um run longo, desconectar, e (re)entrar na sessão de outro dispositivo quando quiser, sem interromper o agente.
- **Fluxo desejado:**
1. Dispara o run do jeito que já dispara hoje (worktree + skill, ex. `/work-issue`).
2. Fecha o terminal do workstation ou perde o SSH; o agente continua trabalhando.
3. Do laptop ou celular, entra na sessão existente e vê o histórico/estado do agente; acompanha ou digita se quiser.
4. Sai de novo — quantas vezes quiser — sem matar o run.
5. Quando decidir encerrar, encerra de forma explícita, nunca por acidente ao fechar o cliente.
- **Anti-goals de produto:**
- Não vira pool/supervisor de agentes (o pool morreu na OPS65; não ressuscitar).
- Não automatiza produção nem cria caminho que burle gates humanos (PR/CI/approval).
- Não cria um segundo jeito de iniciar sessão que ignore o worktree/claim vigente.
- Não exige terminal aberto no workstation nem processo pendurado em SSH.
- Não abre porta pública na internet.
## Objetivo e aceite
- O run continua trabalhando quando nenhum cliente está olhando (nenhum terminal/SSH aberto no workstation).
- (Re)entrar na sessão de outro dispositivo retoma exatamente onde estava — mesma sessão, mesmo worktree, mesmo histórico.
- Sair do cliente nunca é o mesmo que encerrar o run; encerrar é ato explícito.
- Nada novo escutando além do loopback sem credencial: se alguma superfície passar de `127.0.0.1`, exige autenticação e só em rede confiável (SSH/tailnet) — fail-closed sem credencial.
- O worktree/branch em que o agente roda continua sendo o contrato de ambiente; não há nova forma de claim nem de sessão órfã.
- A sessão de um run é endereçável a partir da Issue/worktree (dá para saber como chegar nela sem adivinhar) — pré-requisito do "abrir a sessão do agente" do painel OPS109.
## Dados (intenção)
- **Vou apresentar dados?** Não.
- **Decisões desbloqueadas:** N/A.
- **Forma:** N/A — `Dados: N/A` — ferramenta/ops de operação de agentes; sem métrica, gráfico ou dado eleitoral/de contato envolvido.
## Dados da decisão (literais)
- Se algo escutar além do loopback, a credencial é a do próprio opencode por ambiente: `OPENCODE_SERVER_PASSWORD` (e `OPENCODE_SERVER_USERNAME`); valores reais vivem fora do repo (workstation), nunca commitados — sem credencial, a superfície responde 401.
- "Loopback" aqui é `127.0.0.1`, o default atual do modo servidor do opencode 1.18.31.
- Os nomes acima são o contrato disponível hoje; porta, host de bind, paths de log/socket e nome de serviço são hipóteses do plano de implementação, não de contrato de produto.
- O painel OPS109 consome este endereçamento (abrir a sessão da Issue); o artefato/mecanismo concreto de descoberta é decisão do plano de implementação — sem mapa paralelo.
## Direção no codebase (hipótese)
- **Áreas prováveis:** `scripts/lib/worktree.mjs` (diretiva `launch`, ~207–233) e `.agents/shell/worktree.sh` (~104–112, executa o launch); documentação em `docs/AGENT-OPS.md` e `AGENTS.md`.
- **Duas direções citáveis, sem escolha aqui:** (a) servidor nativo persistente do opencode (`opencode serve`, dono da sessão) com cliente `opencode attach <url>` de qualquer dispositivo (replay/`--continue`); ou (b) multiplexador de terminal mantendo o TUI vivo entre anexações. A forma é decisão do plano de implementação; o aceite de produto é o mesmo.
- **Precedente a olhar:** OPS106 `scripts/auto-unblock.mjs` (spawn destacado + log + flock) mostra o padrão local de processo que sobrevive ao invocador, embora não seja sessão interativa; OPS40 `scripts/dev.mjs` mostra wrapper de CLI passando flags.
- **Risco de acoplamento:** o launch atual nasce da shell function e depende do `cd`/worktree — qualquer modo destacado precisa preservar worktree/branch e não duplicar a porta de entrada; o painel OPS109 consome o mesmo endereçamento (sem mapa paralelo). Expor bind além do loopback é o risco nº 1: a workstation não tem IP tailnet documentado (só o homeserver) e o repo não tem precedente de acesso remoto a ela.
## Dependências
- Nenhuma dura. Depende de capacidades já presentes no opencode instalado (servidor + attach). O OPS109 (painel) depende deste item para abrir a sessão da Issue — o endereçamento da sessão é aceite daqui.
## Fora de escopo
- Multi-usuário ou servir agentes para terceiros.
- Web UI como produto — se útil na implementação, é meio, não aceite.
- Notificações/push de progresso, painel de frota de runs, métricas de agentes.
- Mudanças no fluxo de claim/worktree ou nos gates de PR/deploy.
## Rabbit holes de produto
- **"Já que tem servidor, vira painel de frota".** Se alguém "só completar": status de todos os runs, iniciar/parar agentes, retomar por lista. **Corte neste item:** uma sessão, um worktree, attach/detach — nada de orquestração.
- **"Expõe na tailnet e pronto".** Se alguém "só completar": bind aberto sem auth "porque a rede é confiável". **Corte neste item:** qualquer superfície além do loopback exige credencial, fail-closed.
- **"Persistência própria de sessão".** Se alguém "só completar": banco/daemon próprio guardando estado do run. **Corte neste item:** usar o que o opencode já persiste; nada de um segundo dono da sessão.
## Questões em aberto (produto)
- **Servidor nativo vs multiplexador?** **Opções:** A servidor persistente + `attach` | B multiplexador de terminal mantendo o TUI | C os dois. **Recomendação:** deixa ao impl plan decidir; produto só exige o mesmo aceite (run sobrevive sem cliente, reattach exato, encerramento explícito, auth fora do loopback). _(assumido — validar com produto)_
- **De onde se acompanha no celular?** **Opções:** A SSH/TUI no terminal do celular | B navegador (web UI) | C só laptop por enquanto. **Recomendação:** A — reaproveita a superfície TUI já validada; B adiciona escopo de UI que o aceite não exige.
## Referências
- GitHub Issue #1019
- `scripts/lib/worktree.mjs` (diretiva `launch`) e `.agents/shell/worktree.sh` (execução do launch) — de onde o run nasce hoje
- `scripts/auto-unblock.mjs` — precedente OPS106 de processo local destacado
- `docs/AGENT-OPS.md` — operação de agentes paralelos; `AGENTS.md` — convenções do repo
- `docs/ops/teqo-1313-deploy.md` — topologia de rede (homeserver × workstation)
- opencode 1.18.31 instalado: `opencode serve`, `opencode attach`, `opencode run --attach` e `opencode web` (help local)