diff --git a/.agents/shell/worktree.sh b/.agents/shell/worktree.sh index 3dc2e7f3..65ac90fb 100644 --- a/.agents/shell/worktree.sh +++ b/.agents/shell/worktree.sh @@ -7,7 +7,7 @@ # shell que o chamou. # # No terminal (esta função), o script também imprime a diretiva `launch -# opencode --model deepseek/deepseek-v4-flash --auto [--prompt "…"]` (OPS26): +# opencode --model vercel/deepseek/deepseek-v4-flash --auto [--prompt "…"]` (OPS26): # a função executa o cd e então a linha (tokenizada por xargs — honra as aspas # do prompt, nunca eval), e o TUI do opencode abre no worktree — `next` com # `/work-issue --issue ` já enviado (OPS33: a Issue claimada vai no prompt), diff --git a/.agents/skills/worktree-next-issue/SKILL.md b/.agents/skills/worktree-next-issue/SKILL.md index 8d954c3a..b6923b86 100644 --- a/.agents/skills/worktree-next-issue/SKILL.md +++ b/.agents/skills/worktree-next-issue/SKILL.md @@ -31,7 +31,7 @@ No opencode, isso é o comando **`/worktree next`** / **`/worktree kill`** (`.op **Default-go:** `next`, `plan` e `new` imprimem `cd ` na última linha **por padrão**; `--stay` suprime; `--go` explícito continua aceito como no-op. No terminal interativo, use a função `worktree()` de **`.agents/shell/worktree.sh`** (uma linha de `source` no profile) para o `cd` ser aplicado de verdade no shell que te chamou — node não consegue mudar o cwd do shell pai. `kill` também imprime `cd
` no fim (a sessão nunca fica num diretório destruído). -**Launch do opencode (OPS26 + OPS33 + OPS31):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new` imprimem também a diretiva `launch opencode --model deepseek/deepseek-v4-flash --auto [--prompt "/work-issue --issue "]` **antes** do `cd`, e a função a executa depois do cd — `next` abre o TUI com `/work-issue --issue ` já enviado (a Issue claimada vai no prompt; a skill lê o resto do Forgejo); `plan` abre com `/plan-issue` já enviado (OPS31: o TUI já cai no fluxo de planejamento); `new` abre sem `--prompt` ("apenas conversar"). Sem `exec`: sair do opencode volta ao shell no worktree. Presets são constantes em `scripts/lib/worktree.mjs`. **O comando `/worktree` do opencode (e qualquer automação sem o marcador) nunca lança o TUI.** +**Launch do opencode (OPS26 + OPS33 + OPS31):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new` imprimem também a diretiva `launch opencode --model vercel/deepseek/deepseek-v4-flash --auto [--prompt "/work-issue --issue "]` **antes** do `cd`, e a função a executa depois do cd — `next` abre o TUI com `/work-issue --issue ` já enviado (a Issue claimada vai no prompt; a skill lê o resto do Forgejo); `plan` abre com `/plan-issue` já enviado (OPS31: o TUI já cai no fluxo de planejamento); `new` abre sem `--prompt` ("apenas conversar"). O preset roteia pelo **Vercel AI Gateway** (OPS78: rota com cache/estabilidade/failover; variantes low/high/max do model override em `opencode.json`, selecionáveis no TUI e sticky por model). Sem `exec`: sair do opencode volta ao shell no worktree. Presets são constantes em `scripts/lib/worktree.mjs`. **O comando `/worktree` do opencode (e qualquer automação sem o marcador) nunca lança o TUI.** ## Fluxo quando invocado como skill (agentes que não têm opencode) diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index ba44cddc..9c4bb8de 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,12 +4,18 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- +**OPS73-FOLLOWUP — re-home do S10 (pixel Meta) no GitHub main, origin do workspace homeserver + race do seedUser no e2e full (2026-08-23):** fechamento dos débitos colhidos no OPS73 (#760). Das três fases planejadas, **duas já estavam resolvidas** por eventos posteriores ao plano de intenção (escrito 2026-08-20): (1) o **S10 não precisou de cherry-pick** — o merge de convergência OPS75 (`c723d6ac`, GitHub como fonte única, 2026-08-21) já trouxe o pixel Meta completo para o GitHub main (os 11 arquivos da feature são byte-idênticos à ponta Forgejo `504f6c37`; migração `20260819_213947_add_site_settings_facebook_pixel_id`, e2e `campaignHomePixel`, unit `campaignNewsletterLead`, changelog/plano todos em main); (2) o **origin do workspace do homeserver** (`~/teqo-deploy`) já era `https://github.com/fsolla/teqo.git` — verificado via SSH, e o `deploy-homeserver.sh` re-aponta idempotentemente a cada deploy. O **código real da entrega** foi o resíduo da F3: a race do usuário compartilhado `dev@payloadcms.com` no e2e full estava **parcialmente fixa** no main (o simplify do S10 trouxe `seedTestUser` create-if-missing + helper `adminHeaders` com `pg_advisory_lock(727001)`), mas restavam 3 pontos de risco: o `afterAll` de `admin.e2e.spec.ts` ainda **deletava** o usuário compartilhado no meio da suíte paralela (`cleanupTestUser`), e dois specs roteavam logins REST diretos **sem o advisory lock** (`admin.e2e.spec.ts` ×2 e a função inline `adminHeaders` sem lock em `frontend.e2e.spec.ts`). A entrega removeu o `cleanupTestUser` (chamada + helper morto, zero call sites) e roteou todos os logins restantes pelo helper com lock — um único ponto de serialização para a suite inteira. Deploy do main (que inclui o S10) via `deploy.yml` disparado manualmente: o `verify` full **flakou** com 3 reds + 5 flaky pré-existentes **não relacionados** ao seedUser (timeouts de `locator.check` em `campaignMunicipalities:115`, `campaignPermissionProfile` C142, etc. — classe consolidada na casa de flakes #553), então o deploy precisa de re-dispatch pós-merge. Dívida capturada: **#805** (`E2E-ADMIN-LOGIN-LOCK`, depends #760) — o `beforeEach` UI login do admin (page-based, form) continua sem o lock (resíduo da própria classe que o #760 ataca); correção inline de docs drift em `AGENTS-infra.md` (tracker já migrado para GitHub no OPS76). + +**OPS78 — default de launch do worktree opencode → DeepSeek V4 Flash max via Vercel AI Gateway (2026-08-23):** o launch de `pnpm worktree next/plan/new` abria o opencode com o preset `deepseek/deepseek-v4-flash` (rota direta ao provedor). A política mudou: a sessão de launch deve rotear pelo **Vercel AI Gateway** (cache/estabilidade/failover) e expor a variante de raciocínio mais forte (`max`). Mudança config-only (mecanismo OPS26 "trocar modelo = editar a constante"): `OPENCODE_PRESET_MODEL` em `scripts/lib/worktree.mjs` → `vercel/deepseek/deepseek-v4-flash`; o repo `opencode.json` ganhou o override `provider.vercel.models["deepseek/deepseek-v4-flash"].variants` com low/high/max (cada uma com `reasoningEffort`) — o catálogo built-in do opencode não expõe as variantes para esse modelo, e a sobrescreve as adiciona sem quebrar o routing (fix validado ao vivo: `opencode debug config` + `opencode run --model vercel/deepseek/deepseek-v4-flash --variant max`). Fato verificado no source do opencode 1.18: o TUI não tem flag `--variant` (só `opencode run`) e `--model` é estritamente `provider/model`, então a variante é selecionável no TUI e sticky por model (state do opencode) — a verificação do aceite é a linha de launch emitida pelo preset. 8 literais + pin da constante em `tests/unit/worktree.unit.spec.ts` atualizados; textos em `scripts/worktree.mjs` (docblock/help), `.agents/shell/worktree.sh` e `worktree-next-issue/SKILL.md` sincronizados. Anti-goals preservados: cardápio de execução de Issues intocado (`work-issue.md`/`plan-issue.md` + pin `opencodeCommands.unit.spec.ts` seguem com o provider direto), config global do humano intocada, docs históricos congelados. Verificação: `pnpm gate:fast` verde; `gate:push` na entrega; PR em main com auto-merge. + **OPS47 — agent-pool/SKILL.md aponta para plano com política pré-main obsoleta (--base stage + auto-merge) (2026-08-23):** docs-only, zero mudança de comportamento. O `.agents/skills/agent-pool/SKILL.md:18` cita `docs/plans/agent-pool-orchestrator.md` como referência viva de arquitetura ("Arquitetura e elegibilidade"), mas o plano (escrito 2026-07-30, era pré-main) ainda descrevia a política obsoleta: PR `--base stage`, `issue-done-on-stage-merge.yml`, promote `stage→main`, `refresh-stage` — realidade removida em 2026-08-01 (promote/refresh) e 2026-08-12 (migration-lock). **Edição linha-a-linha do plano (dono do concern), frame histórico do pool dormente (OPS65) preservado:** overview e resumo executivo passam a "workers PR direto para main" com nota do promote removido; diagrama mermaid `pull_request closed em stage`→`em main` e `merge em main → issue-done-on-main-merge.yml`; contrato de prompt do worker `gh pr create --base stage`→`--base main` (reflete o vivo: `POOL_BASE_REF='main'` em `scripts/agent-pool.mjs:70` e prompt `--base main` em `scripts/lib/agent-pool-prompt.mjs:40-41`); gargalo de migrations reescrito de "job `migration-lock`" para `countOpenSchemaPrs` (`scripts/lib/agent-pool-forgejo.mjs:83`, com nota da remoção do job em 2026-08-12); referências à skill deletada `model-selection` → `scripts/lib/agent-pool-models.mjs`; `scripts/lib/agent-github.mjs` (inexistente) → `agent-forgejo.mjs`; `.github/workflows/agent-pool.yml` e `gh workflow run agent-pool.yml` marcados como removidos no OPS65 (dispatch falha 404); `startingRef: 'stage'`→`POOL_BASE_REF` (=`'main'`); D7/D8/D1 e rabbit holes alinhados (sem `refresh-stage`, sem "slot ocupado até merge em stage"). **SKILL.md:** resíduo L79 "Antes do promote, use dispatch na branch" → nota histórica (promote removido 2026-08-01, workflow removido OPS65). **Touch-ups de comentário/label (sem mudança de lógica):** `tests/unit/agentPoolState.unit.spec.ts:103` "CI flipped on stage merge"→"on main merge"; `scripts/lib/agent-pool-state.mjs:131` "merge in stage"→"merge in main". Fora de escopo (órfão/histórico): `docs/plans/paradigma-agentes-paralelos.md`; código do pool e workflows (dormentes, não tocados). Impl plan: `docs/plans/agent-pool-skill-md-aponta-para-plano-com-politica-pre-impl.md`. +**OPS46 — infra de teste e2e: coleta local consertada (next/cache) + allocator de município sem colisão no wrap (2026-08-23):** entrega de dois débitos do OPS45 (capture-review-debts). **Fase 1 — e2e local não coleta:** `pnpm exec playwright test --list` falhava em todo spec com `Cannot find module 'node_modules/next/cache'` (e o diagnóstico correto do CI-vs-máquina: não há delta — o CI só roda pelo script `pnpm test:e2e`, que carrega `--conditions=react-server --import=tsx/esm` no `NODE_OPTIONS`; comando bare não tinha nada). Causa: `next@15` não declara `exports` → ESM nativo não resolve `next/cache` (o arquivo é `cache.js`), e `server-only` precisa da condição `react-server`. Fix no runner, zero mudança em produção: hook de resolução ESM vitest-free `tests/helpers/e2eEsmResolve.mjs` (mapeia `next/cache`→`next/cache.js` e injeta `react-server` nas conditions) registrada no processo main via `module.register` e propagada aos workers via `NODE_OPTIONS=--import=e2eEsmLoader.mjs` no `playwright.config.ts` — só quando os flags do script já não estão presentes. Aceite: bare `playwright test --list` coleta 198 testes em 41 arquivos (antes: 0); runs reais bare admin 5/5 e campaign 18/18; `pnpm test:e2e` intocado. **Fase 2 — allocator de município com wrap destrutivo:** a sequência persistente `campaign_fixture_municipality_alloc` nunca é resetada e um ciclo completo de suíte claima ~505 do catálogo de 435 (medido: 432 no int + 73 no e2e) → o wrap acontece em todo ciclo em DB persistente de worktree, e dois runs concorrentes podiam receber o mesmo índice e purgar as linhas vivas um do outro (purge-on-claim). Fix: registry de claims em módulo compartilhado vitest-free `tests/helpers/campaignMunicipalityAllocator.ts` — tabela `campaign_fixture_municipality_claims` (`index` PK, `run_id`, `claimed_at`), claim via `INSERT ON CONFLICT DO NOTHING RETURNING` (conflito = slot vivo → pula; claim stale >2h de run crashado → rouba; tudo ocupado → erro claro de exaustão), release no `cleanup()` dos dois fixtures (int + e2e, só quando claimaram). Guard estático novo no `testMunicipalityAllocatorConventions` (proíbe `nextval`/sequence fora do módulo) + spec int de stress `campaignMunicipalityAllocator.int.spec.ts` (exclusividade viva por 3 wraps, exaustão+recuperação, steal, release). Verificação: tsc/lint/format/cycles verdes (knip: limitação P3 pré-existente de não carregar payload.config, igual no HEAD), unit 2420 + int 819 (92 arquivos) verdes com tabela de claims zerada ao final, e2e local afetado (admin 5/5 + campaign 18/18 bare) verdes; importMap.js restaurado com envs `S3_*` dummy após o `--list` bare regenerá-lo sem elas (classe OPS69 — o guard do #87 cobre no CI). Impl plan: `docs/plans/ops46-infra-teste-e2e-local-allocator-impl.md`. + **OPS46-S1 — hook ESM mapeia next/cache incondicionalmente (2026-08-23):** débito da revisão estrutural do OPS46 (issue #789): `tests/helpers/e2eEsmResolve.mjs` remapeava `next/cache` → `next/cache.js` sem tentar a resolução bare — quando o next publicar um `exports` map, o remap podia mascarar erro de resolução real (fail-loud vira fail-silent). Fix no branch `next/cache`: `await nextResolve(specifier, context)` primeiro; só em `ERR_MODULE_NOT_FOUND` remapeia para `next/cache.js`; qualquer outro erro propaga. Branch `react-server` intocado (injeção de conditions não mascara erro). Spec unit novo `tests/unit/e2eEsmResolve.unit.spec.ts` (3 casos com `nextResolve` mockado: bare-ok sem remap, `ERR_MODULE_NOT_FOUND` remapeia com `context` intacto, erro de outro código propaga). Verificação: `gate:fast` verde (unit 2432), sanity OPS72: `playwright test --list` coleta 202 testes com o hook carregado. Gatilho de reavaliação: bump major do next (fallback vira código morto e pode ser removido). Impl plan: `docs/plans/ops46-s1-hook-esm-bare-first-impl.md`. -**OPS73-FOLLOWUP — re-home do S10 (pixel Meta) no GitHub main, origin do workspace homeserver + race do seedUser no e2e full (2026-08-23):** fechamento dos débitos colhidos no OPS73 (#760). Das três fases planejadas, **duas já estavam resolvidas** por eventos posteriores ao plano de intenção (escrito 2026-08-20): (1) o **S10 não precisou de cherry-pick** — o merge de convergência OPS75 (`c723d6ac`, GitHub como fonte única, 2026-08-21) já trouxe o pixel Meta completo para o GitHub main (os 11 arquivos da feature são byte-idênticos à ponta Forgejo `504f6c37`; migração `20260819_213947_add_site_settings_facebook_pixel_id`, e2e `campaignHomePixel`, unit `campaignNewsletterLead`, changelog/plano todos em main); (2) o **origin do workspace do homeserver** (`~/teqo-deploy`) já era `https://github.com/fsolla/teqo.git` — verificado via SSH, e o `deploy-homeserver.sh` re-aponta idempotentemente a cada deploy. O **código real da entrega** foi o resíduo da F3: a race do usuário compartilhado `dev@payloadcms.com` no e2e full estava **parcialmente fixa** no main (o simplify do S10 trouxe `seedTestUser` create-if-missing + helper `adminHeaders` com `pg_advisory_lock(727001)`), mas restavam 3 pontos de risco: o `afterAll` de `admin.e2e.spec.ts` ainda **deletava** o usuário compartilhado no meio da suíte paralela (`cleanupTestUser`), e dois specs roteavam logins REST diretos **sem o advisory lock** (`admin.e2e.spec.ts` ×2 e a função inline `adminHeaders` sem lock em `frontend.e2e.spec.ts`). A entrega removeu o `cleanupTestUser` (chamada + helper morto, zero call sites) e roteou todos os logins restantes pelo helper com lock — um único ponto de serialização para a suite inteira. Deploy do main (que inclui o S10) via `deploy.yml` disparado manualmente: o `verify` full **flakou** com 3 reds + 5 flaky pré-existentes **não relacionados** ao seedUser (timeouts de `locator.check` em `campaignMunicipalities:115`, `campaignPermissionProfile` C142, etc. — classe consolidada na casa de flakes #553), então o deploy precisa de re-dispatch pós-merge. Dívida capturada: **#805** (`E2E-ADMIN-LOGIN-LOCK`, depends #760) — o `beforeEach` UI login do admin (page-based, form) continua sem o lock (resíduo da própria classe que o #760 ataca); correção inline de docs drift em `AGENTS-infra.md` (tracker já migrado para GitHub no OPS76). - +**OPS47 — agent-pool/SKILL.md aponta para plano com política pré-main obsoleta (--base stage + auto-merge) (2026-08-23):** docs-only, zero mudança de comportamento. O `.agents/skills/agent-pool/SKILL.md:18` cita `docs/plans/agent-pool-orchestrator.md` como referência viva de arquitetura ("Arquitetura e elegibilidade"), mas o plano (escrito 2026-07-30, era pré-main) ainda descrevia a política obsoleta: PR `--base stage`, `issue-done-on-stage-merge.yml`, promote `stage→main`, `refresh-stage` — realidade removida em 2026-08-01 (promote/refresh) e 2026-08-12 (migration-lock). **Edição linha-a-linha do plano (dono do concern), frame histórico do pool dormente (OPS65) preservado:** overview e resumo executivo passam a "workers PR direto para main" com nota do promote removido; diagrama mermaid `pull_request closed em stage`→`em main` e `merge em main → issue-done-on-main-merge.yml`; contrato de prompt do worker `gh pr create --base stage`→`--base main` (reflete o vivo: `POOL_BASE_REF='main'` em `scripts/agent-pool.mjs:70` e prompt `--base main` em `scripts/lib/agent-pool-prompt.mjs:40-41`); gargalo de migrations reescrito de "job `migration-lock`" para `countOpenSchemaPrs` (`scripts/lib/agent-pool-forgejo.mjs:83`, com nota da remoção do job em 2026-08-12); referências à skill deletada `model-selection` → `scripts/lib/agent-pool-models.mjs`; `scripts/lib/agent-github.mjs` (inexistente) → `agent-forgejo.mjs`; `.github/workflows/agent-pool.yml` e `gh workflow run agent-pool.yml` marcados como removidos no OPS65 (dispatch falha 404); `startingRef: 'stage'`→`POOL_BASE_REF` (=`'main'`); D7/D8/D1 e rabbit holes alinhados (sem `refresh-stage`, sem "slot ocupado até merge em stage"). **SKILL.md:** resíduo L79 "Antes do promote, use dispatch na branch" → nota histórica (promote removido 2026-08-01, workflow removido OPS65). **Touch-ups de comentário/label (sem mudança de lógica):** `tests/unit/agentPoolState.unit.spec.ts:103` "CI flipped on stage merge"→"on main merge"; `scripts/lib/agent-pool-state.mjs:131` "merge in stage"→"merge in main". Fora de escopo (órfão/histórico): `docs/plans/paradigma-agentes-paralelos.md`; código do pool e workflows (dormentes, não tocados). Impl plan: `docs/plans/agent-pool-skill-md-aponta-para-plano-com-politica-pre-impl.md`. +**OPS78 — default de launch do worktree opencode → DeepSeek V4 Flash max via Vercel AI Gateway (2026-08-23):** o launch de `pnpm worktree next/plan/new` abria o opencode com o preset `deepseek/deepseek-v4-flash` (rota direta ao provedor). A política mudou: a sessão de launch deve rotear pelo **Vercel AI Gateway** (cache/estabilidade/failover) e expor a variante de raciocínio mais forte (`max`). Mudança config-only (mecanismo OPS26 "trocar modelo = editar a constante"): `OPENCODE_PRESET_MODEL` em `scripts/lib/worktree.mjs` → `vercel/deepseek/deepseek-v4-flash`; o repo `opencode.json` ganhou o override `provider.vercel.models["deepseek/deepseek-v4-flash"].variants` com low/high/max (cada uma com `reasoningEffort`) — o catálogo built-in do opencode não expõe as variantes para esse modelo, e a sobrescreve as adiciona sem quebrar o routing (fix validado ao vivo: `opencode debug config` + `opencode run --model vercel/deepseek/deepseek-v4-flash --variant max`). Fato verificado no source do opencode 1.18: o TUI não tem flag `--variant` (só `opencode run`) e `--model` é estritamente `provider/model`, então a variante é selecionável no TUI e sticky por model (state do opencode) — a verificação do aceite é a linha de launch emitida pelo preset. 8 literais + pin da constante em `tests/unit/worktree.unit.spec.ts` atualizados; textos em `scripts/worktree.mjs` (docblock/help), `.agents/shell/worktree.sh` e `worktree-next-issue/SKILL.md` sincronizados. Anti-goals preservados: cardápio de execução de Issues intocado (`work-issue.md`/`plan-issue.md` + pin `opencodeCommands.unit.spec.ts` seguem com o provider direto), config global do humano intocada, docs históricos congelados. Verificação: `pnpm gate:fast` verde; `gate:push` na entrega; PR em main com auto-merge. **OPS46 — infra de teste e2e: coleta local consertada (next/cache) + allocator de município sem colisão no wrap (2026-08-23):** entrega de dois débitos do OPS45 (capture-review-debts). **Fase 1 — e2e local não coleta:** `pnpm exec playwright test --list` falhava em todo spec com `Cannot find module 'node_modules/next/cache'` (e o diagnóstico correto do CI-vs-máquina: não há delta — o CI só roda pelo script `pnpm test:e2e`, que carrega `--conditions=react-server --import=tsx/esm` no `NODE_OPTIONS`; comando bare não tinha nada). Causa: `next@15` não declara `exports` → ESM nativo não resolve `next/cache` (o arquivo é `cache.js`), e `server-only` precisa da condição `react-server`. Fix no runner, zero mudança em produção: hook de resolução ESM vitest-free `tests/helpers/e2eEsmResolve.mjs` (mapeia `next/cache`→`next/cache.js` e injeta `react-server` nas conditions) registrada no processo main via `module.register` e propagada aos workers via `NODE_OPTIONS=--import=e2eEsmLoader.mjs` no `playwright.config.ts` — só quando os flags do script já não estão presentes. Aceite: bare `playwright test --list` coleta 198 testes em 41 arquivos (antes: 0); runs reais bare admin 5/5 e campaign 18/18; `pnpm test:e2e` intocado. **Fase 2 — allocator de município com wrap destrutivo:** a sequência persistente `campaign_fixture_municipality_alloc` nunca é resetada e um ciclo completo de suíte claima ~505 do catálogo de 435 (medido: 432 no int + 73 no e2e) → o wrap acontece em todo ciclo em DB persistente de worktree, e dois runs concorrentes podiam receber o mesmo índice e purgar as linhas vivas um do outro (purge-on-claim). Fix: registry de claims em módulo compartilhado vitest-free `tests/helpers/campaignMunicipalityAllocator.ts` — tabela `campaign_fixture_municipality_claims` (`index` PK, `run_id`, `claimed_at`), claim via `INSERT ON CONFLICT DO NOTHING RETURNING` (conflito = slot vivo → pula; claim stale >2h de run crashado → rouba; tudo ocupado → erro claro de exaustão), release no `cleanup()` dos dois fixtures (int + e2e, só quando claimaram). Guard estático novo no `testMunicipalityAllocatorConventions` (proíbe `nextval`/sequence fora do módulo) + spec int de stress `campaignMunicipalityAllocator.int.spec.ts` (exclusividade viva por 3 wraps, exaustão+recuperação, steal, release). Verificação: tsc/lint/format/cycles verdes (knip: limitação P3 pré-existente de não carregar payload.config, igual no HEAD), unit 2420 + int 819 (92 arquivos) verdes com tabela de claims zerada ao final, e2e local afetado (admin 5/5 + campaign 18/18 bare) verdes; importMap.js restaurado com envs `S3_*` dummy após o `--list` bare regenerá-lo sem elas (classe OPS69 — o guard do #87 cobre no CI). Impl plan: `docs/plans/ops46-infra-teste-e2e-local-allocator-impl.md`. **E1 — extração do hook compartilhado de busca assíncrona com debounce (2026-08-23):** a 3ª cópia do efeito de busca async (requestId + `setTimeout(250)` + loading/failed) — introduzida pela C143 em `DemandResponsibleMultiSelect` (demand), e presente em `ResponsibleMultiSelect` (C90) e `AsyncSearchCombobox` — vira o hook `useAsyncSearchOptions({ open, query, search, isQueryReady? }) → { options, loading, failed }` (`src/hooks/useAsyncSearchOptions.ts`, novo; constante `ASYNC_SEARCH_DEBOUNCE_MS` exportada). Os 3 componentes consomem o hook e mantêm só o que é seu (grupos por typeLabel, avatares, headings de sugestões do município, `pinnedOptions`, filtro de selecionados, mensagens de vazio); semântica de query vazia preservada por consumidor (Demand busca sempre → sugestões; ResponsibleMultiSelect reseta via `isContactSearchQueryReady`; combobox com gate configurável — o efeito sync `selected←value` fica no componente). Fix pontual: server actions intocadas (continuam props — hook client-safe por injeção), sem schema/migration/access/Consent. Testes: spec unit novo `tests/unit/useAsyncSearchOptions.unit.spec.ts` (renderHook + fake timers, 9 casos: debounce/trim, coalesce, gate reset, closed, failed + retry, in-flight loading, out-of-order, quirk gate→not-ready com resposta stale pinado — comportamento herdado dos 3 originais, registrado na decisão D3 do impl plan). Impl plan `docs/plans/extrair-hook-busca-assincrona-debounce-impl.md`. Verificação: unit 2428 + int 815 verdes, e2e activity/demand (campaignActivity, campaignRegisterDemand, campaignDemandVisibility) verdes em re-run (1 flake de cold-compile pré-existente isolado), tsc/lint/format/cycles/build verdes; knip com o erro de load pré-existente (exit 0); fora de escopo: `WizardMunicipalitySearchStep` (4ª variante, AbortController — defer com gatilho no plano). diff --git a/docs/changelog/2026-08-23-ops78.md b/docs/changelog/2026-08-23-ops78.md new file mode 100644 index 00000000..f249641e --- /dev/null +++ b/docs/changelog/2026-08-23-ops78.md @@ -0,0 +1 @@ +**OPS78 — default de launch do worktree opencode → DeepSeek V4 Flash max via Vercel AI Gateway (2026-08-23):** o launch de `pnpm worktree next/plan/new` abria o opencode com o preset `deepseek/deepseek-v4-flash` (rota direta ao provedor). A política mudou: a sessão de launch deve rotear pelo **Vercel AI Gateway** (cache/estabilidade/failover) e expor a variante de raciocínio mais forte (`max`). Mudança config-only (mecanismo OPS26 "trocar modelo = editar a constante"): `OPENCODE_PRESET_MODEL` em `scripts/lib/worktree.mjs` → `vercel/deepseek/deepseek-v4-flash`; o repo `opencode.json` ganhou o override `provider.vercel.models["deepseek/deepseek-v4-flash"].variants` com low/high/max (cada uma com `reasoningEffort`) — o catálogo built-in do opencode não expõe as variantes para esse modelo, e a sobrescreve as adiciona sem quebrar o routing (fix validado ao vivo: `opencode debug config` + `opencode run --model vercel/deepseek/deepseek-v4-flash --variant max`). Fato verificado no source do opencode 1.18: o TUI não tem flag `--variant` (só `opencode run`) e `--model` é estritamente `provider/model`, então a variante é selecionável no TUI e sticky por model (state do opencode) — a verificação do aceite é a linha de launch emitida pelo preset. 8 literais + pin da constante em `tests/unit/worktree.unit.spec.ts` atualizados; textos em `scripts/worktree.mjs` (docblock/help), `.agents/shell/worktree.sh` e `worktree-next-issue/SKILL.md` sincronizados. Anti-goals preservados: cardápio de execução de Issues intocado (`work-issue.md`/`plan-issue.md` + pin `opencodeCommands.unit.spec.ts` seguem com o provider direto), config global do humano intocada, docs históricos congelados. Verificação: `pnpm gate:fast` verde; `gate:push` na entrega; PR em main com auto-merge. diff --git a/docs/plans/worktree-launch-default-llm-max-gateway-impl.md b/docs/plans/worktree-launch-default-llm-max-gateway-impl.md new file mode 100644 index 00000000..11419a98 --- /dev/null +++ b/docs/plans/worktree-launch-default-llm-max-gateway-impl.md @@ -0,0 +1,90 @@ +# Impl: Worktree launch default → LLM max via Vercel AI Gateway + +Status: rascunho +Atualizado em: 2026-08-23 +Issue: #784 +Intenção: docs/plans/worktree-launch-default-llm-max-gateway.md +Appetite restante: ~0,5–1 dia (ajustado da intenção: mudança é config-only, sem migration, sem code além da constante) + +## Leitura da intenção + +- **Outcome:** o default de launch do worktree opencode (`pnpm worktree next/plan/new`) passa a rotear o DeepSeek pela rota do **Vercel AI Gateway** (`vercel/deepseek/deepseek-v4-flash` — cache/estabilidade/failover) em vez da rota direta atual (`deepseek/deepseek-v4-flash`), o provider fica exposto no `opencode.json` do repo e as variantes low/high/**max** ficam disponíveis — verificável pela linha de launch emitida pelo preset e pelos testes unitários atualizados. +- **O que NÃO negociar:** anti-goals da intenção — NÃO mudar o cardápio de execução de Issues (`work-issue.md`/`plan-issue.md` nem o pin de `opencodeCommands.unit.spec.ts`); NÃO tocar no `~/.config/opencode/opencode.jsonc` global do humano; NÃO editar docs históricos (`docs/plans`, `docs/CHANGELOG-AGENTS.md:208,212` congelado). Fix de variantes é **config-only** em `opencode.json` — sem migration, sem code. +- **O que reavaliar:** a hipótese de produto "a sessão **abre** com a variante max". Verificação ao vivo (opencode 1.18.21): o TUI (`opencode --model X --auto`) não tem flag `--variant` (só `opencode run` tem; `--model` aceita estritamente `provider/model`, sem sufixo de variante). A variante não é forçável na linha de launch — ela é **selecionável no TUI** (keybind variant_cycle) e **sticky** por model key (`~/.local/state/opencode/model.json`), com fallback para `model.request.variant` do catálogo. A intenção é atendida expondo as variantes no override + roteando o preset pelo gateway; a linha de launch é a verificação primária aceita. + +## Abordagem recomendada + +```mermaid +flowchart LR + P["pnpm worktree next/plan/new"] --> L["scripts/lib/worktree.mjs\nOPENCODE_PRESET_MODEL = vercel/deepseek/deepseek-v4-flash"] + L --> D["opencodeLaunchDirective (scripts/lib/worktree.mjs:145)\nlinha de launch --model vercel/deepseek/deepseek-v4-flash"] + D --> T["TUI opencode"] + C["opencode.json\nprovider.vercel + variants low/high/max"] --> T + T -->|"max (sticky via model.json)"| G["Vercel AI Gateway\ncache / estabilidade / failover"] +``` + +**Opções consideradas:** A | B | C +**Recomendação:** B — mudar a constante single-source do preset para a rota do gateway (`vercel/deepseek/deepseek-v4-flash`) e expor as variantes low/high/max via override do model config no `opencode.json` do projeto (bloco `provider.vercel.models["deepseek/deepseek-v4-flash"].variants`, max com `reasoningEffort: "max"`). É a única opção que atende o outcome dentro das capacidades reais do TUI, preserva o cardápio de execução intacto, é verificável (linha de launch + testes unit) e cabe no appetite (config-only). O override foi validado ao vivo (`opencode debug config` mostra o override; `opencode run --model vercel/deepseek/deepseek-v4-flash --variant max` funciona). +**Rejeitadas:** +- **A — agente default com `variant: max`** (`model.agent`/default): muda o comportamento de launch de **todas** as sessões do repo e força a variante por fora do TUI, arriscando o cardápio de execução de Issues (que pina `model: deepseek/deepseek-v4-flash` no frontmatter dos comandos) e o próprio modelo de descoberta no TUI. Rejeitada porque é mais intrusiva do que a intenção pede e fere o "config-only" (variante no default muda a experiência de todo mundo, não só o preset). +- **C — flag `--variant` na linha de launch** (`--model ...:max` ou `--variant max`): o TUI do opencode não suporta (`tui.ts` só expõe `--model`; formato estritamente `provider/model`). Verificado ao vivo (erro de servidor em `:max`). Rejeitada por impossibilidade técnica upstream. + +### Componentes / mudanças + +- **`OPENCODE_PRESET_MODEL`** (`scripts/lib/worktree.mjs:28`): valor → `'vercel/deepseek/deepseek-v4-flash'`; comentário `:23-26` atualizado para "preset → rota do Vercel AI Gateway". É a **única** fonte da string (OPS26); emitida em `opencodeLaunchDirective` (`:145`) — nenhum outro código lê/duplica o literal. +- **`opencode.json`** (`:10-34`): adicionar o provider `vercel` **ao lado** de `cheapest-inference` (não substituir) com override do model config: + ```json + "vercel": { + "models": { + "deepseek/deepseek-v4-flash": { + "variants": { + "low": { "reasoningEffort": "low" }, + "high": { "reasoningEffort": "high" }, + "max": { "reasoningEffort": "max" } + } + } + } + } + ``` + (variantes exatamente como no fix de variantes da intenção; o provider `vercel` é built-in/models.dev, credencial do gateway já no ambiente — sem npm/options adicionais.) +- **`tests/unit/worktree.unit.spec.ts`** (`:151,159,165,171,177,180,185,196`): atualizar os 8 literais da linha de launch para `vercel/deepseek/deepseek-v4-flash`; `:185` pina a constante (`OPENCODE_PRESET_MODEL`) — trocar o literal esperado. **NÃO** mexer em `tests/unit/opencodeCommands.unit.spec.ts:36-38` (pina o frontmatter dos comandos de execução — anti-goal). +- **Textos (string atualizada, sentido OPS26 preservado — "preset é constante em `scripts/lib/worktree.mjs`"):** + - `scripts/worktree.mjs:27` (docblock) e `:778` (help console.log) + - `.agents/shell/worktree.sh:10` (comentário header) + - `.agents/skills/worktree-next-issue/SKILL.md:34` (texto do skill) +- **Migration:** sem migration — config/tooling apenas; nenhum schema de Payload muda, `pnpm migrate`/`migrate:create` não se aplicam. +- **Access / Consent:** N/A — não há collection nem dado de cidadão envolvido; não afeta login de campanha nem LGPD. +- **UI:** N/A — TUI do opencode é consumidor, não produto deste plano (nenhum componente Next/Payload do repo muda). + +### Dados → forma (se aplicável) + +N/A — tooling. Não há dado de negócio sendo modelado: a mudança é uma constante de config + bloco de provider no `opencode.json`. (Perguntas de data-presentation não se aplicam.) + +## Fases verificáveis + +1. **Constante + opencode.json + testes** — `scripts/lib/worktree.mjs:23-28`, `opencode.json` (bloco `provider.vercel`), `tests/unit/worktree.unit.spec.ts` (8 literais + pin `:185`). Quota: maior parte do appetite. +2. **Textos** — `scripts/worktree.mjs:27,:778`, `.agents/shell/worktree.sh:10`, `.agents/skills/worktree-next-issue/SKILL.md:34`. Quota: resto do appetite. +3. **Gates** — `pnpm test:unit` (foco em `tests/unit/worktree.unit.spec.ts`, opcionalmente com `-t` no spec); `pnpm gate:fast` (lint + typecheck + test:unit); validação manual: `opencode debug config` mostra o provider `vercel` + variants do override, e um launch real de teste (`pnpm worktree next` em branch descartável) emite `--model vercel/deepseek/deepseek-v4-flash` e responde; push via `pnpm push` → CI PR (checks). + +## Rabbit holes / Não escopo (engenharia) + +- NÃO mudar `.opencode/commands/work-issue.md:3` / `plan-issue.md:3` (frontmatter `model: deepseek/deepseek-v4-flash`) nem o pin de `tests/unit/opencodeCommands.unit.spec.ts:36-38` — o cardápio de execução de Issues fica intacto (anti-goal). +- NÃO forçar a variante via agente default (opção A rejeitada) — variante fica selecionável e sticky no TUI, não imposta. +- NÃO tocar no `~/.config/opencode/opencode.jsonc` global do humano — o provider vive no `opencode.json` do repo. +- NÃO editar `docs/CHANGELOG-AGENTS.md:208,212` nem outros docs históricos (`docs/plans`, changelogs) — congelados. +- NÃO criar provider duplicado/renomear `cheapest-inference` — `vercel` entra ao lado. +- Não inventar flag `--variant` no launch do preset (TUI não suporta) nem sufixo `:max` no `--model` (formato inválido). + +## Riscos e mitigação + +- **O TUI não força a variante na linha de launch** (sem `--variant`; `--model` é estritamente `provider/model`) → a sessão pode abrir com `request.variant` do catálogo (não o max). Mitigação: variantes low/high/max expostas pelo override e cicláveis no TUI com sticky persistente (`~/.local/state/opencode/model.json`); o aceite de produto foi ajustado — a **linha de launch emitida pelo preset** (`--model vercel/deepseek/deepseek-v4-flash`) é a verificação primária. +- **Override rejeitado pelo config loader do opencode** (model config v1 não é setável via user config em todas as versões) → mitigação: validar `opencode debug config` (mostra o override) e `opencode run --model vercel/deepseek/deepseek-v4-flash --variant max` no PR, antes de marcar aprovado; documentar no plano se versões futuras mudarem o formato. +- **String duplicada em textos fora da constante** → mitigação: os 4 pontos de texto são atualizados na mesma mudança (Fase 2) e os testes unit pínam a linha de launch, então qualquer deriva volta a aparecer na CI. +- **Erro de digitação no valor da constante (rota do gateway)** → mitigação: testes unit comparam a linha emitida com o literal esperado; `:185` pina a própria constante. + +## Aceite de engenharia + +- [ ] Aceite de produto da intenção coberto: preset emite `--model vercel/deepseek/deepseek-v4-flash`; variantes low/high/max expostas no `opencode.json` (max com `reasoningEffort: "max"`); verificável pela linha de launch + testes unit atualizados. +- [ ] Anti-goals preservados: `work-issue.md`/`plan-issue.md` e `opencodeCommands.unit.spec.ts` intactos; `~/.config/opencode/opencode.jsonc` intacto; `docs/CHANGELOG-AGENTS.md` e docs históricos intactos; config-only (sem migration, sem code além da constante). +- [ ] Invariantes AGENTS/engineering-standards: constante single-source mantida (OPS26), provider adicionado ao lado (sem twinning), testes de domínio atualizados onde o literal muda. +- [ ] `pnpm test:unit` verde (spec `worktree.unit.spec.ts`) e `pnpm gate:fast` verde; `opencode debug config` validado no PR. diff --git a/opencode.json b/opencode.json index 73e2a530..62dff7c6 100644 --- a/opencode.json +++ b/opencode.json @@ -30,6 +30,23 @@ } } } + }, + "vercel": { + "models": { + "deepseek/deepseek-v4-flash": { + "variants": { + "low": { + "reasoningEffort": "low" + }, + "high": { + "reasoningEffort": "high" + }, + "max": { + "reasoningEffort": "max" + } + } + } + } } } } diff --git a/scripts/lib/worktree.mjs b/scripts/lib/worktree.mjs index 385ccae3..db67fd56 100644 --- a/scripts/lib/worktree.mjs +++ b/scripts/lib/worktree.mjs @@ -19,8 +19,13 @@ export const PLAN_BRANCH_PREFIX = 'plans/plan-issue' /** Env var the interactive-terminal `worktree()` function sets to request the opencode launch. */ export const WORKTREE_TERMINAL_ENV = 'TEQO_WORKTREE_TERMINAL' -/** Preset model for the opencode launch — change the preset by editing this constant. */ -export const OPENCODE_PRESET_MODEL = 'deepseek/deepseek-v4-flash' +/** + * Preset model for the opencode launch — change the preset by editing this + * constant. OPS78: routes through the Vercel AI Gateway (cache/stability/ + * failover) instead of the direct provider; the low/high/max variants are + * exposed by the `provider.vercel` override in `opencode.json`. + */ +export const OPENCODE_PRESET_MODEL = 'vercel/deepseek/deepseek-v4-flash' /** * Skill command sent as the launch's initial message per purpose. `next` diff --git a/scripts/worktree.mjs b/scripts/worktree.mjs index c33867f5..e63476ec 100644 --- a/scripts/worktree.mjs +++ b/scripts/worktree.mjs @@ -24,7 +24,7 @@ * Chamado do terminal interativo (com * `TEQO_WORKTREE_TERMINAL=1`, que só a função shell * seta), imprime também a diretiva `launch - * opencode --model deepseek/deepseek-v4-flash + * opencode --model vercel/deepseek/deepseek-v4-flash * --auto --prompt "/work-issue --issue "` ANTES * do `cd` — a função shell executa o cd e então a * linha, e o TUI do opencode abre no worktree com @@ -775,7 +775,7 @@ if (!subcommand) { console.log(' opencode command, ou a função `worktree()` de .agents/shell/worktree.sh);') console.log(' no terminal (TEQO_WORKTREE_TERMINAL=1) imprime também a diretiva') console.log(' `launch opencode … --prompt "/work-issue --issue "` (OPS26+OPS33:') - console.log(' abre o TUI com deepseek/deepseek-v4-flash + auto + a Issue claimada já') + console.log(' abre o TUI com vercel/deepseek/deepseek-v4-flash (gateway) + auto + a Issue claimada já') console.log(' informada); --stay suprime cd e launch (o claim ainda acontece); --go') console.log(' explícito continua aceito como no-op; --no-migrate pula migrations e o') console.log(' seed mínimo (db:seed:minimal) nos bancos novos (OPS28: paridade com a CI)') diff --git a/tests/unit/worktree.unit.spec.ts b/tests/unit/worktree.unit.spec.ts index ab0fe2e9..dac9351f 100644 --- a/tests/unit/worktree.unit.spec.ts +++ b/tests/unit/worktree.unit.spec.ts @@ -148,7 +148,7 @@ describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33) it('next launches with the preset model, --auto and the /work-issue command sent (value always quoted)', () => { expect(opencodeLaunchDirective({ dir, purpose: 'next', terminal: true })).toBe( - `launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto --prompt "/work-issue"`, + `launch opencode ${dir} --model vercel/deepseek/deepseek-v4-flash --auto --prompt "/work-issue"`, ) }) @@ -156,33 +156,33 @@ describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33) expect( opencodeLaunchDirective({ dir, purpose: 'next', terminal: true, issueNumber: 595 }), ).toBe( - `launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto --prompt "/work-issue --issue 595"`, + `launch opencode ${dir} --model vercel/deepseek/deepseek-v4-flash --auto --prompt "/work-issue --issue 595"`, ) }) it('plan launches with the same presets and /plan-issue sent (OPS31, value quoted)', () => { expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true })).toBe( - `launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto --prompt "/plan-issue"`, + `launch opencode ${dir} --model vercel/deepseek/deepseek-v4-flash --auto --prompt "/plan-issue"`, ) }) it('new launches prompt-less too — "apenas conversar", no skill sent', () => { expect(opencodeLaunchDirective({ dir, purpose: 'new', terminal: true })).toBe( - `launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto`, + `launch opencode ${dir} --model vercel/deepseek/deepseek-v4-flash --auto`, ) }) it('plan/new ignore the issueNumber — only next carries the claimed issue', () => { expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true, issueNumber: 7 })).toBe( - `launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto --prompt "/plan-issue"`, + `launch opencode ${dir} --model vercel/deepseek/deepseek-v4-flash --auto --prompt "/plan-issue"`, ) expect(opencodeLaunchDirective({ dir, purpose: 'new', terminal: true, issueNumber: 7 })).toBe( - `launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto`, + `launch opencode ${dir} --model vercel/deepseek/deepseek-v4-flash --auto`, ) }) it('pins the preset constants — changing the model is editing a constant', () => { - expect(OPENCODE_PRESET_MODEL).toBe('deepseek/deepseek-v4-flash') + expect(OPENCODE_PRESET_MODEL).toBe('vercel/deepseek/deepseek-v4-flash') expect(WORKTREE_TERMINAL_ENV).toBe('TEQO_WORKTREE_TERMINAL') expect(OPENCODE_SKILL_COMMAND_BY_PURPOSE).toEqual({ next: '/work-issue', @@ -193,7 +193,7 @@ describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33) it('an unknown purpose degrades to a prompt-less launch (fail-safe direction)', () => { expect(opencodeLaunchDirective({ dir, purpose: 'bogus', terminal: true })).toBe( - `launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto`, + `launch opencode ${dir} --model vercel/deepseek/deepseek-v4-flash --auto`, ) }) })