From 152d0a147327d197891ce357f032996e0e0ba0d4 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Mon, 10 Aug 2026 10:21:08 -0300 Subject: [PATCH] OPS24: worktree CLI default-go (--stay escape) + kill volta ao main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit next/plan imprimem 'cd ' na última linha por padrão; --stay suprime; --go segue aceito como no-op. kill imprime 'cd
' como última linha (depois do drop dos bancos) e recusa --stay. Superfícies: worktree.sh aplica o cd por padrão; comando /worktree do opencode idem. Docs (AGENTS.md, skills, CHANGELOG) atualizadas. --- .agents/shell/worktree.sh | 42 +++++----- .agents/skills/local-database/SKILL.md | 2 +- .agents/skills/worktree-next-issue/SKILL.md | 6 +- .opencode/commands/worktree.md | 8 +- AGENTS.md | 2 +- docs/CHANGELOG-AGENTS.md | 2 + ...ee-go-default-e-kill-volta-ao-main-impl.md | 78 +++++++++++++++++++ scripts/worktree.mjs | 47 ++++++----- 8 files changed, 139 insertions(+), 48 deletions(-) create mode 100644 docs/plans/worktree-go-default-e-kill-volta-ao-main-impl.md diff --git a/.agents/shell/worktree.sh b/.agents/shell/worktree.sh index 9578ac4f..6c3674a2 100644 --- a/.agents/shell/worktree.sh +++ b/.agents/shell/worktree.sh @@ -1,23 +1,26 @@ -# worktree — CLI determinístico de worktrees em torno da fila de claim, com um -# `--go` que realmente troca de diretório. +# worktree — CLI determinístico de worktrees em torno da fila de claim, com +# troca de diretório por padrão (e `kill` que volta ao main). # # A lógica determinística (fila → branch → worktree) vive em scripts/worktree.mjs; -# esta função é só a camada de shell que APLICA o `cd ` que o script imprime, -# porque um processo filho (node) não consegue mudar o cwd do shell que o chamou. +# esta função é só a camada de shell que APLICA o `cd ` que o script imprime +# na última linha, porque um processo filho (node) não consegue mudar o cwd do +# shell que o chamou. # # Instalação (uma linha no profile): # source /.agents/shell/worktree.sh # # Uso (terminal interativo): -# worktree next [--go] cria/reutiliza o worktree da próxima Issue claimável; -# com --go, cd para dentro dele -# worktree plan [bag] [--go] cria um worktree de planejamento do /plan-issue -# DIFERENTE a cada chamada (sessões paralelas): com bag, -# branch plans/plan-issue- (sufixo -2/-3 se o nome -# já existir), sem bag o próximo plans/plan-issue- -# sequencial; com --go, cd para dentro dele -# worktree kill [--force] +# worktree next [--stay] cria/reutiliza o worktree da próxima Issue claimável +# e cd para dentro dele por padrão; --stay não troca +# worktree plan [bag] [--stay] cria um worktree de planejamento do /plan-issue +# DIFERENTE a cada chamada (sessões paralelas): com bag, +# branch plans/plan-issue- (sufixo -2/-3 se o nome +# já existir), sem bag o próximo plans/plan-issue- +# sequencial; cd para dentro dele por padrão; --stay não +# troca +# worktree kill [--force] destrói o worktree atual e cd para o main por padrão # +# `--go` explícito continua aceito como no-op (era o antigo padrão). # Read-only no GitHub — claim continua sendo `pnpm agent:claim`. worktree() { @@ -25,23 +28,22 @@ worktree() { local root root="$(cd "$(dirname "$src")/../.." && pwd)" - local has_go=0 + local stay=0 local arg for arg in "$@"; do - if [ "$arg" = "--go" ]; then - has_go=1 + if [ "$arg" = "--stay" ]; then + stay=1 fi done - if [ "$has_go" -eq 0 ]; then - node "$root/scripts/worktree.mjs" "$@" - return $? - fi - local out out="$(node "$root/scripts/worktree.mjs" "$@")" || return $? printf '%s\n' "$out" + if [ "$stay" -eq 1 ]; then + return 0 + fi + local dir dir="$(printf '%s\n' "$out" | sed -n 's/^cd //p' | tail -n 1)" if [ -n "$dir" ] && [ -d "$dir" ]; then diff --git a/.agents/skills/local-database/SKILL.md b/.agents/skills/local-database/SKILL.md index d905153b..e7ec13c8 100644 --- a/.agents/skills/local-database/SKILL.md +++ b/.agents/skills/local-database/SKILL.md @@ -26,7 +26,7 @@ VMs Cloud **não rodam** `docker compose` / `pnpm db:start`. Postgres nativo: `. ## Per-worktree environments (`pnpm worktree next`) -Parallel agents must not fight over port 3000, the shared `teqo_test`, or a second Postgres container. `pnpm worktree next [--go]` provisions each worktree with a deterministic isolated env: +Parallel agents must not fight over port 3000, the shared `teqo_test`, or a second Postgres container. `pnpm worktree next [--stay]` provisions each worktree with a deterministic isolated env (`next`/`plan` print `cd ` by default — apply it, or pass `--stay` to suppress; `--go` is still accepted as a no-op): - `slot` = numeric part of the issue code (`C15` → 15, `B164` → 164; hash fallback without digits; bumped on collision among live worktrees). - Dev server port `3100+slot`; databases `teqo_wt` (dev) and `teqo_wt_test` (tests), created on the **one shared container** with the full migration chain applied (one-time, ~30 s each). diff --git a/.agents/skills/worktree-next-issue/SKILL.md b/.agents/skills/worktree-next-issue/SKILL.md index 9df5d96a..99737873 100644 --- a/.agents/skills/worktree-next-issue/SKILL.md +++ b/.agents/skills/worktree-next-issue/SKILL.md @@ -19,8 +19,8 @@ Prepara o ambiente git para a próxima Issue da fila de claim, **sem** tocá-la A lógica determinística (fila → código+slug → branch → worktree) vive em **`scripts/worktree.mjs`** — fonte única, coberta por `tests/unit/worktree.unit.spec.ts`. Não a reescreva à mão aqui. ```bash -pnpm worktree next [--go] [--no-migrate] # cria worktree de origin/main + provisiona o ambiente -pnpm worktree plan [bag] [--go] [--no-migrate] # worktree de PLANEJAMENTO (/plan-issue): um DIFERENTE por invocação +pnpm worktree next [--stay] [--no-migrate] # cria worktree de origin/main + provisiona o ambiente +pnpm worktree plan [bag] [--stay] [--no-migrate] # worktree de PLANEJAMENTO (/plan-issue): um DIFERENTE por invocação pnpm worktree kill [--force] # destrói o worktree atual + remove seus bancos gerados ``` @@ -28,7 +28,7 @@ No opencode, isso é o comando **`/worktree next`** / **`/worktree kill`** (`.op `plan` é o primo do `next`: worktree de **planejamento** para rodar a skill `/plan-issue` sem ocupar o main. **Cada invocação cria um DIFERENTE** (sessões paralelas): com `bag` opcional → branch `plans/plan-issue-` (sufixo `-2`/`-3` se o nome já estiver vivo); sem `bag` → próximo sequencial `plans/plan-issue-` livre. Não é nomeado por Issue nenhuma — proposital, para um `next` posterior da próxima Issue nunca colidir em branch nem em slot (prefixo minúsculo `plans/…` vs branch `-` sempre uppercase-led). O fluxo desta skill (fila → próxima Issue → implementação) é só `next`. -No terminal interativo, para `--go` trocar de diretório de verdade, use a função `worktree()` de **`.agents/shell/worktree.sh`** (uma linha de `source` no profile): o script imprime `cd ` e a função o aplica no shell que te chamou — node não consegue mudar o cwd do shell pai. +**Default-go:** `next` e `plan` 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). ## Fluxo quando invocado como skill (agentes que não têm opencode) diff --git a/.opencode/commands/worktree.md b/.opencode/commands/worktree.md index 65e2c97f..8e7527dd 100644 --- a/.opencode/commands/worktree.md +++ b/.opencode/commands/worktree.md @@ -1,11 +1,11 @@ --- -description: Cria o worktree da próxima Issue claimável (next [--go]), um worktree novo de planejamento do /plan-issue (plan [bag] [--go]) ou destrói o worktree atual (kill [--force]) +description: Cria o worktree da próxima Issue claimável (next [--stay]), um worktree novo de planejamento do /plan-issue (plan [bag] [--stay]) ou destrói o worktree atual (kill [--force]) --- Rode o script determinístico abaixo (não improvise a lógica) e reporte o resultado de forma concisa. -- `next [--go]`: cria (ou reutiliza) o worktree da próxima Issue claimável. Com `--go`, o script imprime `cd ` na última linha — aplique-o na sessão de shell atual (`cd `) e reporte o novo diretório. -- `plan [bag] [--go]`: cria um worktree de planejamento **diferente a cada invocação** (branch `plans/plan-issue-`, ou `plans/plan-issue-` sequencial sem bag; sufixo `-2`/`-3` em colisão) para rodar `/plan-issue` sem ocupar o main; nunca conflita com `next`. -- `kill [--force]`: destrói o worktree em que você está. +- `next [--stay]`: cria (ou reutiliza) o worktree da próxima Issue claimável. Por padrão o script imprime `cd ` na última linha — aplique-o na sessão de shell atual (`cd `) e reporte o novo diretório. `--stay` suprime o `cd` (não troque de diretório). `--go` explícito continua aceito como no-op. +- `plan [bag] [--stay]`: cria um worktree de planejamento **diferente a cada invocação** (branch `plans/plan-issue-`, ou `plans/plan-issue-` sequencial sem bag; sufixo `-2`/`-3` em colisão) para rodar `/plan-issue` sem ocupar o main; nunca conflita com `next`. Mesmo contrato de `cd` do `next`: aplique a última linha `cd ` por padrão; `--stay` suprime. +- `kill [--force]`: destrói o worktree em que você está e remove seus bancos. O script imprime `cd
` na última linha — aplique-o (`cd
`) para a sessão sair do diretório destruído. !`node scripts/worktree.mjs $ARGUMENTS` diff --git a/AGENTS.md b/AGENTS.md index 0a34e2ec..acbe066b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,7 +34,7 @@ These are product/architecture constraints — not a ban on editing existing cod - `Consent.text` was reconciled from `varchar` to `jsonb` via `20260715_163500_consent_text_to_jsonb` (matching the `richText` field). Any future field-type change is a migration, not a manual DB edit. - To copy production content locally, use `pnpm db:pull` (`scripts/db-pull.mjs`): it only reads prod, only writes local, and excludes supporter PII (`contact`/`signature`/`subscription` row data). - `pnpm db:doctor` diagnoses local database connectivity for the dev AND test targets: it names who holds the port, points at exited/crash-looping containers, and flags Postgres containers sharing one data volume (two postmasters on one PGDATA corrupt the WAL — this destroyed the local cluster's checkpoint on 2026-07-25; the canonical setup is ONE compose container on 5432 hosting `teqo` + `teqo_test`). `pnpm dev` runs the same preflight via `guard-dev-db` and refuses to start with a named remedy instead of a later ECONNREFUSED. `db:start`/`db:stop` pin the compose project with `-p teqo`, so they always target that ONE container even from a worktree directory. -- **Per-worktree environments (`pnpm worktree next`, 2026-08-08):** parallel agents each get a deterministic isolated env — no fighting over port 3000, the shared `teqo_test`, or a second Postgres container. `worktree next [--go]` provisions: a dev-server port `3100+slot` (slot = numeric part of the issue code, hash fallback, bumped on collision among live worktrees), plus dedicated databases `teqo_wt` (dev) and `teqo_wt_test` (tests) created on the shared container with the full migration chain applied; writes `.env.local` + `.env.test.local` (gitignored, marker `# teqo worktree env (generated by pnpm worktree next)`; manual envs are never overwritten) and copies `PAYLOAD_SECRET`/Blob/VAPID from the main repo. `worktree plan [bag] [--go]` (2026-08-08) is the sibling for the `/plan-issue` skill: **each invocation creates a DIFFERENT planning worktree** so parallel `/plan-issue` sessions never share one — branch `plans/plan-issue-` when a `bag` slug is given (suffixed `-2`/`-3` if that name is taken) or the next free sequential `plans/plan-issue-` when not (`PLAN_BRANCH_PREFIX`/`planBranchName`, `scripts/lib/worktree.mjs`); the lowercase `plans/…` prefix deliberately never matches `next`'s uppercase-led `-`, so a later `next` never collides in branch or slot. `kill` drops the worktree's generated databases best-effort. Tests read `.env.test.local` after `.env.test` (vitest.setup.ts / playwright.config.ts / db-doctor / gate-ci mirror the layering), and `assertTestDatabase` accepts `teqo_test` or `teqo__test` on localhost only — still fail-closed on remote. Pure derivation lives in `scripts/lib/worktree-env.mjs` (unit-tested). Without Docker (Cursor Cloud) it degrades to the shared `teqo`/`teqo_test` with a warning. +- **Per-worktree environments (`pnpm worktree next`, 2026-08-08; default-go 2026-08-10):** parallel agents each get a deterministic isolated env — no fighting over port 3000, the shared `teqo_test`, or a second Postgres container. `worktree next [--stay]` provisions: a dev-server port `3100+slot` (slot = numeric part of the issue code, hash fallback, bumped on collision among live worktrees), plus dedicated databases `teqo_wt` (dev) and `teqo_wt_test` (tests) created on the shared container with the full migration chain applied; writes `.env.local` + `.env.test.local` (gitignored, marker `# teqo worktree env (generated by pnpm worktree next)`; manual envs are never overwritten) and copies `PAYLOAD_SECRET`/Blob/VAPID from the main repo. **`next`/`plan` print `cd ` as the last line BY DEFAULT** — the caller applies it (shell function `worktree()` in `.agents/shell/worktree.sh`, or the `/worktree` opencode command); `--stay` suppresses the cd, `--go` explicit stays accepted as a no-op (it was the old default), and `kill` ends with `cd
` so the shell never sits in a destroyed directory. `worktree plan [bag] [--stay]` (2026-08-08) is the sibling for the `/plan-issue` skill: **each invocation creates a DIFFERENT planning worktree** so parallel `/plan-issue` sessions never share one — branch `plans/plan-issue-` when a `bag` slug is given (suffixed `-2`/`-3` if that name is taken) or the next free sequential `plans/plan-issue-` when not (`PLAN_BRANCH_PREFIX`/`planBranchName`, `scripts/lib/worktree.mjs`); the lowercase `plans/…` prefix deliberately never matches `next`'s uppercase-led `-`, so a later `next` never collides in branch or slot. `kill` drops the worktree's generated databases best-effort. Tests read `.env.test.local` after `.env.test` (vitest.setup.ts / playwright.config.ts / db-doctor / gate-ci mirror the layering), and `assertTestDatabase` accepts `teqo_test` or `teqo__test` on localhost only — still fail-closed on remote. Pure derivation lives in `scripts/lib/worktree-env.mjs` (unit-tested). Without Docker (Cursor Cloud) it degrades to the shared `teqo`/`teqo_test` with a warning. - To (re)populate news content, `pnpm db:seed:posts` fetches articles live from jorgesolla.com.br into the LOCAL db (same non-local `DATABASE_URL` guard as `pnpm dev`). It writes from a CLI process outside the deployed runtime, so after seeding — or after ANY direct-DB change — you must bust the production `posts` cache via `POST /api/revalidate`. See "Posts & Tags" below. - To (re)populate the TSE 2022 election baseline, `pnpm db:seed:tse` downloads TSE open data and imports the Bahia scope into the LOCAL db (same guard). Since the 2026-07-23 hardening, elections-tab reads are cached under the `election-tse` tag — after re-seeding a database the deployed app reads, bust it via `POST /api/revalidate?tag=election-tse`. See "Election baseline data (TSE 2022)" below. diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index b24bdd86..82397f49 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- +**Recently resolved (2026-08-10):** **OPS24** (Issue #569) — `--go` virou o **padrão** do worktree CLI e `kill` volta ao main: `worktree next`/`plan` imprimem `cd ` na última linha **por padrão** (antes exigiam `--go`); `--stay` suprime; `--go` explícito continua aceito como no-op (não quebra hábito/automação). `kill` troca a mensagem "Volte ao main: cd …" (que nunca casava com o sed `s/^cd //p` da função shell) pela **linha máquina `cd
` como última linha** — depois do drop dos DBs, que rodava antes — e **não aceita `--stay`** (ficar em cwd destruído é exatamente o bug corrigido; `--go` no kill é no-op). Vale nas duas superfícies que aplicam o cd: função `worktree()` de `.agents/shell/worktree.sh` (agora sempre captura/applica, `--stay` retorna cedo) e comando `/worktree` do opencode (agente aplica o `cd` da última linha sempre, exceto `--stay`). Docs: header/uso do script, AGENTS.md ("Per-worktree environments"), skills `worktree-next-issue`/`local-database`. Sem migration/dados/access; derivação pura (`worktree-env.mjs`/`worktree.mjs` libs) intocada. Intenção [`worktree-go-default-e-kill-volta-ao-main.md`](plans/worktree-go-default-e-kill-volta-ao-main.md); impl [`worktree-go-default-e-kill-volta-ao-main-impl.md`](plans/worktree-go-default-e-kill-volta-ao-main-impl.md). + **Recently resolved (2026-08-10):** **B192** (Issue #533) — o Sollinha encerra respostas factuais com **follow-ups sugeridos como chips** (2–3, no mesmo slot dos chips de abertura do B191, acima do input; tocar envia como mensagem do usuário; substituídos a cada resposta; somem durante o streaming; mobile limita a 2). **Uma única chamada ao modelo:** o prompt (`systemPrompt.ts`) instrui encerrar com o bloco estável `**Sugestões de continuação:**` + lista markdown (só perguntas respondíveis pelas próprias tools, leader sem sugestão eleitoral — B180; bloco = instrução de interface, nunca citar em prosa) e o cliente extrai. **Contrato em fonte única:** `src/lib/sollinhaFollowUpSuggestions.ts` — `SOLLINHA_FOLLOW_UP_MARKER` (interpolado no prompt) + `splitSollinhaFollowUpBlock` (última ocorrência vence; body sempre mascarado do marcador em diante; itens `-/*/1./1)` aceitos, bold e duplicados limpos, teto 3; **fail-closed: <2 itens ou linha não-lista → sem chips**, resposta normal). UI: strip no render de todo text part assistant **antes** do markdown/links (bloco nunca pisca como prosa; mensagens antigas restauradas via B188 continuam mascaradas e os chips renascem por derivação); slot B191 estendido (catálogo vazio → follow-ups); tipo compartilhado renomeado `SollinhaOpeningQuestion → SollinhaChatChip`. **Strip defensivo no servidor:** `route.ts` remove o bloco das mensagens assistant ao reconverter para o modelo (próximo turno sem eco nem custo de contexto). Unit (16 casos do split) + e2e novo `campaignAiChatFollowUps.e2e.spec.ts` (wire format v7 como o mock do B187; bolha sem o bloco, chips, substituição, fail-closed, cap mobile). Sem migration/access/Consent/schema/endpoint novo. Intenção [`sollinha-follow-ups-respostas.md`](plans/sollinha-follow-ups-respostas.md); impl [`sollinha-follow-ups-respostas-impl.md`](plans/sollinha-follow-ups-respostas-impl.md). **Recently resolved (2026-08-10):** **OPS23** (Issue #566) — a Action OPS18 (`plan-issue-ready-on-main-merge.yml`) falhava **sempre** no primeiro disparo real (PR #563, run 31381977772): o job roda `actions/setup-node@v5` sem `pnpm/action-setup` e o script é plain Node — com `package-manager-cache: true` (default do v5), o setup-node resolve o `packageManager` do package.json (`pnpm@10.11.0`) e procura o executável pnpm → `Unable to locate executable file: pnpm`, promote nunca rodava. Correção: `package-manager-cache: false` no setup-node (precedente exato dos jobs plain-Node: cooldown do `ci.yml`, `vercel-promote.yml`, `archive-cursor-agent.yml`; `pnpm/action-setup@v6` rejeitado — só existe onde há `pnpm install`). Varredura dos 21 usos de setup-node: `plan-issue-ready` era o único com o bug latente. Validação funcional = próximo merge de PR com `Related #N` (trigger `closed+merged→main` só dispara em merge real). Impl [`ops23-action-promote-related-setup-node-impl.md`](plans/ops23-action-promote-related-setup-node-impl.md). diff --git a/docs/plans/worktree-go-default-e-kill-volta-ao-main-impl.md b/docs/plans/worktree-go-default-e-kill-volta-ao-main-impl.md new file mode 100644 index 00000000..8b6070e7 --- /dev/null +++ b/docs/plans/worktree-go-default-e-kill-volta-ao-main-impl.md @@ -0,0 +1,78 @@ +# Impl: Worktree CLI: `--go` vira o padrão (escape `--stay`) e `kill` volta ao main + +Status: aprovado +Atualizado em: 2026-08-10 +Issue: #569 +Intenção: docs/plans/worktree-go-default-e-kill-volta-ao-main.md +Appetite restante: ~0,5 dia eng (herdado — mudança pequena, 7 arquivos de texto/CLI) + +## Leitura da intenção + +- **Outcome:** `worktree next`/`plan` sem flags aplicam o `cd` (hoje exigem `--go`); `--stay` suprime; `--go` vira no-op; `worktree kill` termina no main repo (cwd nunca num diretório destruído). Vale nas duas superfícies que aplicam o cd: função `worktree()` do shell e comando `/worktree` do opencode. +- **O que NÃO negociar:** lógica determinística de branch/slot/provisionamento intacta; `kill` não ganha `--stay` (decidido no gate: kill sempre volta ao main); nada de detectar terminal vs opencode no script (o script só imprime o `cd`; quem aplica decide). +- **O que reavaliar:** a hipótese da intenção manda "`cmdKill` imprime `cd ` no mesmo formato máquina". Verifiquei o formato real: `next`/`plan` imprimem `cd ` na ÚLTIMA linha; `kill` hoje imprime `Volte ao main: cd ${mainRoot}` — linha que **não** casa com o sed `s/^cd //p` da função shell, por isso nada aplica. A correção é substituir a mensagem humana pela linha máquina (o contexto "Worktree destruído:" já diz o que aconteceu), não acrescentar as duas. Ajuste extra: a linha máquina do `kill` precisa ser a última do output — hoje o `dropWorktreeDatabases` (best-effort, imprime `[worktree] banco removido: …`) roda DEPOIS do print. O sed usa `tail -n 1` de linhas que casam `^cd `, então funcionaria mesmo assim, mas o contrato "última linha" fica limpo movendo o print para depois dos drops. + +## Abordagem recomendada + +```mermaid +flowchart LR + A[worktree next/plan] --> B[flags: --stay suprime, --go no-op] + A2[kill] --> B2[sempre imprime cd mainRoot como última linha] + B --> C[worktree.mjs imprime cd dir por default] + B2 --> C + C --> D[worktree.sh: aplica cd por default; --stay não aplica] + C --> E[/worktree opencode: agente aplica cd sempre] + D --> F[terminal termina no worktree / no main] +``` + +**Opções consideradas:** A | B | C +**Recomendação:** A — inversão do flag na origem (script), com shell e opencode command consumindo a mesma linha máquina. +**Rejeitadas:** + +- **B — inverter só na função shell** (shell decide aplicar por default e passa `--stay` ao script): deixaria o opencode command e consumidores diretos do script com a semântica velha; a superfície `/worktree` também precisa do default-go. Rejeitada por inconsistência. +- **C — detectar "terminal vs opencode" no script** (ex.: env `WORKTREE_TERMINAL`): anti-goal explícito da intenção; o contrato continua sendo "script imprime `cd`; quem aplica decide". Rejeitada. +- **D — `kill --stay`** (ficar no cwd morto): anti-goal de produto (é exatamente o bug que se corrige). Rejeitada — `kill` com `--stay` morre com mensagem clara. + +### Componentes / mudanças + +- **`scripts/worktree.mjs`**: + - Header (docblock, linhas 5-34) e bloco de uso (`pnpm worktree` sem args, linhas 486-504): `next [--stay]`, `plan [bag] [--stay]`, `kill [--force]`; nota de que `--go` segue aceito como no-op. + - `cmdNext(go, skipMigrate)` / `cmdPlan(go, skipMigrate, bag)`: parâmetro `go` → `stay`; `if (go) console.log(\`cd ${dir}\`)`vira`if (!stay) console.log(\`cd ${dir}\`)` (linhas 329 e 400). + - `cmdKill(force)`: troca `Volte ao main: cd ${mainRoot}` por `cd ${mainRoot}` como **última** linha (depois de `await dropWorktreeDatabases`); `flags.go` no kill vira no-op aceito (consistente com o novo status do flag); `flags.stay` no kill → `die('--stay não se aplica a kill — ele sempre volta ao main')`. + - Dispatch (linhas 508-514): `cmdNext(Boolean(flags.stay), …)`, `cmdPlan(Boolean(flags.stay), …)`, `kill` valida `--stay`. +- **`.agents/shell/worktree.sh`**: a função sempre captura o output e aplica o `cd` da linha máquina; `--stay` detectado nos args → repassa ao script e **não** aplica cd (retorno 0 após imprimir). Remove o ramo `has_go` (o `--go` nem precisa ser tratado — vira default). Funciona para `next`/`plan`/`kill` com o mesmo sed. +- **`.opencode/commands/worktree.md`**: frontmatter + corpo — `next [--stay]`, `plan [bag] [--stay]`, `kill [--force]`; o agente aplica o `cd ` da última linha **sempre** (default), suprimido só com `--stay`; `kill` também aplica o `cd `. +- **`AGENTS.md`** (seção "Per-worktree environments"): `worktree next [--go]` → `worktree next [--stay]`, `worktree plan [bag] [--go]` → `[bag] [--stay]`, `kill` volta ao main; nota `--go` no-op. +- **`.agents/skills/worktree-next-issue/SKILL.md`**: linhas de uso (`next [--go]` → `[--stay]`) e a nota sobre a função `worktree()` (agora default). +- **`.agents/skills/local-database/SKILL.md`**: menção `pnpm worktree next [--go]` → `[--stay]`. +- **`docs/CHANGELOG-AGENTS.md`**: entrada curta da entrega (padrão OPS17+). +- **Migration:** sem migration (nenhum schema). +- **Access / Consent:** N/A — CLI de dev fora do runtime. +- **UI:** Impeccable A — N/A (sem superfície de usuário de produto). + +## Fases verificáveis + +1. **Script** — `scripts/worktree.mjs` (flags + kill line) + shell function + opencode command + docs (AGENTS.md, skills, CHANGELOG). Verificação manual de contrato: `node scripts/worktree.mjs` sem args mostra o uso novo; `next --stay`/`--go`/sem flag imprimem/omitem a linha `cd` (a fila pode estar vazia → testar via `cmdPlan` que não depende da fila, ou pela mensagem de `die` da fila vazia — o que importa é o contrato de flags não quebrar). +2. **Verificação real do kill** (a única parte que toca estado real): criar worktree de teste barato não é viável nesta sessão (kill precisa estar DENTRO de um worktree e destrói DBs) — o contrato do kill fica coberto pelo mesmo formato de linha `cd ` e pela lógica simples de print; validar o caminho feliz com um worktree de planejamento descartável se o ambiente permitir. +3. **Gates** — `pnpm gate:fast` (lint + typecheck + unit); `pnpm push` (gate completo: format/knip/cycles/test/build) — este item é scripts/docs puro, sem testes de domínio novos. + +## Rabbit holes / Não escopo (engenharia) + +- Não adicionar um helper "puro" para a linha `cd ` com unit test — a derivação não muda, o contrato é uma template string de 1 linha já pinada por 3 usos no mesmo arquivo; testes unitários do `cmdNext` exigiriam mock de git/pg que não existe no repo (as duas libs puras já são cobertas). +- Não tocar `scripts/lib/worktree-env.mjs` / `worktree.mjs` (derivação pura) — nenhuma mudança de branch/slot/provisionamento. +- Não criar "vários escapes" (`--no-go`, `--here`…) — a intenção pinou `--stay` como o único. +- Não alterar o marker `GENERATED_ENV_MARKER` (pinado por teste de unidade). +- OPS26 (abrir opencode no go) e OPS27 (`worktree new`) são itens próprios — esta entrega só muda a semântica de flags; OPS26 já nasce sobre o default-go. + +## Riscos e mitigação + +- **Quebrar automação/scripts que dependem da ausência do cd sem `--go`** — mitigação: `--go` continua aceito como no-op e o formato da linha máquina é idêntico ao de hoje; quem não quer o cd usa `--stay` (novo) — documentado nas duas superfícies. +- **`kill` imprimir a linha máquina antes do drop dos DBs** — mitigação: print movido para depois de `dropWorktreeDatabases`; a linha é a última do output (contrato do sed `tail -n 1`). +- **Shell function: perda do streaming de output** (antes, sem `--go`, o node imprimia direto; agora sempre captura e reimprime) — mitigação: comportamento idêntico ao do `--go` de hoje (já capturava); `printf '%s\n' "$out"` preserva o texto; o `|| return $?` preserva o exit code do script. +- **`kill --stay` ambíguo** — mitigação: die com mensagem clara (mesmo padrão do `--go` antigo), nunca silêncio. + +## Aceite de engenharia + +- [ ] Aceite de produto da intenção ainda coberto (default-go nas duas superfícies; `--stay`; kill volta ao main; `--go` no-op) +- [ ] Invariantes AGENTS/engineering-standards (nenhuma mudança em branch/slot/provisionamento; docs atualizadas) +- [ ] Testes de domínio previstos (unit/int): sem mudança de access/write paths — `pnpm test:unit` existente verde; verificação manual do contrato de flags/linha `cd` diff --git a/scripts/worktree.mjs b/scripts/worktree.mjs index bfa26884..84cd8288 100644 --- a/scripts/worktree.mjs +++ b/scripts/worktree.mjs @@ -2,14 +2,16 @@ * `pnpm worktree` — worktree management determinístico em torno da fila de * claim do projeto (mesma fila de `agent:claim` / agent pool). * - * pnpm worktree next [--go] [--no-migrate] + * pnpm worktree next [--stay] [--no-migrate] * cria worktree a partir de origin/main para a * próxima Issue claimável; branch `-`. - * Com `--go`, imprime `cd ` no fim — node + * Por padrão imprime `cd ` no fim — node * não muda o cwd do shell pai; o shell chamador * aplica (opencode/CDP usa o opencode command; * terminal interativo: função `worktree()` em * `.agents/shell/worktree.sh`, sourced no profile). + * `--stay` suprime a linha `cd`; `--go` explícito + * continua aceito como no-op (era o antigo padrão). * Também PROVISIONA o ambiente isolado do worktree: * porta do dev server + bancos próprios derivados * deterministicamente do branch (ver @@ -17,7 +19,7 @@ * paralelo não disputarem porta 3000 nem o * `teqo_test` compartilhado. `--no-migrate` pula a * aplicação das migrations nos bancos novos. - * pnpm worktree plan [bag] [--go] [--no-migrate] + * pnpm worktree plan [bag] [--stay] [--no-migrate] * cria um worktree de PLANEJAMENTO novo para rodar * a skill /plan-issue sem ocupar o main — cada * invocação cria UM DIFERENTE, para sessões de @@ -30,7 +32,11 @@ * Mesmo provisionamento isolado do `next`. * pnpm worktree kill [--force] destrói o worktree em que o shell atual está * (recusa worktree sujo sem `--force`) e remove os - * bancos gerados do worktree (best-effort) + * bancos gerados do worktree (best-effort); por + * padrão termina imprimindo `cd
` para o + * shell voltar ao worktree principal — o cwd nunca + * fica num diretório destruído (não aceita + * `--stay`; `--go` é no-op) * * Read-only no GitHub: `next` NUNCA claima (claim = `pnpm agent:claim`). * Dir raiz: `~/.cursor/worktrees/teqo/` (mesma casa dos worktrees do Cursor). @@ -280,7 +286,7 @@ const provision = async ({ dir, branch, issue, env, skipMigrate, mainRoot, purpo runMigrate(dir, testUrl, payloadSecret) } -const cmdNext = async (go, skipMigrate) => { +const cmdNext = async (stay, skipMigrate) => { const pick = nextClaimableIssue() if (!pick) { die('Fila vazia — nada `ready` desbloqueado. Rode `pnpm agent:status` para ver a fila.') @@ -326,7 +332,7 @@ const cmdNext = async (go, skipMigrate) => { console.log(` banco dev: postgresql://teqo:teqo@localhost:5432/${env.devDatabase}`) console.log(` banco test: postgresql://teqo:teqo@localhost:5432/${env.testDatabase}`) - if (go) console.log(`cd ${dir}`) + if (!stay) console.log(`cd ${dir}`) } /** @@ -340,7 +346,7 @@ const cmdNext = async (go, skipMigrate) => { * worktree — parallel `/plan-issue` sessions never share one. Same isolated * env provisioning as `next`. */ -const cmdPlan = async (go, skipMigrate, bag) => { +const cmdPlan = async (stay, skipMigrate, bag) => { git(['fetch', 'origin']) const entries = parseWorktreeList(git(['worktree', 'list', '--porcelain'])) @@ -397,7 +403,7 @@ const cmdPlan = async (go, skipMigrate, bag) => { console.log(` banco dev: postgresql://teqo:teqo@localhost:5432/${env.devDatabase}`) console.log(` banco test: postgresql://teqo:teqo@localhost:5432/${env.testDatabase}`) - if (go) console.log(`cd ${dir}`) + if (!stay) console.log(`cd ${dir}`) } /** Generated database names referenced by a worktree's own env files. */ @@ -475,9 +481,10 @@ const cmdKill = async (force) => { console.log(`Worktree destruído: ${top}`) console.log(` branch removido: ${branch ?? '(detached — nada a remover)'}`) - console.log(`Volte ao main: cd ${mainRoot}`) await dropWorktreeDatabases(databaseNames) + + console.log(`cd ${mainRoot}`) } const { flags, positional } = parseArgs(process.argv.slice(2), new Set()) @@ -485,31 +492,33 @@ const subcommand = positional[0] if (!subcommand) { console.log( - 'Uso: pnpm worktree next [--go] [--no-migrate] | plan [bag] [--go] [--no-migrate] | kill [--force]', + 'Uso: pnpm worktree next [--stay] [--no-migrate] | plan [bag] [--stay] [--no-migrate] | kill [--force]', ) - console.log(' next [--go] [--no-migrate]') + console.log(' next [--stay] [--no-migrate]') console.log(' cria worktree da próxima Issue claimável (branch -) e provisiona o') console.log(' ambiente isolado: porta de dev + bancos próprios (determinístico do branch);') - console.log(' com --go imprime `cd ` no fim (quem aplica o cd: opencode command, ou a') - console.log(' função `worktree()` de .agents/shell/worktree.sh); --no-migrate pula migrations') - console.log(`\n plan [bag] [--go] [--no-migrate]`) + console.log(' por padrão imprime `cd ` no fim (quem aplica o cd: opencode command, ou a') + console.log(' função `worktree()` de .agents/shell/worktree.sh); --stay suprime o cd; --go') + console.log(' explícito continua aceito como no-op; --no-migrate pula migrations') + console.log(`\n plan [bag] [--stay] [--no-migrate]`) console.log( ' cria um worktree de planejamento DIFERENTE a cada invocação (sessões /plan-issue', ) console.log(' paralelas): com bag, branch plans/plan-issue- (sufixo -2/-3 se o nome já') console.log(' existir); sem bag, o próximo plans/plan-issue- sequencial livre; o prefixo') console.log(' minúsculo plans/… nunca colide com o branch - de `next`') - console.log(' kill [--force] destrói o worktree em que você está (recusa sujo sem --force) e') - console.log(' remove os bancos gerados do worktree (best-effort)') + console.log(' kill [--force] destrói o worktree em que você está (recusa sujo sem --force),') + console.log(' remove os bancos gerados do worktree (best-effort) e imprime') + console.log(' `cd
` no fim — o shell sempre volta ao worktree principal') process.exit(1) } try { - if (subcommand === 'next') await cmdNext(Boolean(flags.go), Boolean(flags['no-migrate'])) + if (subcommand === 'next') await cmdNext(Boolean(flags.stay), Boolean(flags['no-migrate'])) else if (subcommand === 'plan') - await cmdPlan(Boolean(flags.go), Boolean(flags['no-migrate']), positional[1]) + await cmdPlan(Boolean(flags.stay), Boolean(flags['no-migrate']), positional[1]) else if (subcommand === 'kill') { - if (flags.go) die('`--go` só faz sentido com `next`.') + if (flags.stay) die('`--stay` não se aplica a `kill` — ele sempre volta ao main.') await cmdKill(Boolean(flags.force)) } else die(`subcomando desconhecido: ${subcommand} (esperado: next | plan | kill)`) } catch (error) {