diff --git a/.agents/shell/worktree.sh b/.agents/shell/worktree.sh index 6c3674a2..1fecdd9c 100644 --- a/.agents/shell/worktree.sh +++ b/.agents/shell/worktree.sh @@ -6,7 +6,16 @@ # 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): +# No terminal (esta função), o script também imprime a diretiva `launch +# opencode --model deepseek/deepseek-v4-flash --auto [--prompt …]` (OPS26): +# a função executa o cd e então a linha, e o TUI do opencode abre no worktree — +# `next` com `/work-issue` já enviado, `plan` sem prompt (autocomplete completa +# `/plan-`). Sem `exec` de propósito: ao sair do opencode, o terminal volta ao +# shell dentro do worktree. Presets são constantes em scripts/lib/worktree.mjs; +# o marcador TEQO_WORKTREE_TERMINAL=1 é o que separa esta superfície da do comando +# `/worktree` do opencode (que nunca lança TUI). `--stay` suprime cd e launch. +# +# Instalação (uma linha no profile; requer bash ou zsh — usa BASH_SOURCE, arrays e here-strings): # source /.agents/shell/worktree.sh # # Uso (terminal interativo): @@ -37,7 +46,7 @@ worktree() { done local out - out="$(node "$root/scripts/worktree.mjs" "$@")" || return $? + out="$(TEQO_WORKTREE_TERMINAL=1 node "$root/scripts/worktree.mjs" "$@")" || return $? printf '%s\n' "$out" if [ "$stay" -eq 1 ]; then @@ -53,4 +62,16 @@ worktree() { printf 'worktree: não consegui aplicar o `cd` da saída do script.\n' >&2 return 1 fi + + # Diretiva `launch` (só existe quando TEQO_WORKTREE_TERMINAL=1 e sem --stay): o + # script a gera a partir de constantes + dir slugificado (sem espaços), então o + # split por IFS=' ' abaixo é intencional e seguro — nunca eval. Falha do launch + # (ex.: opencode fora do PATH) só avisa: o worktree já está pronto e utilizável. + local launch + launch="$(printf '%s\n' "$out" | sed -n 's/^launch //p' | tail -n 1)" + if [ -n "$launch" ]; then + local -a launch_args + IFS=' ' read -r -a launch_args <<< "$launch" + "${launch_args[@]}" || printf 'worktree: launch do opencode falhou (código %s) — o worktree segue pronto em %s.\n' "$?" "$PWD" >&2 + fi } diff --git a/.agents/skills/worktree-next-issue/SKILL.md b/.agents/skills/worktree-next-issue/SKILL.md index 99737873..e85a4495 100644 --- a/.agents/skills/worktree-next-issue/SKILL.md +++ b/.agents/skills/worktree-next-issue/SKILL.md @@ -30,6 +30,8 @@ No opencode, isso é o comando **`/worktree next`** / **`/worktree kill`** (`.op **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). +**Launch do opencode (OPS26):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan` imprimem também a diretiva `launch opencode --model deepseek/deepseek-v4-flash --auto [--prompt /work-issue]` **antes** do `cd`, e a função a executa depois do cd — `next` abre o TUI com `/work-issue` já enviado; `plan` abre sem prompt (`/plan-` + autocomplete do TUI). 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) 1. Rode `pnpm worktree next` e leia a saída inteira. Se a fila estiver vazia, ele para sozinho — não crie worktree sem Issue. diff --git a/.opencode/commands/worktree.md b/.opencode/commands/worktree.md index 8e7527dd..2c314b23 100644 --- a/.opencode/commands/worktree.md +++ b/.opencode/commands/worktree.md @@ -8,4 +8,6 @@ Rode o script determinístico abaixo (não improvise a lógica) e reporte o resu - `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. +> **Este comando roda SEM o marcador `TEQO_WORKTREE_TERMINAL`, então nunca recebe a diretiva `launch`** — só o `cd`. O launch do opencode (OPS26) acontece apenas na função `worktree()` de `.agents/shell/worktree.sh` (terminal interativo); nunca abra um TUI aninhado a partir daqui. + !`node scripts/worktree.mjs $ARGUMENTS` diff --git a/AGENTS.md b/AGENTS.md index acbe066b..8587605d 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; 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. +- **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. **In the interactive terminal the shell function sets `TEQO_WORKTREE_TERMINAL=1` and `next`/`plan` additionally print a `launch opencode --model deepseek/deepseek-v4-flash --auto [--prompt /work-issue]` directive BEFORE the `cd` line, which the function executes after the cd (OPS26, 2026-08-10): `next` opens the opencode TUI in the worktree with `/work-issue` already sent (the OPS25 command runs the whole cycle), `plan` opens with the same presets and NO prompt (`/plan-` + TUI autocomplete — the opencode CLI's `--prompt` always auto-submits, there is no prefill-without-submit; gap registered for the opencode repo). The launch is without `exec` (exiting the TUI returns to the shell in the worktree, no auto-`kill`), `--stay` suppresses both the cd and the launch, the presets are constants in `scripts/lib/worktree.mjs` (`OPENCODE_PRESET_MODEL`, `OPENCODE_SKILL_COMMAND_BY_PURPOSE`, unit-tested), and **the `/worktree` opencode command never launches a TUI** (no marker — no nested TUI). `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 7d191007..36e695af 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):** **OPS26** (Issue #571) — `worktree next`/`plan` **no terminal** terminam com o TUI do opencode aberto no worktree: a função `worktree()` de `.agents/shell/worktree.sh` marca `TEQO_WORKTREE_TERMINAL=1` e o script imprime a diretiva `launch opencode --model deepseek/deepseek-v4-flash --auto [--prompt /work-issue]` **antes** do `cd` (que segue sendo a última linha — contrato OPS24 intacto); a função aplica o cd e executa a linha (sem `exec`: sair do TUI volta ao shell no worktree; sem auto-`kill`). `next` envia `/work-issue` (o comando OPS25 roda o ciclo completo); `plan` abre **sem prompt** — a CLI do opencode não tem pre-fill sem submit (`--prompt` sempre auto-envia; fallback acordado: `/plan-` + autocomplete; gap registrado para o repo do opencode). Presets são constantes em `scripts/lib/worktree.mjs` (`OPENCODE_PRESET_MODEL`, `OPENCODE_SKILL_COMMAND_BY_PURPOSE`, `opencodeLaunchDirective` — pura, unit-testada; trocar de modelo é editar a constante). **Sem o marcador (comando `/worktree` do opencode, automação) a diretiva não é impressa** — nunca abre TUI aninhado; `--stay` suprime cd e launch; `kill` intocado. Docs: AGENTS.md ("Per-worktree environments"), header/uso do script, `.opencode/commands/worktree.md`, skill `worktree-next-issue`. Sem migration/dados/access/UI. Intenção [`worktree-go-abre-opencode-com-presets.md`](plans/worktree-go-abre-opencode-com-presets.md); impl [`worktree-go-abre-opencode-com-presets-impl.md`](plans/worktree-go-abre-opencode-com-presets-impl.md). + **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):** **OPS25** (Issue #570) — atalhos diretos no TUI do opencode: `.opencode/commands/work-issue.md` e `plan-issue.md` (mecanismo nativo de commands, mesmo padrão de `issue.md`/`worktree.md`). O frontmatter leva `description` pt-BR (autocomplete do TUI) + `model: deepseek/deepseek-v4-flash` (pin do modelo de preferência, alinhado ao launch da OPS26 com `--model`); o corpo instrui carregar a skill de **mesmo nome** pela ferramenta de skills e segui-la ponta a ponta, apontando a fonte canônica `.agents/skills//SKILL.md` — **sem transcrever** (anti-goal da intenção) — com `$ARGUMENTS` repassado no fim. **Guard de acoplamento** `tests/unit/opencodeCommands.unit.spec.ts`: comando↔skill presos por nome exato (renome de skill → build falha em vez de comando quebrado silencioso). Sem migration/access/Consent/UI. Intenção [`opencode-comandos-work-issue-e-plan-issue.md`](plans/opencode-comandos-work-issue-e-plan-issue.md); impl [`opencode-comandos-work-issue-e-plan-issue-impl.md`](plans/opencode-comandos-work-issue-e-plan-issue-impl.md). diff --git a/docs/plans/opencode-comandos-work-issue-e-plan-issue.md b/docs/plans/opencode-comandos-work-issue-e-plan-issue.md index 5d6dfa47..e0ba278c 100644 --- a/docs/plans/opencode-comandos-work-issue-e-plan-issue.md +++ b/docs/plans/opencode-comandos-work-issue-e-plan-issue.md @@ -27,7 +27,7 @@ Toda sessão de trabalho no repo começa invocando `/work-issue` (executar uma I - O template instrui o agente a carregar a skill pelo nome exato e seguir o fluxo dela ponta a ponta, sem reescrever o conteúdo. - `$ARGUMENTS` é repassado (ex.: `/plan-issue `). - Cada comando leva `model: deepseek/deepseek-v4-flash` no frontmatter — sessão manual já abre no modelo de preferência (ver questão em aberto). -- Abertura automática a partir de `worktree next/plan` (OPS26) usa estes comandos como primeira mensagem. +- Abertura automática a partir de `worktree next/plan` (OPS26) usa estes comandos como primeira mensagem — `next` envia `/work-issue`; `plan` abre sem prompt (fallback acordado na OPS26: a CLI do opencode não tem pre-fill sem submit). ## Dados (intenção) diff --git a/docs/plans/worktree-go-abre-opencode-com-presets-impl.md b/docs/plans/worktree-go-abre-opencode-com-presets-impl.md new file mode 100644 index 00000000..96403b27 --- /dev/null +++ b/docs/plans/worktree-go-abre-opencode-com-presets-impl.md @@ -0,0 +1,79 @@ +# Impl: Worktree `go` abre o opencode com pré-seleções (DeepSeek V4 Flash, auto-approve, skill inicial) + +Status: aprovado +Atualizado em: 2026-08-10 +Issue: #571 +Intenção: docs/plans/worktree-go-abre-opencode-com-presets.md +Appetite restante: ~0,5–1 dia eng (herdado; cabe folgado) + +## Leitura da intenção + +- **Outcome:** `worktree next` no terminal termina com o TUI do opencode aberto no worktree (modelo `deepseek/deepseek-v4-flash`, `--auto`, `/work-issue` **enviado**); `worktree plan` abre com os mesmos presets e **sem** prompt (fallback acordado: `/plan-` + autocomplete; pre-fill sem submit quando o CLI do opencode ganhar a flag); sem marcador de terminal → **nenhum** launch (só `cd`); `--stay` suprime cd e launch; presets são constantes no script. +- **O que NÃO negociar:** sem TUI aninhado (`/worktree` do opencode não lança); sem alterar provisionamento; sem presets por-worktree via config commitada; launch sem `exec` (sair do TUI volta ao shell no worktree); sem auto-`kill` ao sair. +- **O que reavaliar:** a hipótese de "env/flag que só a função shell passa" — validada como **marcador de env** (aceite #3 exige que o script distinga a superfície); a localização das constantes (script vs função shell) — aceite #6 exige script. + +## Abordagem recomendada + +```mermaid +flowchart LR + S[worktree.sh função worktree] -->|TEQO_WORKTREE_TERMINAL=1 + args| N[node scripts/worktree.mjs] + N -->|print| CD["launch opencode --model … --auto [--prompt /work-issue]
cd "] + S -->|sed 's/^launch //p'| L[executa launch sem exec
foreground, TUI assume o terminal] + S -->|sed 's/^cd //p'| D[cd primeiro] + C[.opencode/commands/worktree.md] -->|sem marcador| N2[node scripts/worktree.mjs] + N2 -->|print| CD2["cd (sem launch — superfície nunca lança TUI)"] + C -->|aplica| D2[cd ] +``` + +**Opções consideradas:** A (marcador de env `TEQO_WORKTREE_TERMINAL=1` — linha `launch` só quando marcado) | B (script sempre imprime `launch`; cada chamador decide o que executar) | C (flag `--launch` no argv) +**Recomendação:** A — o aceite #3 exige que o launch **não aconteça** quando o script roda sem marcador (o `/worktree` do opencode, automação): o marcador é o que separa as duas superfícies; a superfície opencode nunca recebe a linha, então seu texto não precisa ensinar a ignorá-la. +**Rejeitadas:** B — inverte o aceite (a linha sairia para o `/worktree` também; o comando precisaria aprender a descartá-la, e automação futura ingênua executaria TUI aninhado); C — o shell repassa `"$@"` intacto; uma flag extra exigiria strip antes do repasse e poderia vazar para a superfície opencode. + +### Componentes / mudanças + +- **`opencodeLaunchDirective`** (`scripts/lib/worktree.mjs`): função pura `({ dir, purpose, terminal }) → string | null` — devolve `null` sem marcador; linha `launch opencode --model --auto` + `--prompt /work-issue` só para `purpose: 'next'` (plan **sem** prompt — fallback acordado). Constantes exportadas no lib (fonte única, unit-testável): `OPENCODE_PRESET_MODEL = 'deepseek/deepseek-v4-flash'`, `OPENCODE_SKILL_COMMAND_BY_PURPOSE = { next: '/work-issue', plan: null }`, `WORKTREE_TERMINAL_ENV = 'TEQO_WORKTREE_TERMINAL'`. Invariante documentada: dir é sempre `/` — sem espaços, word-splitting seguro. +- **`scripts/worktree.mjs`**: lê `process.env[WORKTREE_TERMINAL_ENV] === '1'`; em `cmdNext`/`cmdPlan`, no bloco `!stay`, imprime a linha `launch …` **antes** do `cd ` (a linha `cd` continua sendo a **última** — contrato OPS24 preservado para todos os consumidores). Header do script + bloco de uso ganham uma linha sobre o launch. +- **`.agents/shell/worktree.sh`**: chama `TEQO_WORKTREE_TERMINAL=1 node …`; depois do cd aplicado, extrai a última linha `launch …` (mesmo padrão sed do cd) e executa word-split (`$launch`, sem `eval` — formato fixo gerado pelo script, sem globs) **sem `exec`** (aceite #5: ao sair do TUI, volta ao shell no worktree); falha do launch (ex.: `opencode` ausente) imprime aviso e o worktree segue utilizável. +- **Migration:** sem migration (nenhuma mudança de schema/dados). +- **Access / Consent:** N/A (CLI de dev). +- **UI:** Impeccable A — N/A (sem UI de produto; TUI de terceiros). + +### Ordem das linhas na saída + +```text +… (bloco "Ambiente isolado") … +launch opencode /home//.cursor/worktrees/teqo/OPS26-x --model deepseek/deepseek-v4-flash --auto --prompt /work-issue +cd /home//.cursor/worktrees/teqo/OPS26-x +``` + +`cd` continua última linha (contrato documentado em AGENTS.md/skill/comando intacto); a função shell aplica cd primeiro e só então executa o launch (foreground). + +## Fases verificáveis + +1. **Derivação pura** — lib (`opencodeLaunchDirective` + constantes) + unit tests novos em `tests/unit/worktree.unit.spec.ts` (next → prompt presente; plan → sem prompt; terminal=false → null; constantes pinadas). Quota: pequena. +2. **Script + shell** — `cmdNext`/`cmdPlan` imprimem a diretiva; função shell aplica marcador, cd e launch; textos (header/uso do script, header do .sh). Verificação manual: `TEQO_WORKTREE_TERMINAL=1 node scripts/worktree.mjs …` (output shape) e simulação do parse sed com saída sintética (sem criar worktree/DBs reais). +3. **Docs** — AGENTS.md (parágrafo Per-worktree), `.opencode/commands/worktree.md` (nota: comando nunca lança TUI), `.agents/skills/worktree-next-issue/SKILL.md` (linha default-go), `docs/CHANGELOG-AGENTS.md` (entrada única, padrão das entregas). +4. **Gates** — `pnpm gate:fast` na iteração; `pnpm push` na entrega (gate:ci + push). + +## Rabbit holes / Não escopo (engenharia) + +- `opencode.json` por worktree / config global `~/.config/opencode` (presets são flags por invocação — rabbit hole cortado na intenção). +- Flag de pre-fill sem submit no CLI do opencode (gap registrado em `~/Code/propositions/opencode/prefill-prompt-sem-submit.md`; se ganhar a flag, `plan` passa a preencher — gatilho documentado na intenção). +- Modelo dinâmico por Issue (`model:` da Issue claimada vs constante) — constante fixa, fora de escopo da intenção. +- `worktree new` (OPS27, herda launch sem skill inicial) — item próprio. +- Execução via `eval` na função shell — desnecessária; word-splitting de formato fixo basta. +- Testes da função shell (bats etc.) — repo não tem suíte de shell; verificação via simulação do parse. + +## Riscos e mitigação + +- **TUI aninhado por engano** (marcador esquecido em automação futura): o default é **sem** launch — falha segura no sentido bom (nenhum TUI surpresa). Aceite #3 cobre. +- **Launch falho** (`opencode` fora do PATH, TUI com erro): cd já aplicado; aviso + worktree utilizável — nada destrutivo. +- **Word-splitting da linha launch** quebra se dir tiver espaços: impossível hoje (root fixo sem espaço + branch slugificado); invariante documentada no lib e na função shell. +- **Contrato "cd última linha"** quebrado por engano: diretiva impressa **antes** do cd; unit test do lib não cobre a ordem — coberto pela verificação manual de fase 2 e pelo texto do header. +- **Doc drift** (AGENTS.md/skill dizendo "cd última linha por padrão"): atualizações na fase 3 no MESMO PR (padrão do repo: docs e código juntos). + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto (6 critérios: next envia `/work-issue`; plan sem prompt; sem marcador → sem launch; `--stay` suprime; sem `exec` + sem auto-kill; constantes no script) +- [x] Invariantes AGENTS/engineering-standards (nenhuma mudança em src/, migrations, access, Consent, transações) +- [x] Testes de domínio previstos (unit da derivação pura; sem int/e2e — nenhum caminho de escrita/access muda) 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 index 8b6070e7..fa5ce386 100644 --- 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 @@ -30,7 +30,7 @@ flowchart LR **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. +- **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. _(Reavaliado na OPS26: o marcador `TEQO_WORKTREE_TERMINAL` foi adotado lá para a diretiva `launch` — só a função shell marca, e a superfície `/worktree` nunca recebe a linha.)_ - **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 diff --git a/scripts/lib/worktree.mjs b/scripts/lib/worktree.mjs index 75bdc7d1..ca10b41e 100644 --- a/scripts/lib/worktree.mjs +++ b/scripts/lib/worktree.mjs @@ -16,6 +16,22 @@ import { slugify } from '../../src/lib/slug.ts' */ 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' + +/** + * Skill command sent as the launch's initial message per purpose. `next` + * sends `/work-issue` (the OPS25 command executes the full cycle); `plan` + * sends nothing — the opencode CLI's `--prompt` always auto-submits, there is + * no prefill-without-submit flag, and the TUI autocomplete completes `/plan-` + * instead (gap registered for the opencode repo). Switch `plan` to + * `/plan-issue` here if the CLI ever gains prefill-without-submit. + */ +export const OPENCODE_SKILL_COMMAND_BY_PURPOSE = { next: '/work-issue', plan: null } + /** Total branch-name budget for plan branches — mirrors `branchNameForIssue`. */ const PLAN_BRANCH_MAX_LENGTH = 60 @@ -89,3 +105,21 @@ export const planBranchName = ({ bag = '', taken = new Set() }) => { if (!taken.has(candidate)) return candidate } } + +/** + * Launch directive for the opencode TUI, printed by `worktree next`/`plan` + * right before the `cd ` line when called from the interactive terminal + * (`WORKTREE_TERMINAL=1`): `launch opencode --model --auto + * [--prompt ]`. The shell function (`.agents/shell/worktree.sh`) + * applies the `cd` first, then word-splits and executes this line — the dir + * is always `/`, so the line never + * needs quoting. Returns `null` outside the terminal so the `/worktree` + * opencode command never launches a nested TUI. + */ +export const opencodeLaunchDirective = ({ dir, purpose, terminal = false }) => { + if (!terminal) return null + const prompt = OPENCODE_SKILL_COMMAND_BY_PURPOSE[purpose] + const args = [dir, '--model', OPENCODE_PRESET_MODEL, '--auto'] + if (prompt) args.push('--prompt', prompt) + return `launch opencode ${args.join(' ')}` +} diff --git a/scripts/worktree.mjs b/scripts/worktree.mjs index 84cd8288..1f885bc7 100644 --- a/scripts/worktree.mjs +++ b/scripts/worktree.mjs @@ -12,6 +12,17 @@ * `.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). + * 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 + * --auto --prompt /work-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 `/work-issue` + * já enviado (OPS26). Presets em + * scripts/lib/worktree.mjs. Sem o marcador (comando + * `/worktree` do opencode), a diretiva não é + * impressa — nunca abre TUI aninhado. * Também PROVISIONA o ambiente isolado do worktree: * porta do dev server + bancos próprios derivados * deterministicamente do branch (ver @@ -29,7 +40,12 @@ * sequencial `plans/plan-issue-` livre. Nenhum * deles (branch nem slot) colide com um `next` * posterior (prefixo minúsculo `plans/…`). - * Mesmo provisionamento isolado do `next`. + * Mesmo provisionamento isolado do `next`; no + * terminal, mesma diretiva `launch` — porém SEM + * `--prompt` (a CLI do opencode não tem pre-fill + * sem submit; `/plan-` + autocomplete do TUI). + * `--stay` suprime a linha `cd`; `--go` explícito + * continua aceito como no-op (era o antigo padrão). * 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); por @@ -59,11 +75,30 @@ import { worktreeEnvFileContents, worktreeEnvironment, } from './lib/worktree-env.mjs' -import { branchNameForIssue, planBranchName } from './lib/worktree.mjs' +import { + branchNameForIssue, + opencodeLaunchDirective, + planBranchName, + WORKTREE_TERMINAL_ENV, +} from './lib/worktree.mjs' const die = dieAgent('worktree') const WORKTREES_ROOT = join(homedir(), '.cursor', 'worktrees', 'teqo') +/** + * True when the interactive terminal shell function (`.agents/shell/worktree.sh`) + * calls us: it sets `TEQO_WORKTREE_TERMINAL=1` and executes the `launch` + * directive we print. Without the marker (the `/worktree` opencode command, + * automation) the launch line is never printed — no nested TUI. + */ +const terminalShell = process.env[WORKTREE_TERMINAL_ENV] === '1' + +/** Print the `launch` directive (only exists from the terminal shell); the `cd` line stays last. */ +const printLaunchDirective = ({ dir, purpose }) => { + const line = opencodeLaunchDirective({ dir, purpose, terminal: terminalShell }) + if (line) console.log(line) +} + /** Fixed compose project so `db:start`/provisioning always target ONE container. */ const SHARED_COMPOSE_PROJECT = 'teqo' /** Maintenance database on the shared local container — only ever localhost. */ @@ -332,7 +367,10 @@ const cmdNext = async (stay, skipMigrate) => { console.log(` banco dev: postgresql://teqo:teqo@localhost:5432/${env.devDatabase}`) console.log(` banco test: postgresql://teqo:teqo@localhost:5432/${env.testDatabase}`) - if (!stay) console.log(`cd ${dir}`) + if (!stay) { + printLaunchDirective({ dir, purpose: 'next' }) + console.log(`cd ${dir}`) + } } /** @@ -403,7 +441,10 @@ const cmdPlan = async (stay, 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 (!stay) console.log(`cd ${dir}`) + if (!stay) { + printLaunchDirective({ dir, purpose: 'plan' }) + console.log(`cd ${dir}`) + } } /** Generated database names referenced by a worktree's own env files. */ @@ -498,15 +539,22 @@ if (!subcommand) { 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(' 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( + ' função `worktree()` de .agents/shell/worktree.sh); no terminal (TEQO_WORKTREE_TERMINAL=1)', + ) + console.log(' imprime também a diretiva `launch opencode …` (OPS26: abre o TUI com') + console.log(' deepseek/deepseek-v4-flash + auto + /work-issue enviado); --stay suprime cd e') + console.log(' launch; --go 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( + ' minúsculo plans/… nunca colide com o branch - de `next`; no terminal,', + ) + console.log(' mesma diretiva `launch` porém sem --prompt (autocomplete completa /plan-)') 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') diff --git a/tests/unit/worktree.unit.spec.ts b/tests/unit/worktree.unit.spec.ts index 5bc2a488..695b50e7 100644 --- a/tests/unit/worktree.unit.spec.ts +++ b/tests/unit/worktree.unit.spec.ts @@ -16,8 +16,12 @@ import { import { branchNameForIssue, issueCodeAndSubject, + OPENCODE_PRESET_MODEL, + OPENCODE_SKILL_COMMAND_BY_PURPOSE, + opencodeLaunchDirective, PLAN_BRANCH_PREFIX, planBranchName, + WORKTREE_TERMINAL_ENV, } from '../../scripts/lib/worktree.mjs' type TestIssue = { @@ -128,6 +132,39 @@ describe('planBranchName (per-invocation planning worktrees)', () => { }) }) +describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26)', () => { + const dir = '/home/fsolla/.cursor/worktrees/teqo/OPS26-foo' + + it('returns null outside the terminal — the /worktree command never launches a TUI', () => { + expect(opencodeLaunchDirective({ dir, purpose: 'next' })).toBeNull() + expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: false })).toBeNull() + }) + + it('next launches with the preset model, --auto and the /work-issue command sent', () => { + expect(opencodeLaunchDirective({ dir, purpose: 'next', terminal: true })).toBe( + `launch opencode ${dir} --model deepseek/deepseek-v4-flash --auto --prompt /work-issue`, + ) + }) + + it('plan launches with the same presets but NO prompt (no prefill-without-submit)', () => { + expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true })).toBe( + `launch opencode ${dir} --model 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(WORKTREE_TERMINAL_ENV).toBe('TEQO_WORKTREE_TERMINAL') + expect(OPENCODE_SKILL_COMMAND_BY_PURPOSE).toEqual({ next: '/work-issue', plan: null }) + }) + + 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`, + ) + }) +}) + describe('worktreeEnvironment (per-worktree ports and databases)', () => { it('derives slot, port and database names deterministically from the branch', () => { const env = worktreeEnvironment({ branch: 'C15-foo', code: 'C15' })