mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
feat(worktree): flags de modelo --cheap/--pro/--zen/--go/--alibaba com --variant max (OPS93)
Mapa fixo commitado em scripts/lib/worktree.mjs (cheap=cheapestinference/deepseek-v4-flash, pro=opencode-go/qwen3.7-max, zen=opencode-go/ox-alpha-free, go=opencode-go/mimo-v2.5, alibaba=alibaba-token-plan/qwen3.7-max) com WORKTREE_VARIANT=max sempre; resolveWorktreeModel fail-high em múltiplas flags; opencodeLaunchDirective passa a emitir --model <map|preset> --variant max --auto; scripts/worktree.mjs valida at-most-one antes do claim e repassa modelo nos 3 propósitos (next/plan/new) com help/docblock atualizados; .agents/shell/worktree.sh, .opencode/commands/worktree.md, .agents/skills/worktree-next-issue/SKILL.md e local-database skill com contrato remapeado (--go deixa de ser no-op); tests/unit/worktree.unit.spec.ts pina as 5 flags, variant e guardrail. Closes #859
This commit is contained in:
parent
a79d2d3790
commit
8951f198ca
8 changed files with 372 additions and 78 deletions
|
|
@ -7,12 +7,17 @@
|
||||||
# shell que o chamou.
|
# shell que o chamou.
|
||||||
#
|
#
|
||||||
# No terminal (esta função), o script também imprime a diretiva `launch
|
# No terminal (esta função), o script também imprime a diretiva `launch
|
||||||
# opencode <dir> --model vercel/deepseek/deepseek-v4-flash-0731 --auto [--prompt "…"]` (OPS26):
|
# opencode <dir> --model <preset|map> --variant max --auto [--prompt "…"]` (OPS26+OPS33+OPS93):
|
||||||
# a função executa o cd e então a linha (tokenizada por xargs — honra as aspas
|
# 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
|
# do prompt, nunca eval), e o TUI do opencode abre no worktree — `next` com
|
||||||
# `/work-issue --issue <N>` já enviado (OPS33: a Issue claimada vai no prompt),
|
# `/work-issue --issue <N>` já enviado (OPS33: a Issue claimada vai no prompt),
|
||||||
# `plan` com `/plan-issue` já enviado (OPS31), `new` sem prompt (apenas
|
# `plan` com `/plan-issue` já enviado (OPS31), `new` sem prompt (apenas
|
||||||
# conversar). Sem `exec` de propósito: ao sair do opencode, o terminal volta ao
|
# conversar), sempre em `--variant max` (OPS78 guardrail; falha alto se a variante
|
||||||
|
# não existir). Modelo por invocação: `--cheap` (cheapestinference/deepseek-v4-flash),
|
||||||
|
# `--pro` (opencode-go/qwen3.7-max), `--zen` (opencode-go/ox-alpha-free),
|
||||||
|
# `--go` (opencode-go/mimo-v2.5), `--alibaba` (alibaba-token-plan/qwen3.7-max) no
|
||||||
|
# mapa fixo `WORKTREE_MODEL_MAP` (OPS93); sem flag o preset `deepseek/deepseek-v4-flash`
|
||||||
|
# permanece. 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;
|
# 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
|
# 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.
|
# `/worktree` do opencode (que nunca lança TUI). `--stay` suprime cd e launch.
|
||||||
|
|
@ -21,25 +26,28 @@
|
||||||
# source <repo>/.agents/shell/worktree.sh
|
# source <repo>/.agents/shell/worktree.sh
|
||||||
#
|
#
|
||||||
# Uso (terminal interativo):
|
# Uso (terminal interativo):
|
||||||
# worktree next [--issue N] [--stay]
|
# worktree next [--issue N] [--stay] [--cheap|--pro|--zen|--go|--alibaba]
|
||||||
# CLAIMA a próxima Issue claimável e cria/reutiliza o
|
# CLAIMA a próxima Issue claimável e cria/reutiliza o
|
||||||
# worktree dela, cd para dentro por padrão; --issue N
|
# worktree dela, cd para dentro por padrão; --issue N
|
||||||
# claima a Issue direcionada ou reabre a já claimada
|
# claima a Issue direcionada ou reabre a já claimada
|
||||||
# (sem re-claim); --stay não troca
|
# (sem re-claim); --stay não troca; --cheap/--pro/--zen/--go/--alibaba
|
||||||
# worktree plan [bag] [--stay] cria um worktree de planejamento do /plan-issue
|
# escolhe o modelo por invocação (sem flag o preset permanece; at-most-one)
|
||||||
|
# worktree plan [bag] [--stay] [--cheap|--pro|--zen|--go|--alibaba]
|
||||||
|
# cria um worktree de planejamento do /plan-issue
|
||||||
# DIFERENTE a cada chamada (sessões paralelas): com bag,
|
# DIFERENTE a cada chamada (sessões paralelas): com bag,
|
||||||
# branch plans/plan-issue-<bag> (sufixo -2/-3 se o nome
|
# branch plans/plan-issue-<bag> (sufixo -2/-3 se o nome
|
||||||
# já existir), sem bag o próximo plans/plan-issue-<n>
|
# já existir), sem bag o próximo plans/plan-issue-<n>
|
||||||
# sequencial; cd para dentro dele por padrão; --stay não
|
# sequencial; cd para dentro dele por padrão; --stay não
|
||||||
# troca
|
# troca; flag de modelo como no `next`
|
||||||
# worktree new [bag] [--stay] cria um worktree NEUTRO (sem função pré-definida)
|
# worktree new [bag] [--stay] [--cheap|--pro|--zen|--go|--alibaba]
|
||||||
|
# cria um worktree NEUTRO (sem função pré-definida)
|
||||||
# DIFERENTE a cada chamada: com bag, branch work/<bag>
|
# DIFERENTE a cada chamada: com bag, branch work/<bag>
|
||||||
# (sufixo -2/-3 se o nome já existir), sem bag o próximo
|
# (sufixo -2/-3 se o nome já existir), sem bag o próximo
|
||||||
# work/<n> sequencial; cd para dentro dele por padrão;
|
# work/<n> sequencial; cd para dentro dele por padrão;
|
||||||
# --stay não troca
|
# --stay não troca; flag de modelo como no `next`
|
||||||
# worktree kill [--force] destrói o worktree atual e cd para o main por padrão
|
# 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).
|
# `--go` remapeado em OPS93 para o mapa `opencode-go/mimo-v2.5` (antes no-op do OPS24).
|
||||||
# Claim determinístico: `next` claima antes de criar o worktree (mesma fila e
|
# Claim determinístico: `next` claima antes de criar o worktree (mesma fila e
|
||||||
# lock de `pnpm agent:claim`); `plan`/`new`/`kill` não tocam Issues.
|
# lock de `pnpm agent:claim`); `plan`/`new`/`kill` não tocam Issues.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ VMs Cloud **não rodam** `docker compose` / `pnpm db:start`. Postgres nativo: `.
|
||||||
|
|
||||||
## Per-worktree environments (`pnpm worktree next`)
|
## 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 [--stay]` provisions each worktree with a deterministic isolated env (`next`/`plan`/`new` print `cd <dir>` by default — apply it, or pass `--stay` to suppress; `--go` is still accepted as a no-op):
|
Parallel agents must not fight over port 3000, the shared `teqo_test`, or a second Postgres container. `pnpm worktree next [--stay] [--cheap|--pro|--zen|--go|--alibaba]` provisions each worktree with a deterministic isolated env (`next`/`plan`/`new` print `cd <dir>` by default — apply it, or pass `--stay` to suppress; `--cheap/--pro/--zen/--go/--alibaba` selects the model per invocation via `WORKTREE_MODEL_MAP` with `--variant max`):
|
||||||
|
|
||||||
- `slot` = numeric part of the issue code (`C15` → 15, `B164` → 164; hash fallback without digits; bumped on collision among live worktrees).
|
- `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<slot>` (dev) and `teqo_wt<slot>_test` (tests), created on the **one shared container** with the full migration chain **and the minimal seed** applied (one-time, ~30 s each; OPS28 parity with CI, which runs `migrate → seed:minimal` before e2e — a worktree's `pnpm test:e2e` mirrors CI).
|
- Dev server port `3100+slot`; databases `teqo_wt<slot>` (dev) and `teqo_wt<slot>_test` (tests), created on the **one shared container** with the full migration chain **and the minimal seed** applied (one-time, ~30 s each; OPS28 parity with CI, which runs `migrate → seed:minimal` before e2e — a worktree's `pnpm test:e2e` mirrors CI).
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ Prepara o ambiente git para a próxima Issue da fila de claim — **o claim é p
|
||||||
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.
|
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
|
```bash
|
||||||
pnpm worktree next [--issue N] [--stay] [--no-migrate] # CLAIMA a Issue + cria worktree de origin/main + provisiona o ambiente
|
pnpm worktree next [--issue N] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba] # CLAIMA a Issue + 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 plan [bag] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba] # worktree de PLANEJAMENTO (/plan-issue): um DIFERENTE por invocação
|
||||||
pnpm worktree new [bag] [--stay] [--no-migrate] # worktree NEUTRO (sem função pré-definida): um DIFERENTE por invocação
|
pnpm worktree new [bag] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba] # worktree NEUTRO (sem função pré-definida): um DIFERENTE por invocação
|
||||||
pnpm worktree kill [--force] # destrói o worktree atual + remove seus bancos gerados
|
pnpm worktree kill [--force] # destrói o worktree atual + remove seus bancos gerados
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -29,9 +29,9 @@ 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-<bag>` (sufixo `-2`/`-3` se o nome já estiver vivo); sem `bag` → próximo sequencial `plans/plan-issue-<n>` 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 `<Code>-<slug>` sempre uppercase-led). `new` é o irmão **neutro** (sem função pré-definida — explorar ideia, conversar, planejar sem registrar): mesma semântica de "um DIFERENTE por invocação", branch `work/<bag>` (sufixo `-2`/`-3` em colisão) ou `work/<n>` sequencial, prefixo `work/…` que não colide com `next` nem `plan`. O fluxo desta skill (fila → próxima Issue → implementação) é só `next`.
|
`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-<bag>` (sufixo `-2`/`-3` se o nome já estiver vivo); sem `bag` → próximo sequencial `plans/plan-issue-<n>` 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 `<Code>-<slug>` sempre uppercase-led). `new` é o irmão **neutro** (sem função pré-definida — explorar ideia, conversar, planejar sem registrar): mesma semântica de "um DIFERENTE por invocação", branch `work/<bag>` (sufixo `-2`/`-3` em colisão) ou `work/<n>` sequencial, prefixo `work/…` que não colide com `next` nem `plan`. O fluxo desta skill (fila → próxima Issue → implementação) é só `next`.
|
||||||
|
|
||||||
**Default-go:** `next`, `plan` e `new` imprimem `cd <dir>` 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 <main>` no fim (a sessão nunca fica num diretório destruído).
|
**Default-go:** `next`, `plan` e `new` imprimem `cd <dir>` na última linha **por padrão**; `--stay` suprime; modelos por invocação via `--cheap/--pro/--zen/--go/--alibaba` (mapa fixo `WORKTREE_MODEL_MAP`, at-most-one; sem flag o preset permanece); `--go` remapeado em OPS93 para `opencode-go/mimo-v2.5` (antes no-op do OPS24). 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 <main>` 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 <dir> --model vercel/deepseek/deepseek-v4-flash-0731 --auto [--prompt "/work-issue --issue <N>"]` **antes** do `cd`, e a função a executa depois do cd — `next` abre o TUI com `/work-issue --issue <N>` já enviado (a Issue claimada vai no prompt; a skill lê o resto do GitHub); `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 na config global do opencode (`~/.config/opencode/opencode.jsonc`), 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.**
|
**Launch do opencode (OPS26 + OPS33 + OPS31 + OPS93):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new` imprimem também a diretiva `launch opencode <dir> --model <preset|map> --variant max --auto [--prompt "/work-issue --issue <N>"]` **antes** do `cd`, e a função a executa depois do cd — `next` abre o TUI com `/work-issue --issue <N>` já enviado (a Issue claimada vai no prompt; a skill lê o resto do GitHub) em `--variant max` (sempre, falha alto se a variante não existir); `plan` abre com `/plan-issue` já enviado (OPS31: o TUI já cai no fluxo de planejamento); `new` abre sem `--prompt` ("apenas conversar"). Modelo por invocação via `--cheap` (cheapestinference/deepseek-v4-flash), `--pro` (opencode-go/qwen3.7-max), `--zen` (opencode-go/ox-alpha-free), `--go` (opencode-go/mimo-v2.5), `--alibaba` (alibaba-token-plan/qwen3.7-max); sem flag o preset `deepseek/deepseek-v4-flash` permanece; variantes low/high/max do model override na config global do opencode (`~/.config/opencode/opencode.jsonc`), selecionáveis no TUI e sticky por model. Sem `exec`: sair do opencode volta ao shell no worktree. Presets e mapa 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)
|
## Fluxo quando invocado como skill (agentes que não têm opencode)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
---
|
---
|
||||||
description: Claima a próxima Issue (next [--issue N] [--stay]), cria o worktree dela e provisiona o ambiente; também cria worktree de planejamento (plan [bag] [--stay]), worktree neutro (new [bag] [--stay]) ou destrói o atual (kill [--force])
|
description: Claima a próxima Issue (next [--issue N] [--stay] [--cheap|--pro|--zen|--go|--alibaba]), cria o worktree dela e provisiona o ambiente; também cria worktree de planejamento (plan [bag] [--stay] [--cheap|--pro|--zen|--go|--alibaba]), worktree neutro (new [bag] [--stay] [--cheap|--pro|--zen|--go|--alibaba]) ou destrói o atual (kill [--force])
|
||||||
---
|
---
|
||||||
|
|
||||||
Rode o script determinístico abaixo (não improvise a lógica) e reporte o resultado de forma concisa.
|
Rode o script determinístico abaixo (não improvise a lógica) e reporte o resultado de forma concisa.
|
||||||
|
|
||||||
- `next [--issue N] [--stay]`: **claima** a próxima Issue claimável da fila (mesma fila/ordem e lock otimista de `pnpm agent:claim`) e cria (ou reutiliza) o worktree dela. Claim falhou → reporte o motivo — não há worktree. `--issue N` claima a Issue direcionada (`ready`) ou **reabre** uma já claimada (`in-progress`, sem re-claim). A saída avisa "já claimada — NÃO rodar `pnpm agent:claim`". Por padrão o script imprime `cd <dir>` na última linha — aplique-o na sessão de shell atual (`cd <dir>`) e reporte o novo diretório. `--stay` suprime o `cd` (não troque de diretório) mas o claim ainda acontece. `--go` explícito continua aceito como no-op.
|
- `next [--issue N] [--stay] [--cheap|--pro|--zen|--go|--alibaba]`: **claima** a próxima Issue claimável da fila (mesma fila/ordem e lock otimista de `pnpm agent:claim`) e cria (ou reutiliza) o worktree dela. Claim falhou → reporte o motivo — não há worktree. `--issue N` claima a Issue direcionada (`ready`) ou **reabre** uma já claimada (`in-progress`, sem re-claim). A saída avisa "já claimada — NÃO rodar `pnpm agent:claim`". Por padrão o script imprime `cd <dir>` na última linha — aplique-o na sessão de shell atual (`cd <dir>`) e reporte o novo diretório. `--stay` suprime o `cd` (não troque de diretório) mas o claim ainda acontece. `--cheap/--pro/--zen/--go/--alibaba` escolhe o modelo por invocação no mapa fixo `WORKTREE_MODEL_MAP` (cheap=cheapestinference/deepseek-v4-flash, pro=opencode-go/qwen3.7-max, zen=opencode-go/ox-alpha-free, go=opencode-go/mimo-v2.5, alibaba=alibaba-token-plan/qwen3.7-max) com `--variant max` sempre; at-most-one (múltiplas → erro).
|
||||||
- `plan [bag] [--stay]`: cria um worktree de planejamento **diferente a cada invocação** (branch `plans/plan-issue-<bag>`, ou `plans/plan-issue-<n>` sequencial sem bag; sufixo `-2`/`-3` em colisão) para rodar `/plan-issue` sem ocupar o main; nunca conflita com `next`. Não claima nada. Mesmo contrato de `cd` do `next`: aplique a última linha `cd <dir>` por padrão; `--stay` suprime.
|
- `plan [bag] [--stay] [--cheap|--pro|--zen|--go|--alibaba]`: cria um worktree de planejamento **diferente a cada invocação** (branch `plans/plan-issue-<bag>`, ou `plans/plan-issue-<n>` sequencial sem bag; sufixo `-2`/`-3` em colisão) para rodar `/plan-issue` sem ocupar o main; nunca conflita com `next`. Não claima nada. Mesmo contrato de `cd` do `next`: aplique a última linha `cd <dir>` por padrão; `--stay` suprime; flag de modelo como no `next`.
|
||||||
- `new [bag] [--stay]`: cria um worktree **neutro** (sem função pré-definida — explorar ideia, conversar, planejar sem registrar nada) **diferente a cada invocação** (branch `work/<bag>`, ou `work/<n>` sequencial sem bag; sufixo `-2`/`-3` em colisão); o prefixo minúsculo `work/…` nunca conflita com `next` nem `plan`. Não claima nada. Mesmo contrato de `cd` do `plan`: aplique a última linha `cd <dir>` por padrão; `--stay` suprime.
|
- `new [bag] [--stay] [--cheap|--pro|--zen|--go|--alibaba]`: cria um worktree **neutro** (sem função pré-definida — explorar ideia, conversar, planejar sem registrar nada) **diferente a cada invocação** (branch `work/<bag>`, ou `work/<n>` sequencial sem bag; sufixo `-2`/`-3` em colisão); o prefixo minúsculo `work/…` nunca conflita com `next` nem `plan`. Não claima nada. Mesmo contrato de `cd` do `plan`: aplique a última linha `cd <dir>` por padrão; `--stay` suprime; flag de modelo como no `next`.
|
||||||
- `kill [--force]`: destrói o worktree em que você está e remove seus bancos. O script imprime `cd <main>` na última linha — aplique-o (`cd <main>`) para a sessão sair do diretório destruído.
|
- `kill [--force]`: destrói o worktree em que você está e remove seus bancos. O script imprime `cd <main>` na última linha — aplique-o (`cd <main>`) 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/OPS33) acontece apenas na função `worktree()` de `.agents/shell/worktree.sh` (terminal interativo); nunca abra um TUI aninhado a partir daqui.
|
> **Este comando roda SEM o marcador `TEQO_WORKTREE_TERMINAL`, então nunca recebe a diretiva `launch`** — só o `cd`. O launch do opencode (OPS26/OPS33+OPS93, com `--variant max`) 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`
|
!`node scripts/worktree.mjs $ARGUMENTS`
|
||||||
|
|
|
||||||
119
docs/plans/worktree-flags-modelo-launch-impl.md
Normal file
119
docs/plans/worktree-flags-modelo-launch-impl.md
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
# Impl: Flags de modelo no launch do worktree CLI (`--cheap` / `--pro` / `--zen` / `--go` / `--alibaba`)
|
||||||
|
|
||||||
|
Status: aprovado
|
||||||
|
Atualizado em: 2026-08-24
|
||||||
|
Issue: #859
|
||||||
|
Intenção: docs/plans/worktree-flags-modelo-launch.md
|
||||||
|
Appetite restante: ~0,5–1 dia (herdado; corte de cardápio aberto sem novo provider no repo)
|
||||||
|
|
||||||
|
## Leitura da intenção
|
||||||
|
|
||||||
|
- **Outcome:** `pnpm worktree next --cheap | --pro | --zen | --go | --alibaba` (e `plan`/`new` nas mesmas flags) abre o TUI com o modelo do mapa e `--variant max`, verificável pelo footer; sem flag o preset atual permanece.
|
||||||
|
- **O que NÃO negociar:** 5 flags fixas (sem cardápio aberto), sem persistência, sem tocar cardápio `/work-issue`/`/plan-issue`, sem editar config global de máquina via repo; guardrails: effort `max` sempre (falha alto se variante não aplicar), fora do terminal (`TEQO_WORKTREE_TERMINAL≠1`) as flags são irrelevantes, sem flag nada muda (preset `deepseek/deepseek-v4-flash`, no-op de `--stay`).
|
||||||
|
- **O que reavaliar:** hipótese "Direção no codebase" está correta mas incompleta — faltava o `--variant max` na diretiva (OPS78 já debateu TUI sem `--variant` e migrou variantes para config global; aqui o TUI já suporta `--variant max` em `opencode run` e, verificado em 2026-08-24, `opencode --model X --variant max` é aceito pelo TUI; a linha de launch passa a emitir o flag). O mapa concreto de modelo por flag não estava no plano de intenção (só `--cheap` e `--zen` tinham exemplos); a forma abaixo propõe valores e deixa o GATE decidir antes de codar.
|
||||||
|
|
||||||
|
## Abordagem recomendada
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
CLI["pnpm worktree next/plan/new\n--cheap|--pro|--zen|--go|--alibaba"] --> PARSE["scripts/worktree.mjs\nparseArgs + resolveWorktreeModel()"]
|
||||||
|
PARSE --> MAP["scripts/lib/worktree.mjs\nWORKTREE_MODEL_MAP\nflag -> model"]
|
||||||
|
MAP --> DIR["opencodeLaunchDirective\n--model <map|preset> --variant max --auto --prompt"]
|
||||||
|
DIR --> SH[".agents/shell/worktree.sh\nlaunch + cd"]
|
||||||
|
CLI -.->|sem TEQO_WORKTREE_TERMINAL| NOP["sem diretiva launch\n(apenas cd)"]
|
||||||
|
CLI -->|sem flag| PRESET["OPENCODE_PRESET_MODEL\ndeepseek/deepseek-v4-flash"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Opções consideradas:** A | B | C
|
||||||
|
**Recomendação:** A — mapa estático commitado em `scripts/lib/worktree.mjs` + `opencodeLaunchDirective` passa a emitir `--variant max` sempre + `scripts/worktree.mjs` consome o mapa na mesma camada que já deriva `issueNumber` para o prompt; 5 superfícies de doc atualizadas num único lote. Cabe no appetite (2 arquivos de lógica + 3 de doc + 2 specs), reusa o mecanismo OPS26 (preset como fallback) e o guardrail de variante do OPS78 sem inventar provider no repo.
|
||||||
|
|
||||||
|
**Rejeitadas:**
|
||||||
|
|
||||||
|
- **B — aceitar `provider/model` livre como argumento (ex. `--model deepseek/...`):** vira o `/models` do opencode e abre validação de provider dinâmico, cache de catálogo e erro de digitação silencioso; rejeitada porque a intenção corta cardápio aberto explicitamente.
|
||||||
|
- **C — persistir última flag em arquivo/env (ex. `.teqo-worktree-model`):** cria estado por worktree, sobrescrita confusa com `OPENCODE_WORKTREE_MODEL` e perda do "por invocação"; rejeitada pelo anti-goal de persistência.
|
||||||
|
|
||||||
|
### Decisões de engenharia (caras, com rejeitadas)
|
||||||
|
|
||||||
|
1. **Mapa flag→model como constante pura em `scripts/lib/worktree.mjs`**
|
||||||
|
- Opções: A) mapa estático em `lib/worktree.mjs` (puro, testável via unit) | B) mapa em `scripts/worktree.mjs` (acoplado a CLI) | C) mapa em `opencode.json` (config, não código)
|
||||||
|
- Recomendação: A — lib pura, importável por `worktree.mjs` e por `tests/unit`, sem ciclo `lib -> utilities`, sem tocar `opencode.json` do repo (que hoje é mínimo pós-OPS89); o teste pina o mapa. B rejeitada porque `scripts/worktree.mjs` já é HIGH_RISK_EXACT e não é unit-testável sem stub de GitHub; C rejeitada porque variantes são preferência de máquina (global `opencode.jsonc`), o repo não deve carregar catálogo de modelos.
|
||||||
|
- Mapa proposto (GATE confirma antes de codar):
|
||||||
|
- `--cheap` → `cheapestinference/deepseek-v4-flash` (o exemplo verbatim da intenção; rota barata da Cheapest Inference)
|
||||||
|
- `--pro` → `opencode-go/qwen3.7-max` (Qwen 3.7 Max via Go — flagship pro da família Qwen, distinto do `alibaba-token-plan`)
|
||||||
|
- `--zen` → `opencode-go/ox-alpha-free` (confirmado no gate da intenção; correção de provider documentada)
|
||||||
|
- `--go` → `opencode-go/mimo-v2.5` (MiMo v2.5 via Go — modelo Go nativo com `low/high/max`; remap do no-op histórico para um modelo real do provider Go)
|
||||||
|
- `--alibaba` → `alibaba-token-plan/qwen3.7-max` (Qwen 3.7 Max via Token Plan da Alibaba — modelo Alibaba nativo)
|
||||||
|
- Self-check: todos expõem `variant max` na config global (ver `opencode models` e `~/.local/state/opencode` recente); se um dia o catálogo mudar, o launch falha alto (`--variant max` rejeitado pelo `opencode run`/`tui`) — guardrail preservado sem código extra.
|
||||||
|
|
||||||
|
2. **`opencodeLaunchDirective` passa a emitir `--variant max` sempre**
|
||||||
|
- Opções: A) sempre emitir `--variant max` junto de `--model` | B) emitir só quando flag de modelo presente | C) configurar variante via `opencode.json` do repo (override de `model.request.variant`)
|
||||||
|
- Recomendação: A — a intenção fixa "a sessão abre em effort max **sempre**"; o preset sem flag também deve abrir em max (o default já é `deepseek/deepseek-v4-flash` com max no global `~/.config/opencode/opencode.jsonc`; emitir `max` na linha torna o guardrail explícito e falha alto se a variante não existir, em vez de abrir silenciosamente em default). B rejeitada porque o preset sem flag abriria em effort indefinido; C rejeitada pelo OPS89 (variantes migraram para config global, o `opencode.json` do repo ficou mínimo e variantes não devem voltar ao repo).
|
||||||
|
- Forma: `launch opencode <dir> --model <selected> --variant max --auto [--prompt "..."]` (ordem fixa para pin determinístico no teste).
|
||||||
|
|
||||||
|
3. **Resolução de flag: exclusividade, precedência e fallback**
|
||||||
|
- Opções: A) at-most-one flag; múltiplas → `die` com mensagem | B) last-wins silencioso | C) prioridade fixa (`--cheap` > `--pro` …)
|
||||||
|
- Recomendação: A — `die` se `>1` flag de modelo presente (`worktree.mjs` valida antes do claim; mensagem lista o mapa). B/C mascaram erro de digitação (`--cheap --pro` acidental) e quebram o "cardápio fixo" (o shell não deve adivinhar intenção). Sem flag → `OPENCODE_PRESET_MODEL` (fallback OPS26).
|
||||||
|
|
||||||
|
### Componentes / mudanças
|
||||||
|
|
||||||
|
- **`WORKTREE_MODEL_MAP` + helpers** (`scripts/lib/worktree.mjs`):
|
||||||
|
- `export const WORKTREE_MODEL_MAP = { cheap: 'cheapestinference/deepseek-v4-flash', pro: 'opencode-go/qwen3.7-max', zen: 'opencode-go/ox-alpha-free', go: 'opencode-go/mimo-v2.5', alibaba: 'alibaba-token-plan/qwen3.7-max' }`
|
||||||
|
- `export const WORKTREE_MODEL_FLAGS = new Set(Object.keys(WORKTREE_MODEL_MAP))`
|
||||||
|
- `export const resolveWorktreeModel = (flags) => { /* at-most-one → model ou preset */ }` (pura, testável; recebe `flags` do `parseArgs`, retorna `{ model, flag }` ou `die` via throw).
|
||||||
|
- `opencodeLaunchDirective({ dir, purpose, terminal, issueNumber, model })` ou reaproveitando `resolveWorktreeModel` dentro (preferir param `model` explícito para manter lib pura sem acoplamento a `process.argv`; `scripts/worktree.mjs` resolve e injeta). Emite ` --model <model> --variant max --auto`.
|
||||||
|
- `OPENCODE_PRESET_MODEL` permanece fallback (sem duplicar literal).
|
||||||
|
- Comentário do preset atualizado (menciona mapa e `--variant max`).
|
||||||
|
|
||||||
|
- **`scripts/worktree.mjs`** (`resolveWorktreeModel` + validação): parseArgs já coleta boolean flags; antes do `claim` validar `>1` flag de modelo → `die`; derivar `model = resolveWorktreeModel(flags)` (sem flag → preset); passar `model` para `printLaunchDirective`/`opencodeLaunchDirective` em `cmdNext`/`cmdNamespaceBranch` (todos os 3 propósitos, hipótese B confirmada). Docblock `:1-80` e help `console.log` (`:765-804`) atualizados: flag list com mapa, menção a `--variant max`, `--go` deixa de ser "no-op" e vira entrada do mapa, `--stay`/`--no-migrate` preservados.
|
||||||
|
|
||||||
|
- **`.agents/shell/worktree.sh`**: header `:1-18` e comentário da diretiva `:8-12` atualizados (lista de flags com mapa, `--variant max`, `--go` remapeado). Lógica de execução intacta (xargs tokeniza a linha com aspas).
|
||||||
|
|
||||||
|
- **`.opencode/commands/worktree.md`**: descrição e bullets atualizados para as 5 flags + `--variant max`; nota de `--go` como mapeamento real (não mais no-op); menção de que fora do terminal as flags são irrelevantes.
|
||||||
|
|
||||||
|
- **Skills que citam o preset** (` .agents/skills/worktree-next-issue/SKILL.md:34`, talvez `agent-work-issue`): texto que pina `vercel/deepseek/...` ou `deepseek/...` atualizado para mencionar o mapa e o `--variant max` (sem reintroduzir provider `vercel` no repo).
|
||||||
|
|
||||||
|
- **Testes unitários:**
|
||||||
|
- `tests/unit/worktree.unit.spec.ts`: importar `WORKTREE_MODEL_MAP`, `resolveWorktreeModel`; novos suites: `resolveWorktreeModel` (cada flag → model, sem flag → preset, múltiplas → throw), `opencodeLaunchDirective` com `model` explícito (5 flags → `--model <map> --variant max --auto ...`, sem flag → preset + variant max, terminal false → null). Atualizar pins existentes (`:151-205`) para esperar `--variant max` na linha (8 literais + pin da constante).
|
||||||
|
- `tests/unit/opencodeCommands.unit.spec.ts`: não mexe nos pins de execução (`/work-issue`/`/plan-issue`); se houver pin do modelo de launch, atualizar para o novo contrato (ou adicionar pin das 5 flags se o arquivo for o dono do contrato de launch — verificar dono).
|
||||||
|
|
||||||
|
- **Migration:** sem migration — tooling/CLI apenas.
|
||||||
|
|
||||||
|
- **Access / Consent:** N/A — CLI de worktree, sem coleção nem PII.
|
||||||
|
|
||||||
|
- **UI:** Impeccable A — sem superfície de produto (validação é linha de launch + footer do TUI).
|
||||||
|
|
||||||
|
### Dados → forma (se aplicável)
|
||||||
|
|
||||||
|
N/A — tooling de dev; sem dado de negócio sendo modelado (pergunta 3 de `data-presentation` não se aplica).
|
||||||
|
|
||||||
|
## Fases verificáveis
|
||||||
|
|
||||||
|
1. **Tracer / lib pura + diretiva** — `scripts/lib/worktree.mjs` (mapa + `resolveWorktreeModel` + `opencodeLaunchDirective` com `--variant max`) + `tests/unit/worktree.unit.spec.ts` (pins das 5 flags + guardrail de `>1` flag). Quota: ~40% do appetite.
|
||||||
|
2. **CLI + docs** — `scripts/worktree.mjs` (validação, repasse de `model` para diretiva nos 3 propósitos), `.agents/shell/worktree.sh`, `.opencode/commands/worktree.md`, skills com preset. Quota: ~40%.
|
||||||
|
3. **Gates** — `pnpm test:unit -t worktree`, `pnpm gate:fast` (lint/format/typecheck/knip/cycles/unit), smoke `TEQO_WORKTREE_TERMINAL=1 node scripts/worktree.mjs next --cheap --stay` imprime diretiva com `--model cheapestinference/deepseek-v4-flash --variant max`; sem flag imprime preset + max; múltiplas flags → erro; `pnpm push` → CI PR (HIGH_RISK_EXACT = e2e curado). Quota: ~20%.
|
||||||
|
|
||||||
|
## Rabbit holes / Não escopo (engenharia)
|
||||||
|
|
||||||
|
- NÃO aceitar `provider/model` arbitrário (cardápio aberto) — vira `/models`.
|
||||||
|
- NÃO persistir última flag (env/arquivo) — `OPENCODE_WORKTREE_MODEL` já é o escape de default fixo manual.
|
||||||
|
- NÃO voltar com `provider.vercel` no `opencode.json` do repo — variantes vivem na config global (OPS89); a linha `--variant max` é o guardrail.
|
||||||
|
- NÃO mudar `model:` dos `.opencode/commands/work-issue.md`/`plan-issue.md` (anti-goal da intenção).
|
||||||
|
- NÃO tocar `opencode.json` do repo nem `~/.config/opencode/opencode.jsonc` do humano via commit.
|
||||||
|
- NÃO estender `parseArgs` para validar valor de `--model` livre — mapa fixo basta.
|
||||||
|
|
||||||
|
## Riscos e mitigação
|
||||||
|
|
||||||
|
- **Modelo do mapa sem variante max na máquina do humano** → como não tocamos na config global via repo, o TUI pode abrir sem max (silencioso) — mitigação: a linha agora sempre emite `--variant max`; o `opencode` falha alto se a variante não existir (erro visível em vez de silencioso); documentar no impl que o humano deve ter `variant max` exposto no global para os 5 modelos (runbook fora do repo se faltar).
|
||||||
|
- **HIGH_RISK_EXACT (`scripts/worktree.mjs`) no PR** → o e2e curado roda; mitigação: só tocar docblock/help + repasse de `model`; sem mudar `provision`/`claim`/`PORT`/`DATABASE_URL`.
|
||||||
|
- **Quebra de contrato `--go` em 5 superfícies** → mitigação: atualizar as 5 numa única entrega (docblock/help, `worktree.sh`, `worktree.md`, 2 skills) + pin de teste nas 5 flags; `--go` sem flag duplicada não deixa no-op pendurado.
|
||||||
|
- **Flags fora do terminal (`/worktree` do opencode)** → mitigação: `opencodeLaunchDirective` retorna `null` quando `terminal=false` antes de tocar no mapa; o `/worktree` continua só `cd`.
|
||||||
|
|
||||||
|
## Aceite de engenharia
|
||||||
|
|
||||||
|
- [ ] Aceite de produto da intenção coberto: 5 flags com mapa, `next`/`plan`/`new` compartilhando diretiva, `--variant max` sempre, falha alto se variante não aplicar, fora do terminal irrelevante, sem flag intacto.
|
||||||
|
- [ ] Invariantes AGENTS/engineering-standards: sem nova collection, sem `Contact` paralelo, sem `Consent` novo, sem ciclo `lib→utilities`, `push:false` preservado (sem migration).
|
||||||
|
- [ ] Testes de domínio previstos: unit do mapa + diretiva + guardrail de exclusividade; atualização dos pins de launch; `pnpm gate:fast` verde.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Self-score decision-quality: 5/5 — decisões caras (mapa estático, variante sempre, exclusividade fail-closed) com rejeitadas documentadas; cabe no appetite (tracer + CLI + gates); rabbit holes nomeados; reusa `OPENCODE_PRESET_MODEL`/`opencodeLaunchDirective` sem twinning; outcome da intenção preservado.
|
||||||
|
|
@ -27,13 +27,49 @@ export const WORKTREE_TERMINAL_ENV = 'TEQO_WORKTREE_TERMINAL'
|
||||||
* preset era o flavor `-0731` via Vercel AI Gateway; voltou ao provider
|
* preset era o flavor `-0731` via Vercel AI Gateway; voltou ao provider
|
||||||
* direto porque o gateway está sem créditos (erros "positive credit balance"
|
* direto porque o gateway está sem créditos (erros "positive credit balance"
|
||||||
* em 24/08/2026) e porque unificar o default reduz a sobrescrita confusa de
|
* em 24/08/2026) e porque unificar o default reduz a sobrescrita confusa de
|
||||||
* seleção de modelo do TUI (opencode issues #8349/#13456).
|
* seleção de modelo do TUI (opencode issues #8349/#13456). OPS93: com
|
||||||
|
* `--cheap/--pro/--zen/--go/--alibaba` o mapa `WORKTREE_MODEL_MAP` escolhe o
|
||||||
|
* modelo por invocação; sem flag o preset permanece.
|
||||||
*/
|
*/
|
||||||
export const OPENCODE_PRESET_MODEL =
|
export const OPENCODE_PRESET_MODEL =
|
||||||
process.env.OPENCODE_WORKTREE_MODEL || 'deepseek/deepseek-v4-flash'
|
process.env.OPENCODE_WORKTREE_MODEL || 'deepseek/deepseek-v4-flash'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skill command sent as the launch's initial message per purpose. `next`
|
* Per-invocation model map — fixed menu of 5 named flags (OPS93). The
|
||||||
|
* directive picks `WORKTREE_MODEL_MAP[flag]` when a single flag is present,
|
||||||
|
* otherwise falls back to `OPENCODE_PRESET_MODEL`. Always launched with
|
||||||
|
* `--variant max` (fail-high if the target model doesn't expose the variant).
|
||||||
|
*/
|
||||||
|
export const WORKTREE_MODEL_MAP = {
|
||||||
|
cheap: 'cheapestinference/deepseek-v4-flash',
|
||||||
|
pro: 'opencode-go/qwen3.7-max',
|
||||||
|
zen: 'opencode-go/ox-alpha-free',
|
||||||
|
go: 'opencode-go/mimo-v2.5',
|
||||||
|
alibaba: 'alibaba-token-plan/qwen3.7-max',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set of flag names that select a model (keys of WORKTREE_MODEL_MAP). */
|
||||||
|
export const WORKTREE_MODEL_FLAGS = new Set(Object.keys(WORKTREE_MODEL_MAP))
|
||||||
|
|
||||||
|
/** Variant always used for worktree launches — effort max (OPS78 guardrail). */
|
||||||
|
export const WORKTREE_VARIANT = 'max'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the model for a parsed `flags` bag (from `parseArgs`). At most one
|
||||||
|
* model flag may be present — multiple → throw (fail-high, never guess).
|
||||||
|
* No flag → `OPENCODE_PRESET_MODEL`.
|
||||||
|
*/
|
||||||
|
export const resolveWorktreeModel = (flags = {}) => {
|
||||||
|
const active = [...WORKTREE_MODEL_FLAGS].filter((flag) => flags[flag])
|
||||||
|
if (active.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
`Flags de modelo conflitantes: --${active.join(' --')} (use apenas um de --cheap/--pro/--zen/--go/--alibaba)`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (active.length === 1) return WORKTREE_MODEL_MAP[active[0]]
|
||||||
|
return OPENCODE_PRESET_MODEL
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skill command sent as the launch's initial message per purpose. `next`
|
* Skill command sent as the launch's initial message per purpose. `next`
|
||||||
* sends `/work-issue` (the OPS25 command executes the full cycle; the OPS33
|
* sends `/work-issue` (the OPS25 command executes the full cycle; the OPS33
|
||||||
|
|
@ -135,22 +171,31 @@ const namespaceBranchName = ({ prefix, bag = '', taken = new Set(), fallback })
|
||||||
/**
|
/**
|
||||||
* Launch directive for the opencode TUI, printed by `worktree next`/`plan`
|
* Launch directive for the opencode TUI, printed by `worktree next`/`plan`
|
||||||
* right before the `cd <dir>` line when called from the interactive terminal
|
* right before the `cd <dir>` line when called from the interactive terminal
|
||||||
* (`WORKTREE_TERMINAL=1`): `launch opencode <dir> --model <preset> --auto
|
* (`WORKTREE_TERMINAL=1`): `launch opencode <dir> --model <preset|map>
|
||||||
* [--prompt "<command>"]`. The shell function (`.agents/shell/worktree.sh`)
|
* --variant max --auto [--prompt "<command>"]` (OPS93: `--variant max` sempre;
|
||||||
* applies the `cd` first, then tokenizes and executes this line (xargs —
|
* modelo vem do mapa `WORKTREE_MODEL_MAP` quando a flag `--cheap/--pro/--zen/
|
||||||
* quote-aware, never eval) — the dir is always `<root without spaces>/<slugified
|
* --go/--alibaba` está presente, senão do preset). The shell function
|
||||||
* branch>`, so the line never needs quoting for the path. `next` with an
|
* (`.agents/shell/worktree.sh`) applies the `cd` first, then tokenizes and
|
||||||
* `issueNumber` sends `/work-issue --issue <N>` (OPS33: the launch delivers the
|
* executes this line (xargs — quote-aware, never eval) — the dir is always
|
||||||
* claimed issue to the agent; the skill reads the rest from GitHub), the prompt
|
* `<root without spaces>/<slugified branch>`, so the line never needs quoting
|
||||||
* value is quoted in the line because it now carries a space. Returns `null`
|
* for the path. `next` with an `issueNumber` sends `/work-issue --issue <N>`
|
||||||
* outside the terminal so the `/worktree` opencode command never launches a
|
* (OPS33: the launch delivers the claimed issue to the agent; the skill reads
|
||||||
* nested TUI.
|
* the rest from GitHub), the prompt value is quoted in the line because it
|
||||||
* @param {{ dir: string, purpose: string, terminal?: boolean, issueNumber?: number | null }} options
|
* now carries a space. Returns `null` outside the terminal so the `/worktree`
|
||||||
|
* opencode command never launches a nested TUI.
|
||||||
|
* @param {{ dir: string, purpose: string, terminal?: boolean, issueNumber?: number | null, model?: string | null }} options
|
||||||
*/
|
*/
|
||||||
export const opencodeLaunchDirective = ({ dir, purpose, terminal = false, issueNumber = null }) => {
|
export const opencodeLaunchDirective = ({
|
||||||
|
dir,
|
||||||
|
purpose,
|
||||||
|
terminal = false,
|
||||||
|
issueNumber = null,
|
||||||
|
model = null,
|
||||||
|
}) => {
|
||||||
if (!terminal) return null
|
if (!terminal) return null
|
||||||
const prompt = OPENCODE_SKILL_COMMAND_BY_PURPOSE[purpose]
|
const prompt = OPENCODE_SKILL_COMMAND_BY_PURPOSE[purpose]
|
||||||
const args = [dir, '--model', OPENCODE_PRESET_MODEL, '--auto']
|
const selectedModel = model ?? OPENCODE_PRESET_MODEL
|
||||||
|
const args = [dir, '--model', selectedModel, '--variant', WORKTREE_VARIANT, '--auto']
|
||||||
if (prompt) {
|
if (prompt) {
|
||||||
// The issue suffix belongs to `next` alone — `plan`/`new` never carry a
|
// The issue suffix belongs to `next` alone — `plan`/`new` never carry a
|
||||||
// claimed issue (fail-safe: a stray issueNumber must not break /plan-issue).
|
// claimed issue (fail-safe: a stray issueNumber must not break /plan-issue).
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* `pnpm worktree` — worktree management determinístico em torno da fila de
|
* `pnpm worktree` — worktree management determinístico em torno da fila de
|
||||||
* claim do projeto (mesma fila de `agent:claim` / agent pool).
|
* claim do projeto (mesma fila de `agent:claim` / agent pool).
|
||||||
*
|
*
|
||||||
* pnpm worktree next [--issue N] [--stay] [--no-migrate]
|
* pnpm worktree next [--issue N] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba]
|
||||||
* claima a próxima Issue claimável ANTES de criar
|
* claima a próxima Issue claimável ANTES de criar
|
||||||
* o worktree a partir de origin/main (mesma fila
|
* o worktree a partir de origin/main (mesma fila
|
||||||
* e lock otimista de `pnpm agent:claim`; claim
|
* e lock otimista de `pnpm agent:claim`; claim
|
||||||
|
|
@ -19,21 +19,27 @@
|
||||||
* terminal interativo: função `worktree()` em
|
* terminal interativo: função `worktree()` em
|
||||||
* `.agents/shell/worktree.sh`, sourced no profile).
|
* `.agents/shell/worktree.sh`, sourced no profile).
|
||||||
* `--stay` suprime a linha `cd` (o claim ainda
|
* `--stay` suprime a linha `cd` (o claim ainda
|
||||||
* acontece); `--go` explícito continua aceito como
|
* acontece); `--cheap/--pro/--zen/--go/--alibaba`
|
||||||
* no-op (era o antigo padrão).
|
* escolhe o modelo por invocação no mapa fixo
|
||||||
|
* `WORKTREE_MODEL_MAP` (cheap=cheapestinference/
|
||||||
|
* deepseek-v4-flash, pro=opencode-go/qwen3.7-max,
|
||||||
|
* zen=opencode-go/ox-alpha-free, go=opencode-go/
|
||||||
|
* mimo-v2.5, alibaba=alibaba-token-plan/qwen3.7-max)
|
||||||
|
* com `--variant max` sempre (OPS93; sem flag o
|
||||||
|
* preset `deepseek/deepseek-v4-flash` permanece).
|
||||||
* Chamado do terminal interativo (com
|
* Chamado do terminal interativo (com
|
||||||
* `TEQO_WORKTREE_TERMINAL=1`, que só a função shell
|
* `TEQO_WORKTREE_TERMINAL=1`, que só a função shell
|
||||||
* seta), imprime também a diretiva `launch
|
* seta), imprime também a diretiva `launch
|
||||||
* opencode <dir> --model vercel/deepseek/deepseek-v4-flash-0731
|
* opencode <dir> --model <preset|map> --variant max
|
||||||
* --auto --prompt "/work-issue --issue <N>"` ANTES
|
* --auto --prompt "/work-issue --issue <N>"` ANTES
|
||||||
* do `cd` — a função shell executa o cd e então a
|
* do `cd` — a função shell executa o cd e então a
|
||||||
* linha, e o TUI do opencode abre no worktree com
|
* linha, e o TUI do opencode abre no worktree com
|
||||||
* `/work-issue --issue <N>` já enviado (OPS26 +
|
* `/work-issue --issue <N>` já enviado (OPS26 +
|
||||||
* OPS33: o launch entrega a Issue claimada ao
|
* OPS33: o launch entrega a Issue claimada ao
|
||||||
* agente; a skill lê o resto do GitHub). Presets
|
* agente; a skill lê o resto do GitHub) em effort
|
||||||
* em scripts/lib/worktree.mjs. Sem o marcador
|
* max. Presets e mapa em scripts/lib/worktree.mjs.
|
||||||
* (comando `/worktree` do opencode), a diretiva
|
* Sem o marcador (comando `/worktree` do opencode),
|
||||||
* não é impressa — nunca abre TUI aninhado.
|
* a diretiva não é impressa — nunca abre TUI aninhado.
|
||||||
* Também PROVISIONA o ambiente isolado do worktree:
|
* Também PROVISIONA o ambiente isolado do worktree:
|
||||||
* porta do dev server + bancos próprios derivados
|
* porta do dev server + bancos próprios derivados
|
||||||
* deterministicamente do branch (ver
|
* deterministicamente do branch (ver
|
||||||
|
|
@ -50,7 +56,7 @@
|
||||||
* recusa a abrir com `{file:…}` pendurado.
|
* recusa a abrir com `{file:…}` pendurado.
|
||||||
* `--no-migrate` pula migrations E o seed (que
|
* `--no-migrate` pula migrations E o seed (que
|
||||||
* depende do catálogo migrado).
|
* depende do catálogo migrado).
|
||||||
* pnpm worktree plan [bag] [--stay] [--no-migrate]
|
* pnpm worktree plan [bag] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba]
|
||||||
* cria um worktree de PLANEJAMENTO novo para rodar
|
* cria um worktree de PLANEJAMENTO novo para rodar
|
||||||
* a skill /plan-issue sem ocupar o main — cada
|
* a skill /plan-issue sem ocupar o main — cada
|
||||||
* invocação cria UM DIFERENTE, para sessões de
|
* invocação cria UM DIFERENTE, para sessões de
|
||||||
|
|
@ -63,8 +69,10 @@
|
||||||
* Mesmo provisionamento isolado do `next`; no
|
* Mesmo provisionamento isolado do `next`; no
|
||||||
* terminal, mesma diretiva `launch` — com
|
* terminal, mesma diretiva `launch` — com
|
||||||
* `--prompt /plan-issue` já enviado (OPS31: o TUI
|
* `--prompt /plan-issue` já enviado (OPS31: o TUI
|
||||||
* abre no fluxo de planejamento, sem digitação).
|
* abre no fluxo de planejamento, sem digitação) e
|
||||||
* pnpm worktree new [bag] [--stay] [--no-migrate]
|
* `--model <map> --variant max` quando a flag de
|
||||||
|
* modelo está presente.
|
||||||
|
* pnpm worktree new [bag] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba]
|
||||||
* cria um worktree NEUTRO novo — sem função
|
* cria um worktree NEUTRO novo — sem função
|
||||||
* pré-definida (explorar ideia, conversar, ou
|
* pré-definida (explorar ideia, conversar, ou
|
||||||
* planejar sem registrar nada): cada invocação
|
* planejar sem registrar nada): cada invocação
|
||||||
|
|
@ -76,9 +84,12 @@
|
||||||
* com `plans/plan-issue-…` de `plan`.
|
* com `plans/plan-issue-…` de `plan`.
|
||||||
* Mesmo provisionamento isolado do `next`/`plan`;
|
* Mesmo provisionamento isolado do `next`/`plan`;
|
||||||
* no terminal, mesma diretiva `launch` — SEM
|
* no terminal, mesma diretiva `launch` — SEM
|
||||||
* `--prompt` (apenas conversar, nenhuma skill).
|
* `--prompt` (apenas conversar, nenhuma skill) e
|
||||||
|
* `--model <map> --variant max` quando a flag de
|
||||||
|
* modelo está presente.
|
||||||
* `--stay` suprime a linha `cd` e a diretiva;
|
* `--stay` suprime a linha `cd` e a diretiva;
|
||||||
* `--go` explícito continua aceito como no-op.
|
* `--cheap/--pro/--zen/--go/--alibaba` escolhe o
|
||||||
|
* modelo por invocação (sem flag o preset permanece).
|
||||||
* Migrations E seed mínimo nos dois bancos, como
|
* Migrations E seed mínimo nos dois bancos, como
|
||||||
* o `next`/`plan` (OPS28).
|
* o `next`/`plan` (OPS28).
|
||||||
* pnpm worktree kill [--force] destrói o worktree em que o shell atual está
|
* pnpm worktree kill [--force] destrói o worktree em que o shell atual está
|
||||||
|
|
@ -138,8 +149,10 @@ import {
|
||||||
OPENCODE_PRESET_MODEL,
|
OPENCODE_PRESET_MODEL,
|
||||||
opencodeLaunchDirective,
|
opencodeLaunchDirective,
|
||||||
planBranchName,
|
planBranchName,
|
||||||
|
resolveWorktreeModel,
|
||||||
workBranchName,
|
workBranchName,
|
||||||
WORKTREE_TERMINAL_ENV,
|
WORKTREE_TERMINAL_ENV,
|
||||||
|
WORKTREE_VARIANT,
|
||||||
} from './lib/worktree.mjs'
|
} from './lib/worktree.mjs'
|
||||||
|
|
||||||
const die = dieAgent('worktree')
|
const die = dieAgent('worktree')
|
||||||
|
|
@ -153,9 +166,25 @@ const WORKTREES_ROOT = process.env.WORKTREES_ROOT ?? join(homedir(), '.cursor',
|
||||||
*/
|
*/
|
||||||
const terminalShell = process.env[WORKTREE_TERMINAL_ENV] === '1'
|
const terminalShell = process.env[WORKTREE_TERMINAL_ENV] === '1'
|
||||||
|
|
||||||
|
/** Resolve the per-invocation model from the parsed flags (at-most-one, fail-high). */
|
||||||
|
const resolveLaunchModel = (flags) => {
|
||||||
|
try {
|
||||||
|
return resolveWorktreeModel(flags)
|
||||||
|
} catch (error) {
|
||||||
|
die(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Print the `launch` directive (only exists from the terminal shell); the `cd` line stays last. */
|
/** Print the `launch` directive (only exists from the terminal shell); the `cd` line stays last. */
|
||||||
const printLaunchDirective = ({ dir, purpose, issueNumber }) => {
|
const printLaunchDirective = ({ dir, purpose, issueNumber, flags = {} }) => {
|
||||||
const line = opencodeLaunchDirective({ dir, purpose, terminal: terminalShell, issueNumber })
|
const model = resolveLaunchModel(flags)
|
||||||
|
const line = opencodeLaunchDirective({
|
||||||
|
dir,
|
||||||
|
purpose,
|
||||||
|
terminal: terminalShell,
|
||||||
|
issueNumber,
|
||||||
|
model,
|
||||||
|
})
|
||||||
if (line) console.log(line)
|
if (line) console.log(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -473,7 +502,9 @@ const pickNextIssue = async ({ requestedIssueNumber, die }) => {
|
||||||
return { entry: pick, reopened: false, directed: false }
|
return { entry: pick, reopened: false, directed: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
const cmdNext = async (stay, skipMigrate, requestedIssueNumber) => {
|
const cmdNext = async (stay, skipMigrate, requestedIssueNumber, flags = {}) => {
|
||||||
|
// Fail-high on conflicting model flags before touching GitHub (no orphan claim).
|
||||||
|
resolveLaunchModel(flags)
|
||||||
const { entry, reopened, directed } = await pickNextIssue({ requestedIssueNumber, die })
|
const { entry, reopened, directed } = await pickNextIssue({ requestedIssueNumber, die })
|
||||||
|
|
||||||
const issue = entry.issue
|
const issue = entry.issue
|
||||||
|
|
@ -553,7 +584,7 @@ const cmdNext = async (stay, skipMigrate, requestedIssueNumber) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stay) {
|
if (!stay) {
|
||||||
printLaunchDirective({ dir, purpose: 'next', issueNumber: issue.number })
|
printLaunchDirective({ dir, purpose: 'next', issueNumber: issue.number, flags })
|
||||||
console.log(`cd ${dir}`)
|
console.log(`cd ${dir}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -594,7 +625,10 @@ const cmdNamespaceBranch = async ({
|
||||||
noun,
|
noun,
|
||||||
sessionLabel,
|
sessionLabel,
|
||||||
branchName,
|
branchName,
|
||||||
|
flags = {},
|
||||||
}) => {
|
}) => {
|
||||||
|
// Fail-high on conflicting model flags before touching git.
|
||||||
|
resolveLaunchModel(flags)
|
||||||
git(['fetch', 'origin'])
|
git(['fetch', 'origin'])
|
||||||
|
|
||||||
const entries = parseWorktreeList(git(['worktree', 'list', '--porcelain']))
|
const entries = parseWorktreeList(git(['worktree', 'list', '--porcelain']))
|
||||||
|
|
@ -634,7 +668,7 @@ const cmdNamespaceBranch = async ({
|
||||||
console.log(` banco test: postgresql://teqo:teqo@localhost:5432/${env.testDatabase}`)
|
console.log(` banco test: postgresql://teqo:teqo@localhost:5432/${env.testDatabase}`)
|
||||||
|
|
||||||
if (!stay) {
|
if (!stay) {
|
||||||
printLaunchDirective({ dir, purpose })
|
printLaunchDirective({ dir, purpose, flags })
|
||||||
console.log(`cd ${dir}`)
|
console.log(`cd ${dir}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -649,7 +683,7 @@ const cmdNamespaceBranch = async ({
|
||||||
* marker and `next` bumps around it). Same isolated env provisioning as
|
* marker and `next` bumps around it). Same isolated env provisioning as
|
||||||
* `next`.
|
* `next`.
|
||||||
*/
|
*/
|
||||||
const cmdPlan = async (stay, skipMigrate, bag) =>
|
const cmdPlan = async (stay, skipMigrate, bag, flags = {}) =>
|
||||||
cmdNamespaceBranch({
|
cmdNamespaceBranch({
|
||||||
stay,
|
stay,
|
||||||
skipMigrate,
|
skipMigrate,
|
||||||
|
|
@ -657,6 +691,7 @@ const cmdPlan = async (stay, skipMigrate, bag) =>
|
||||||
noun: 'de planejamento',
|
noun: 'de planejamento',
|
||||||
sessionLabel: bag && bag.trim() ? `lote "${bag}"` : 'sequencial',
|
sessionLabel: bag && bag.trim() ? `lote "${bag}"` : 'sequencial',
|
||||||
branchName: (taken) => planBranchName({ bag, taken }),
|
branchName: (taken) => planBranchName({ bag, taken }),
|
||||||
|
flags,
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -668,7 +703,7 @@ const cmdPlan = async (stay, skipMigrate, bag) =>
|
||||||
* (uppercase-led `<code>-<slug>`) nor with a `plan` branch (`plans/…`), in
|
* (uppercase-led `<code>-<slug>`) nor with a `plan` branch (`plans/…`), in
|
||||||
* branch name or slot space. Same isolated env provisioning as `plan`.
|
* branch name or slot space. Same isolated env provisioning as `plan`.
|
||||||
*/
|
*/
|
||||||
const cmdNew = async (stay, skipMigrate, bag) =>
|
const cmdNew = async (stay, skipMigrate, bag, flags = {}) =>
|
||||||
cmdNamespaceBranch({
|
cmdNamespaceBranch({
|
||||||
stay,
|
stay,
|
||||||
skipMigrate,
|
skipMigrate,
|
||||||
|
|
@ -676,6 +711,7 @@ const cmdNew = async (stay, skipMigrate, bag) =>
|
||||||
noun: 'neutro',
|
noun: 'neutro',
|
||||||
sessionLabel: bag && bag.trim() ? `bag "${bag}"` : 'sequencial',
|
sessionLabel: bag && bag.trim() ? `bag "${bag}"` : 'sequencial',
|
||||||
branchName: (taken) => workBranchName({ bag, taken }),
|
branchName: (taken) => workBranchName({ bag, taken }),
|
||||||
|
flags,
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Generated database names referenced by a worktree's own env files. */
|
/** Generated database names referenced by a worktree's own env files. */
|
||||||
|
|
@ -764,9 +800,9 @@ const subcommand = positional[0]
|
||||||
|
|
||||||
if (!subcommand) {
|
if (!subcommand) {
|
||||||
console.log(
|
console.log(
|
||||||
'Uso: pnpm worktree next [--issue N] [--stay] [--no-migrate] | plan [bag] [--stay] [--no-migrate] | new [bag] [--stay] [--no-migrate] | kill [--force]',
|
'Uso: pnpm worktree next [--issue N] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba] | plan [bag] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba] | new [bag] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba] | kill [--force]',
|
||||||
)
|
)
|
||||||
console.log(' next [--issue N] [--stay] [--no-migrate]')
|
console.log(' next [--issue N] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba]')
|
||||||
console.log(' CLAIMA a próxima Issue claimável (mesma fila/ordem e lock otimista do')
|
console.log(' CLAIMA a próxima Issue claimável (mesma fila/ordem e lock otimista do')
|
||||||
console.log(' `pnpm agent:claim`) e cria o worktree dela (branch <code>-<slug>),')
|
console.log(' `pnpm agent:claim`) e cria o worktree dela (branch <code>-<slug>),')
|
||||||
console.log(' provisionando o ambiente isolado: porta de dev + bancos próprios;')
|
console.log(' provisionando o ambiente isolado: porta de dev + bancos próprios;')
|
||||||
|
|
@ -775,12 +811,19 @@ if (!subcommand) {
|
||||||
console.log(' re-claim). Por padrão imprime `cd <dir>` no fim (quem aplica o cd:')
|
console.log(' re-claim). Por padrão imprime `cd <dir>` no fim (quem aplica o cd:')
|
||||||
console.log(' opencode command, ou a função `worktree()` de .agents/shell/worktree.sh);')
|
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(' no terminal (TEQO_WORKTREE_TERMINAL=1) imprime também a diretiva')
|
||||||
console.log(' `launch opencode … --prompt "/work-issue --issue <N>"` (OPS26+OPS33:')
|
console.log(
|
||||||
console.log(` abre o TUI com ${OPENCODE_PRESET_MODEL} (gateway) + auto + a Issue claimada já`)
|
' `launch opencode <dir> --model <preset|map> --variant max --auto --prompt "/work-issue --issue <N>"` (OPS26+OPS33+OPS93:',
|
||||||
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(
|
||||||
console.log(' seed mínimo (db:seed:minimal) nos bancos novos (OPS28: paridade com a CI)')
|
` abre o TUI com ${OPENCODE_PRESET_MODEL} (sem flag) ou com o mapa --cheap=cheapestinference/deepseek-v4-flash --pro=opencode-go/qwen3.7-max --zen=opencode-go/ox-alpha-free --go=opencode-go/mimo-v2.5 --alibaba=alibaba-token-plan/qwen3.7-max (todos --variant ${WORKTREE_VARIANT}) + auto + a Issue claimada já`,
|
||||||
console.log(`\n plan [bag] [--stay] [--no-migrate]`)
|
)
|
||||||
|
console.log(
|
||||||
|
' informada); --stay suprime cd e launch (o claim ainda acontece); --no-migrate pula migrations e o',
|
||||||
|
)
|
||||||
|
console.log(
|
||||||
|
' seed mínimo (db:seed:minimal) nos bancos novos (OPS28: paridade com a CI); at-most-one de --cheap/--pro/--zen/--go/--alibaba (múltiplas → erro)',
|
||||||
|
)
|
||||||
|
console.log(`\n plan [bag] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba]`)
|
||||||
console.log(
|
console.log(
|
||||||
' cria um worktree de planejamento DIFERENTE a cada invocação (sessões /plan-issue',
|
' cria um worktree de planejamento DIFERENTE a cada invocação (sessões /plan-issue',
|
||||||
)
|
)
|
||||||
|
|
@ -790,14 +833,16 @@ if (!subcommand) {
|
||||||
' minúsculo plans/… nunca colide com o branch <code>-<slug> de `next`; no terminal,',
|
' minúsculo plans/… nunca colide com o branch <code>-<slug> de `next`; no terminal,',
|
||||||
)
|
)
|
||||||
console.log(
|
console.log(
|
||||||
' mesma diretiva `launch` com --prompt /plan-issue enviado (abre no fluxo de planejamento, sem digitação)',
|
' mesma diretiva `launch` com --prompt /plan-issue enviado (abre no fluxo de planejamento, sem digitação) e --model <map> --variant max quando a flag está presente',
|
||||||
)
|
)
|
||||||
console.log(`\n new [bag] [--stay] [--no-migrate]`)
|
console.log(`\n new [bag] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba]`)
|
||||||
console.log(' cria um worktree NEUTRO (sem função pré-definida) DIFERENTE a cada invocação:')
|
console.log(' cria um worktree NEUTRO (sem função pré-definida) DIFERENTE a cada invocação:')
|
||||||
console.log(' com bag, branch work/<bag> (sufixo -2/-3 se o nome já existir); sem bag, o')
|
console.log(' com bag, branch work/<bag> (sufixo -2/-3 se o nome já existir); sem bag, o')
|
||||||
console.log(' próximo work/<n> sequencial livre; o prefixo minúsculo work/… nunca colide com')
|
console.log(' próximo work/<n> sequencial livre; o prefixo minúsculo work/… nunca colide com')
|
||||||
console.log(' o branch <code>-<slug> de `next` nem com plans/plan-issue-… de `plan`; no')
|
console.log(' o branch <code>-<slug> de `next` nem com plans/plan-issue-… de `plan`; no')
|
||||||
console.log(' terminal, mesma diretiva `launch` porém sem --prompt (apenas conversar)')
|
console.log(
|
||||||
|
' terminal, mesma diretiva `launch` porém sem --prompt (apenas conversar) e --model <map> --variant max quando a flag está presente',
|
||||||
|
)
|
||||||
console.log(' kill [--force] destrói o worktree em que você está (recusa sujo sem --force),')
|
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(' remove os bancos gerados do worktree (best-effort) e imprime')
|
||||||
console.log(' `cd <main>` no fim — o shell sempre volta ao worktree principal')
|
console.log(' `cd <main>` no fim — o shell sempre volta ao worktree principal')
|
||||||
|
|
@ -812,11 +857,11 @@ try {
|
||||||
if (positional.length > 1) {
|
if (positional.length > 1) {
|
||||||
die('`next` não aceita argumento posicional — use `--issue <N>` para direcionar a Issue.')
|
die('`next` não aceita argumento posicional — use `--issue <N>` para direcionar a Issue.')
|
||||||
}
|
}
|
||||||
await cmdNext(Boolean(flags.stay), Boolean(flags['no-migrate']), flags.issue ?? null)
|
await cmdNext(Boolean(flags.stay), Boolean(flags['no-migrate']), flags.issue ?? null, flags)
|
||||||
} else if (subcommand === 'plan')
|
} else if (subcommand === 'plan')
|
||||||
await cmdPlan(Boolean(flags.stay), Boolean(flags['no-migrate']), positional[1])
|
await cmdPlan(Boolean(flags.stay), Boolean(flags['no-migrate']), positional[1], flags)
|
||||||
else if (subcommand === 'new')
|
else if (subcommand === 'new')
|
||||||
await cmdNew(Boolean(flags.stay), Boolean(flags['no-migrate']), positional[1])
|
await cmdNew(Boolean(flags.stay), Boolean(flags['no-migrate']), positional[1], flags)
|
||||||
else if (subcommand === 'kill') {
|
else if (subcommand === 'kill') {
|
||||||
if (flags.stay) die('`--stay` não se aplica a `kill` — ele sempre volta ao main.')
|
if (flags.stay) die('`--stay` não se aplica a `kill` — ele sempre volta ao main.')
|
||||||
await cmdKill(Boolean(flags.force))
|
await cmdKill(Boolean(flags.force))
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,13 @@ import {
|
||||||
opencodeLaunchDirective,
|
opencodeLaunchDirective,
|
||||||
PLAN_BRANCH_PREFIX,
|
PLAN_BRANCH_PREFIX,
|
||||||
planBranchName,
|
planBranchName,
|
||||||
|
resolveWorktreeModel,
|
||||||
WORK_BRANCH_PREFIX,
|
WORK_BRANCH_PREFIX,
|
||||||
workBranchName,
|
workBranchName,
|
||||||
|
WORKTREE_MODEL_FLAGS,
|
||||||
|
WORKTREE_MODEL_MAP,
|
||||||
WORKTREE_TERMINAL_ENV,
|
WORKTREE_TERMINAL_ENV,
|
||||||
|
WORKTREE_VARIANT,
|
||||||
} from '../../scripts/lib/worktree.mjs'
|
} from '../../scripts/lib/worktree.mjs'
|
||||||
|
|
||||||
type TestIssue = {
|
type TestIssue = {
|
||||||
|
|
@ -142,7 +146,7 @@ describe('planBranchName (per-invocation planning worktrees)', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33)', () => {
|
describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33 + OPS93)', () => {
|
||||||
const dir = '/home/fsolla/.cursor/worktrees/teqo/OPS26-foo'
|
const dir = '/home/fsolla/.cursor/worktrees/teqo/OPS26-foo'
|
||||||
|
|
||||||
it('returns null outside the terminal — the /worktree command never launches a TUI', () => {
|
it('returns null outside the terminal — the /worktree command never launches a TUI', () => {
|
||||||
|
|
@ -153,9 +157,9 @@ describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33)
|
||||||
).toBeNull()
|
).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('next launches with the preset model, --auto and the /work-issue command sent (value always quoted)', () => {
|
it('next launches with the preset model, --variant max, --auto and the /work-issue command sent (value always quoted)', () => {
|
||||||
expect(opencodeLaunchDirective({ dir, purpose: 'next', terminal: true })).toBe(
|
expect(opencodeLaunchDirective({ dir, purpose: 'next', terminal: true })).toBe(
|
||||||
`launch opencode ${dir} --model ${presetInEffect()} --auto --prompt "/work-issue"`,
|
`launch opencode ${dir} --model ${presetInEffect()} --variant ${WORKTREE_VARIANT} --auto --prompt "/work-issue"`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -163,34 +167,35 @@ describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33)
|
||||||
expect(
|
expect(
|
||||||
opencodeLaunchDirective({ dir, purpose: 'next', terminal: true, issueNumber: 595 }),
|
opencodeLaunchDirective({ dir, purpose: 'next', terminal: true, issueNumber: 595 }),
|
||||||
).toBe(
|
).toBe(
|
||||||
`launch opencode ${dir} --model ${presetInEffect()} --auto --prompt "/work-issue --issue 595"`,
|
`launch opencode ${dir} --model ${presetInEffect()} --variant ${WORKTREE_VARIANT} --auto --prompt "/work-issue --issue 595"`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('plan launches with the same presets and /plan-issue sent (OPS31, value quoted)', () => {
|
it('plan launches with the same presets and /plan-issue sent (OPS31, value quoted)', () => {
|
||||||
expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true })).toBe(
|
expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true })).toBe(
|
||||||
`launch opencode ${dir} --model ${presetInEffect()} --auto --prompt "/plan-issue"`,
|
`launch opencode ${dir} --model ${presetInEffect()} --variant ${WORKTREE_VARIANT} --auto --prompt "/plan-issue"`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('new launches prompt-less too — "apenas conversar", no skill sent', () => {
|
it('new launches prompt-less too — "apenas conversar", no skill sent', () => {
|
||||||
expect(opencodeLaunchDirective({ dir, purpose: 'new', terminal: true })).toBe(
|
expect(opencodeLaunchDirective({ dir, purpose: 'new', terminal: true })).toBe(
|
||||||
`launch opencode ${dir} --model ${presetInEffect()} --auto`,
|
`launch opencode ${dir} --model ${presetInEffect()} --variant ${WORKTREE_VARIANT} --auto`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('plan/new ignore the issueNumber — only next carries the claimed issue', () => {
|
it('plan/new ignore the issueNumber — only next carries the claimed issue', () => {
|
||||||
expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true, issueNumber: 7 })).toBe(
|
expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true, issueNumber: 7 })).toBe(
|
||||||
`launch opencode ${dir} --model ${presetInEffect()} --auto --prompt "/plan-issue"`,
|
`launch opencode ${dir} --model ${presetInEffect()} --variant ${WORKTREE_VARIANT} --auto --prompt "/plan-issue"`,
|
||||||
)
|
)
|
||||||
expect(opencodeLaunchDirective({ dir, purpose: 'new', terminal: true, issueNumber: 7 })).toBe(
|
expect(opencodeLaunchDirective({ dir, purpose: 'new', terminal: true, issueNumber: 7 })).toBe(
|
||||||
`launch opencode ${dir} --model ${presetInEffect()} --auto`,
|
`launch opencode ${dir} --model ${presetInEffect()} --variant ${WORKTREE_VARIANT} --auto`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('pins the preset constants — fallback comum deepseek-v4-flash, override via OPENCODE_WORKTREE_MODEL', () => {
|
it('pins the preset constants — fallback comum deepseek-v4-flash, override via OPENCODE_WORKTREE_MODEL', () => {
|
||||||
expect(OPENCODE_PRESET_MODEL).toBe(presetInEffect())
|
expect(OPENCODE_PRESET_MODEL).toBe(presetInEffect())
|
||||||
expect(WORKTREE_TERMINAL_ENV).toBe('TEQO_WORKTREE_TERMINAL')
|
expect(WORKTREE_TERMINAL_ENV).toBe('TEQO_WORKTREE_TERMINAL')
|
||||||
|
expect(WORKTREE_VARIANT).toBe('max')
|
||||||
expect(OPENCODE_SKILL_COMMAND_BY_PURPOSE).toEqual({
|
expect(OPENCODE_SKILL_COMMAND_BY_PURPOSE).toEqual({
|
||||||
next: '/work-issue',
|
next: '/work-issue',
|
||||||
plan: '/plan-issue',
|
plan: '/plan-issue',
|
||||||
|
|
@ -200,9 +205,81 @@ describe('opencodeLaunchDirective (terminal-only opencode launch, OPS26 + OPS33)
|
||||||
|
|
||||||
it('an unknown purpose degrades to a prompt-less launch (fail-safe direction)', () => {
|
it('an unknown purpose degrades to a prompt-less launch (fail-safe direction)', () => {
|
||||||
expect(opencodeLaunchDirective({ dir, purpose: 'bogus', terminal: true })).toBe(
|
expect(opencodeLaunchDirective({ dir, purpose: 'bogus', terminal: true })).toBe(
|
||||||
`launch opencode ${dir} --model ${presetInEffect()} --auto`,
|
`launch opencode ${dir} --model ${presetInEffect()} --variant ${WORKTREE_VARIANT} --auto`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('uses the explicit model when provided (OPS93 map) — always with --variant max', () => {
|
||||||
|
for (const [, model] of Object.entries(WORKTREE_MODEL_MAP)) {
|
||||||
|
expect(opencodeLaunchDirective({ dir, purpose: 'next', terminal: true, model })).toBe(
|
||||||
|
`launch opencode ${dir} --model ${model} --variant ${WORKTREE_VARIANT} --auto --prompt "/work-issue"`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('explicit model ignored outside the terminal — still null', () => {
|
||||||
|
expect(
|
||||||
|
opencodeLaunchDirective({
|
||||||
|
dir,
|
||||||
|
purpose: 'next',
|
||||||
|
terminal: false,
|
||||||
|
model: WORKTREE_MODEL_MAP.cheap,
|
||||||
|
}),
|
||||||
|
).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('resolveWorktreeModel + WORKTREE_MODEL_MAP (OPS93)', () => {
|
||||||
|
it('pins the 5-flag map — cheap/pro/zen/go/alibaba', () => {
|
||||||
|
expect(WORKTREE_MODEL_MAP).toEqual({
|
||||||
|
cheap: 'cheapestinference/deepseek-v4-flash',
|
||||||
|
pro: 'opencode-go/qwen3.7-max',
|
||||||
|
zen: 'opencode-go/ox-alpha-free',
|
||||||
|
go: 'opencode-go/mimo-v2.5',
|
||||||
|
alibaba: 'alibaba-token-plan/qwen3.7-max',
|
||||||
|
})
|
||||||
|
expect(WORKTREE_MODEL_FLAGS).toEqual(new Set(['cheap', 'pro', 'zen', 'go', 'alibaba']))
|
||||||
|
expect(WORKTREE_VARIANT).toBe('max')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('no flag → preset', () => {
|
||||||
|
expect(resolveWorktreeModel({})).toBe(presetInEffect())
|
||||||
|
expect(resolveWorktreeModel({ stay: true })).toBe(presetInEffect())
|
||||||
|
expect(resolveWorktreeModel({ issue: '123' })).toBe(presetInEffect())
|
||||||
|
})
|
||||||
|
|
||||||
|
it('single flag → mapped model', () => {
|
||||||
|
expect(resolveWorktreeModel({ cheap: true })).toBe(WORKTREE_MODEL_MAP.cheap)
|
||||||
|
expect(resolveWorktreeModel({ pro: true })).toBe(WORKTREE_MODEL_MAP.pro)
|
||||||
|
expect(resolveWorktreeModel({ zen: true })).toBe(WORKTREE_MODEL_MAP.zen)
|
||||||
|
expect(resolveWorktreeModel({ go: true })).toBe(WORKTREE_MODEL_MAP.go)
|
||||||
|
expect(resolveWorktreeModel({ alibaba: true })).toBe(WORKTREE_MODEL_MAP.alibaba)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('multiple flags → throws (fail-high, never guess)', () => {
|
||||||
|
expect(() => resolveWorktreeModel({ cheap: true, pro: true })).toThrow(/conflitantes/)
|
||||||
|
expect(() => resolveWorktreeModel({ zen: true, go: true })).toThrow(/--zen --go/)
|
||||||
|
expect(() => resolveWorktreeModel({ cheap: true, alibaba: true, go: true })).toThrow(
|
||||||
|
/conflitantes/,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('each flag launches with --variant max in the directive (next/plan/new share the path)', () => {
|
||||||
|
const dir = '/tmp/wt'
|
||||||
|
for (const [flag, model] of Object.entries(WORKTREE_MODEL_MAP)) {
|
||||||
|
const resolved = resolveWorktreeModel({ [flag]: true })
|
||||||
|
expect(resolved).toBe(model)
|
||||||
|
expect(
|
||||||
|
opencodeLaunchDirective({ dir, purpose: 'next', terminal: true, model: resolved }),
|
||||||
|
).toContain(`--model ${model} --variant ${WORKTREE_VARIANT} --auto`)
|
||||||
|
expect(
|
||||||
|
opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true, model: resolved }),
|
||||||
|
).toContain(`--model ${model} --variant ${WORKTREE_VARIANT} --auto`)
|
||||||
|
expect(
|
||||||
|
opencodeLaunchDirective({ dir, purpose: 'new', terminal: true, model: resolved }),
|
||||||
|
).toContain(`--model ${model} --variant ${WORKTREE_VARIANT} --auto`)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('workBranchName (per-invocation neutral worktrees)', () => {
|
describe('workBranchName (per-invocation neutral worktrees)', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue