From 7998ae5c40eb3bb8b2235149ab8be689fd0d36c5 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Wed, 16 Sep 2026 00:05:18 -0300 Subject: [PATCH] =?UTF-8?q?docs(plans):=20OPS118/OPS119=20=E2=80=94=20rela?= =?UTF-8?q?t=C3=B3rio=20em=20lote=20+=20worktree=20plan=20sem=20auto-envio?= =?UTF-8?q?=20(Related=20#1065,=20Related=20#1066)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...orio-cidade-lote-municipios-sub-agentes.md | 109 ++++++++++++++++++ ...worktree-plan-sem-auto-envio-plan-issue.md | 99 ++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 docs/plans/ops118-relatorio-cidade-lote-municipios-sub-agentes.md create mode 100644 docs/plans/ops119-worktree-plan-sem-auto-envio-plan-issue.md diff --git a/docs/plans/ops118-relatorio-cidade-lote-municipios-sub-agentes.md b/docs/plans/ops118-relatorio-cidade-lote-municipios-sub-agentes.md new file mode 100644 index 00000000..b56d57c7 --- /dev/null +++ b/docs/plans/ops118-relatorio-cidade-lote-municipios-sub-agentes.md @@ -0,0 +1,109 @@ +# OPS118 — relatorio-cidade: lote de municípios + sub-agentes por etapa + +Status: rascunho +Atualizado em: 2026-09-15 +Issue: #1065 +Priority: P2 +Impeccable: A — N/A (skill/ferramenta de agentes, sem superfície de produto) +Rascunho UI: N/A — sem UI +Appetite: ~1 dia eng; contrato de lote na skill + decomposição em sub-agentes + prova +Responsável: — + +## Intenção + +Hoje a skill `/relatorio-cidade` é estritamente single-city: o "Pipeline (3 passos)" (`.agents/skills/relatorio-cidade/SKILL.md:20-59`) usa **um** `` para tudo — resolução (`:22-25`), pesquisa web → `data/relatorios-cidade/.research.json` (`:26-28`), extração read-only → `.snapshot.json` (`:29-45`) e render local → `docs/research/relatorios-cidade/-.pdf|.md` (`:46-59`). Não há loop nem lote em lugar nenhum, e o subagente `.opencode/agent/relatorio-cidade.md` repete os 4 papéis (slug → pesquisa → ssh → build) sempre no singular. Pedir "Salvador, Ilheus, Itacare" hoje exige 3 execuções manuais. Pior: a **pesquisa web é o sugador de contexto** (SKILL `:26-28`) e roda no mesmo agente que depois carrega logs de ssh e de build — o contexto do orquestrador incha à toa. Este item entrega (a) um contrato de **lote** e (b) a quebra do fluxo em **sub-agentes por etapa**, sem tocar no produto nem no layout do PDF. + +## Persona e fluxo + +- **Persona / contexto:** quem invoca a skill (candidato/CG antes de uma agenda, ou o agente principal preparando um giro por várias cidades). +- **Job principal:** pedir N municípios numa invocação e receber N PDFs+MDs, cada um com o mesmo contrato de hoje. +- **Fluxo desejado:** `/relatorio-cidade Salvador, Ilheus, Itacare` → slugs canônicos resolvidos → cada cidade pesquisada/extraída/gerada **em paralelo** → ao fim, uma lista de saídas (PDF+MD por cidade) e um resumo curto de sucesso/falha por cidade. +- **Anti-goals de produto:** não gerar um PDF agregado único; não inflar o contexto do agente principal com research ou logs; não paralelizar o que não é seguro (checkout compartilhado); não mudar o conteúdo/layout do relatório. + +## Objetivo e aceite + +- A skill aceita **vários** municípios numa única invocação e produz, por cidade, o par `docs/research/relatorios-cidade/-.pdf|.md` — mesmo contrato de um único município hoje. +- A pesquisa web (etapa cara de contexto) roda em **sub-agente por cidade**, isolada do orquestrador; o orquestrador retém apenas um recibo curto por cidade. +- **Falha isolada:** cidade com erro não cancela as demais; o resultado final reporta explicitamente o que saiu e o que falhou (sucesso parcial visível). +- A invocação de lote é documentada de forma literal (vírgula como separador), preservando o caso single-city sem regressão. +- Guardrails da skill intactos: sem fonte não publica, empenho ≠ pagamento, leitura relativa, PII mínima, artefato gitignored. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — mudança de contrato de ferramenta de agentes. +- **Decisões desbloqueadas:** quem pede o relatório de uma agenda inteira decide com N PDFs em um passo, em vez de N invocações. +- **Forma:** _adiada ao plano de implementação_. + +## Dados da decisão (literais) + +- **Invocação de lote (proposta):** `/relatorio-cidade Salvador, Ilheus, Itacare` — lista separada por **vírgula**; cada token é resolvido a slug canônico. Single-city (`/relatorio-cidade Feira de Santana`) permanece válido. +- **Slug canônico:** `src/lib/municipalityCatalog.ts` (435 unidades; Salvador = `salvador-ze-N`). +- **Artefatos por cidade (inalterados):** `data/relatorios-cidade/.research.json`, `data/relatorios-cidade/.snapshot.json`, `docs/research/relatorios-cidade/-.pdf` + `.md`. +- **Scripts (chamados em loop por cidade):** `scripts/extract-city-report-snapshot.mjs:63-78` (`--municipality= --out=`), `scripts/cityReportSnapshot.mjs:259-266` (`composeCityReportSnapshot({payload, actor, slug, …})`), `scripts/build-city-report.mjs:49-62` (`--snapshot` + `--research`; mismatch guard `:94-98`). +- **Recibo do sub-agente por cidade:** `slug`, contagem de itens de pesquisa, `gaps`, nº de notícias ≤90 dias, flags de baixa confiança — **nunca** o corpo do `research.json`. +- **Sem índice agregado neste item:** uma saída PDF+MD por cidade, como hoje. + +## Análise de decomposição em sub-agentes + +- **Recomendado — fan-out por cidade:** + - **Orchestrator (agente principal):** parseia a lista, resolve slugs canônicos (leitura barata do catálogo), dispara os sub-agentes, coleta caminhos de saída + recibo curto, faz a validação/summary final. **Nunca** segura pesquisa web nem corpos de JSON. + - **Researcher+analyst por cidade (×N, paralelo):** o único passo pesado de contexto. Produz o `research.json` completo (fatos do checklist + blocos interpretativos `approach`, `preCandidates`, `leaders`, `leaderAgenda`, `opposition`, `alliances`, `investments`, `demography`, `economy`, `transport`) e devolve só o recibo curto. + - **Extract + build determinísticos:** comandos por cidade mantidos **fora** do researcher (não devem carregar log de ssh/build). Baixo contexto para o modelo. +- **Rejeitado/condicional — collector + analyst separados:** um sub-agente só de fatos crus e outro só de blocos interpretativos adiciona um handoff do fact-set inteiro; como o analista ingere tudo de qualquer forma, o ganho de contexto é marginal. **Só vale** se o fact-set de uma única cidade ficar gigante; recomendação: manter pesquisa+análise em **um** agente por cidade. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `.agents/skills/relatorio-cidade/SKILL.md` (contrato de lote + delegação por etapa), `.opencode/agent/relatorio-cidade.md` (subagente por cidade), possivelmente um novo subagente/role de pesquisa. +- **Precedente a olhar:** `docs/plans/relatorio-cidade-viagem.md` + `-impl.md` (C163, Issue #1007) escolheu skill + subagente (opção A, `-impl.md:152-155`), linguagem no singular ("chamar com um município", `:11`/`:105`), sem lote. +- **Risco de acoplamento:** a extração ssh roda no checkout de **scratch** `~/teqo-report` (SKILL `:29-45`) — cidades concorrentes podem colidir; a extração pode precisar de serialização ou isolamento por cidade (decisão de engenharia, mas a restrição fica registrada). O SHA do extrator e do builder deve continuar sendo o mesmo (`:45`). + +## Dependências + +- **#1025 (suave, não bloqueia):** a API de emendas não atribui emenda ao município (P2/defect) — relação frouxa, não é pré-requisito. +- **C163 / #1007 (histórico):** skill + subagente já existem; este item estende, não recria. + +## Fora de escopo + +- PDF agregado/índice de lote (recomendado **não** fazer aqui). +- Mudar conteúdo, seções ou layout do relatório (seguem como estão). +- Atribuição de emenda ao município (#1025). +- Escrever na base de produção ou commitar artefatos (gitignored em `.gitignore:83-84`). + +## Rabbit holes de produto + +- **"Já que é lote, gera um sumário estadual."** Vira novo produto (agregação, ranking, mapa) fora do appetite. **Corte neste item:** uma saída por cidade; sumário só na resposta do orquestrador. +- **"Divide tudo em muitos sub-agentes."** Cada handoff carrega contexto e some latência. **Corte neste item:** um researcher por cidade; extract/build fora dele. +- **"Paraleliza o ssh também."** Colisão no `~/teqo-report` compartilhado. **Corte neste item:** paralelismo de pesquisa é seguro; extração pode ser serializada. + +## Questões em aberto (produto) + +- **Sintaxe do lote?** **Opções:** A) vírgula (`,`) como separador canônico; B) ponto-e-vírgula; C) espaço livre. **Recomendação:** A — vírgula é legível, não colide com nomes compostos ("Feira de Santana", "Salvador, Ilheus") e dispensa parsing ambíguo. _(assumido — validar no gate)_ +- **Índice agregado do lote?** **Opções:** A) não ter; B) um `.md` de índice com links por cidade. **Recomendação:** A — fora do appetite; o resumo na resposta do orquestrador cobre a conferência. _(assumido — validar no gate)_ + +## Fases (ordem de entrega) + +1. **F1 — Contrato de lote:** documentar o parsing da lista + resolução de slugs em `.agents/skills/relatorio-cidade/SKILL.md`, com o caso single-city preservado. +2. **F2 — Decomposição:** separar pesquisa (sub-agente por cidade) de extract/build no contrato da skill e no `.opencode/agent/relatorio-cidade.md`; definir o recibo curto. +3. **F3 — Prova:** rodar um lote pequeno (2–3 municípios) com 1 cidade falhando de propósito; conferir saídas por cidade e falha isolada; `pnpm format:check`. + +## Verificação + +- Um lote de 2–3 municípios produz um par PDF+MD por cidade, com nomes `-`. +- Cidade inválida/sem fonte falha isolada; as demais entregam (sucesso parcial reportado). +- Recibo do sub-agente não contém corpo de `research.json`; orquestrador não carrega pesquisa web. +- Single-city segue funcionando sem regressão; guards da skill intactos. + +## Riscos + +- **Colisão do checkout compartilhado (`~/teqo-report`).** Mitigação: serializar a extração por cidade ou isolar o checkout. +- **Mismatch snapshot × research.** O guard do builder (`scripts/build-city-report.mjs:94-98`) continua fail-closed — nunca casar JSON à mão. +- **Regressão do single-city.** Mitigação: manter o caminho atual como caso de lote com N=1 e cobrir na prova. +- **Contexto vazando para o orquestrador.** Mitigação: recibo curto por contrato; revisão da skill na verificação. + +## Referências + +- GitHub Issue #1065 · relação suave: #1025 +- `.agents/skills/relatorio-cidade/SKILL.md:20-59,210-215` · `.opencode/agent/relatorio-cidade.md` +- `scripts/extract-city-report-snapshot.mjs:63-78` · `scripts/cityReportSnapshot.mjs:259-266` · `scripts/build-city-report.mjs:49-62,94-98,116-120` +- `src/lib/municipalityCatalog.ts` — catálogo de 435 unidades; `docs/plans/relatorio-cidade-viagem.md` + `-impl.md:152-155` +- `docs/ops/teqo-1313-deploy.md` §C163 · `.gitignore:83-84` · `AGENTS.md` diff --git a/docs/plans/ops119-worktree-plan-sem-auto-envio-plan-issue.md b/docs/plans/ops119-worktree-plan-sem-auto-envio-plan-issue.md new file mode 100644 index 00000000..870888c2 --- /dev/null +++ b/docs/plans/ops119-worktree-plan-sem-auto-envio-plan-issue.md @@ -0,0 +1,99 @@ +# OPS119 — worktree plan: abrir sem auto-enviar o `/plan-issue` (mensagem opcional na linha) + +Status: rascunho +Atualizado em: 2026-09-15 +Issue: #1066 +Priority: P2 +Impeccable: A — N/A (CLI/shell, sem superfície de produto) +Rascunho UI: N/A — sem UI +Appetite: ~0,5 dia eng; mudança de comportamento do launch + testes + textos +Responsável: — + +## Intenção + +Desde a OPS110 o launch do terminal delega ao `scripts/agent-session.mjs` e o driver destacado **auto-submete** `/plan-issue` na sessão compartilhada. Isso veio do OPS31 (Issue #593), que escolheu auto-envio por simetria com o `next`. Na prática o planejamento quase sempre quer contexto na primeira mensagem — e hoje ele não cabe: abrir já enviado engole a chance de escrever o pedido. Queremos que `worktree plan` (sem argumento) abra a sessão **sem mensagem** (o humano digita `/plan-issue`), e que `worktree plan ""` continue abrindo já enviado com `/plan-issue ` — o texto vira também o rótulo do bag/branch, espelhando o `fix`. Isto reverte parte do OPS31 de propósito, com evidência de uso. + +## Persona e fluxo + +- **Persona / contexto:** o humano (dono do repo) no terminal, abrindo um worktree para planejar em `/plan-issue`, com o contexto da ideia na cabeça. +- **Job principal:** abrir o worktree de planejamento e conseguir descrever o que quer antes de a skill rodar. +- **Fluxo desejado:** + 1. `worktree plan` → cria o worktree, provisiona o ambiente, `cd` e abre a sessão **sem nenhuma mensagem enviada**; o humano digita `/plan-issue` com o contexto que quiser. + 2. `worktree plan "revisa o gate do designer"` → mesma abertura, mas o driver auto-submete `/plan-issue revisa o gate do designer`; o texto também batiza o branch (`plans/plan-issue-revisa-o-gate-do-designer`). +- **Anti-goals de produto:** não virar mecanismo de prefill-draft do opencode; não mudar `next`/`new`/`fix`, `--stay` nem o comando `/worktree`. + +## Objetivo e aceite + +- `worktree plan` sem argumento abre a sessão persistente **sem driver** (nenhuma mensagem auto-submetida) — mesmo contrato observável do `new`. +- `worktree plan ""` auto-submete `/plan-issue ` (uma única mensagem) e usa a mesma mensagem como bag do branch — simétrico ao `fix `. +- `next` continua com `/work-issue --issue `; `new` continua driverless; `fix ` continua com `/bug-fix `; `--stay` e o comando `/worktree` do opencode ficam intactos. +- A mensagem passa pela mesma sanitização do `fix` e um bag vazio não gera driver. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — mudança de comportamento de CLI/shell, sem superfície de dados. +- **Decisões desbloqueadas:** o humano decide o texto de abertura do planejamento; o executor confere bag, branch e prompt num só lugar (como no `fix`). +- **Forma:** _adiada ao plano de implementação_. + +## Dados da decisão (literais) + +- **`SESSION_COMMAND_BY_PURPOSE`** (`scripts/lib/agent-session.mjs:169-174`): `plan: 'plan-issue'` permanece como nome da skill; o que muda é **quando** a invocação existe (abaixo). +- **`purposeInvocation`** (`scripts/lib/agent-session.mjs:184-199`): ganha o ramo do `plan` — sem argumento → `null` (sessão sem driver, como `new`); com argumento → `{ command: 'plan-issue', arguments: }`, com a **mesma** sanitização do `fix` (`:195`, `replace(/["\\]/g, '')` + `trim`). +- **`opencodeLaunchDirective`** (`scripts/lib/worktree.mjs:202-232`): a emissão de `--argument=""` deixa de ser só do `fix` (`:227-230`) e passa a valer para `fix` **e** `plan` (condição `purpose === 'fix' || purpose === 'plan'`). +- **`cmdPlan`** (`scripts/worktree.mjs:755-764`): passa `argument: bag` ao `cmdNamespaceBranch`, como o `cmdFix` já faz (`:810`). O runner (`:675-743`, print em `:740`) e o `driverArgs` (`:287-313`, apêndice só se `arguments` for truthy, `:311`) **não mudam**. +- **Posicional intacto:** `plan` segue lendo só `positional[1]` (`scripts/worktree.mjs:1038`) e o bag continua virando slug via `planBranchName` (`scripts/lib/worktree.mjs:277-278`) — agora acumulando a função de mensagem, como o `fix`. +- **Testes a repin:** `tests/unit/agentSession.unit.spec.ts:218-224` (plan sem msg → `null`; plan com msg → `{ command:'plan-issue', arguments:'' }`); `tests/unit/worktree.unit.spec.ts:190-209` (diretiva do plan sem arg inalterada) e `:255-266` (hoje "argument é só do fix" → passa a: `plan` carrega `--argument`, `new` segue ignorando). +- **Textos a atualizar:** uso do `plan` em `scripts/worktree.mjs:980-992`, `.agents/shell/worktree.sh:9-31,43-49,102-118`, `.agents/skills/worktree-next-issue/SKILL.md:23,30,34`, parágrafo de worktrees do `AGENTS.md` e uma entrada em `docs/changelog/`. +- **Supersede:** `docs/plans/worktree-plan-abre-opencode-com-plan-issue.md` (OPS31/#593) — auto-envio deixa de ser o default sem argumento; re-flagar `~/Code/propositions/opencode/prefill-prompt-sem-submit.md`. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `scripts/lib/agent-session.mjs` (mapa + `purposeInvocation`), `scripts/lib/worktree.mjs` (diretiva + `cmdPlan`) e os textos em `scripts/worktree.mjs`/`.agents/shell/worktree.sh`/`.agents/skills/worktree-next-issue/SKILL.md`. +- **Precedente a olhar:** o `fix` — o caminho bag→`--argument`→driver já existe e é o espelho exato do que o `plan` passa a fazer; OPS110 (delegação das sessões). +- **Risco de acoplamento:** abrir sem mensagem é a mesma sessão endereçável da OPS110, apenas sem `driverPid`; não tocar em `kill`/`attach` (encerram a sessão driverless normalmente). + +## Dependências + +- **Nenhuma dura.** O opencode instalado (1.18.31) **não tem** prefill-sem-submit (`--prompt` auto-envia; não há flag de draft), por isso o fallback "abrir sem mensagem"; a proposição upstream é dependência futura, não bloqueio. + +## Fora de escopo + +- Implementar prefill-sem-submit no opencode (upstream; anotar na proposição). +- Mudar `next`/`new`/`fix`, `--stay` e o comando `/worktree` do opencode. +- Qualquer mudança em claim, banco ou provisionamento. + +## Rabbit holes de produto + +- **"Já que estamos mexendo, deixa o plan com subcomandos/prompt rico."** Explosão de sintaxe de CLI. **Corte neste item:** só o par sem-arg/arg, espelhando o `fix`. +- **"Abre com prefill e espera o upstream."** Bloqueia a entrega por uma feature que o opencode não tem. **Corte:** fallback driverless agora; prefill re-flagado. + +## Questões em aberto (produto) + +- **O fallback "abrir sem mensagem" basta, ou vale perseguir prefill-sem-submit no upstream?** **Opções:** A) entregar o fallback agora e re-flagar a proposição; B) bloquear este item até o opencode ganhar prefill. **Recomendação:** A — a fricção real (digitar) é baixa e o auto-envio continua via `worktree plan ""`; prefill fica como dependência upstream futura. _(assumido — validar no gate)_ +- **O bag continua sendo rótulo de branch e, agora, também mensagem?** **Opções:** A) sim, idêntico ao `fix` (um texto faz as duas coisas); B) separar bag de mensagem (nova flag). **Recomendação:** A — preserva a sintaxe `[bag]` e o precedente do `fix`; separar criaria um segundo vocabulário. _(assumido — validar no gate)_ + +## Fases (ordem de entrega) + +1. **F1 — Sessão (owner):** `SESSION_COMMAND_BY_PURPOSE`/`purposeInvocation` para o par do `plan` (sem msg → `null`; com msg → `/plan-issue `). +2. **F2 — Launch:** `opencodeLaunchDirective` emite `--argument` para `plan`; `cmdPlan` passa o bag. +3. **F3 — Testes + textos:** repin dos dois specs, atualização da doc de uso e entrada de changelog. + +## Verificação + +- Unit verde nos dois specs repinados; walkthrough manual: `worktree plan` abre sem mensagem e `worktree plan "mensagem"` abre com `/plan-issue mensagem` e branch `plans/plan-issue-mensagem`. +- Não-regressão: comparar as linhas de diretiva de `next`/`new`/`fix` com as de hoje. + +## Riscos + +- **Divergência entre bag (slug) e mensagem (texto sanitizado).** Mitigação: reusar a sanitização do `fix` na diretiva e o `slugify` existente no branch. +- **Regressão no `new`/`fix` ao mexer na condição da diretiva.** Mitigação: teste travando `new` ignorar argumento e `fix` manter o comportamento. +- **Texto/doc dizendo "plan auto-submete `/plan-issue`".** Mitigação: varredura por `/plan-issue` nos textos listados antes do commit. + +## Referências + +- GitHub Issue #1066 +- Rascunho UI (gate): N/A +- `scripts/lib/agent-session.mjs` · `scripts/lib/worktree.mjs` · `scripts/worktree.mjs` — código a editar primeiro +- `docs/plans/worktree-plan-abre-opencode-com-plan-issue.md` (OPS31/#593 — decisão superada) +- `~/Code/propositions/opencode/prefill-prompt-sem-submit.md` (re-flagar) +- `AGENTS.md` — parágrafo de worktrees / sessões persistentes