mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:53:05 +00:00
feat(OPS119): worktree plan abre driverless sem bag e auto-envia /plan-issue <bag> (Closes #1066)
`worktree plan` sem argumento cria a sessão persistente sem driver (contrato do `new`); com bag auto-submete uma única `/plan-issue <bag>` e usa o mesmo texto como rótulo do branch, espelhando o `fix`. - purposeInvocation (lib/agent-session): ramo plan reusa a sanitização do fix e devolve null quando o bag fica vazio (driverless por desenho). - opencodeLaunchDirective emite --argument para fix||plan; cmdPlan repassa argument: bag; cmdStart isenta plan do aviso "sem comando". - next/new/fix, --stay e o comando /worktree intactos. - Specs repinados; textos vivos e changelog atualizados. Débito de risk map das libs + pin do wiring registrado em #1071.
This commit is contained in:
parent
7b840e7dc7
commit
d70a1b5072
14 changed files with 253 additions and 51 deletions
|
|
@ -13,8 +13,9 @@
|
|||
# do argumento, nunca eval). Desde a OPS110 o launch não abre um TUI local dono
|
||||
# da sessão: o `agent-session` sobe/reaproveita o `opencode serve` compartilhado,
|
||||
# cria a sessão endereçável, dispara o driver destacado (que auto-submete
|
||||
# `/work-issue --issue <N>` no `next`, `/plan-issue` no `plan`, `/bug-fix <bag>`
|
||||
# no `fix`; `new` abre só a sessão) e ANEXA o TUI (`opencode attach -s <id>`) —
|
||||
# `/work-issue --issue <N>` no `next`, `/plan-issue <bag>` no `plan` com bag,
|
||||
# `/bug-fix <bag>` no `fix`; `new` e `plan` sem bag abrem só a sessão) e ANEXA
|
||||
# o TUI (`opencode attach -s <id>`) —
|
||||
# fechar o terminal NÃO encerra o run; reentre com `pnpm agent:session attach` e
|
||||
# encerre com `pnpm agent:session stop`. Sem `--variant` (OPS95: o yargs do TUI
|
||||
# rejeita o flag e só imprime o helper; variantes ficam na config global da
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ pnpm worktree kill [--force] # destrói o worktree atual + remove s
|
|||
|
||||
No opencode, isso é o comando **`/worktree next`** / **`/worktree kill`** (`.opencode/commands/worktree.md`), que só repassa `$ARGUMENTS` para o script.
|
||||
|
||||
`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`. No terminal, `plan <bag>` usa o bag também como mensagem de abertura (auto-submete `/plan-issue <bag>`, como o `fix`); `plan` **sem bag** abre a sessão sem driver, para você digitar o `/plan-issue` com contexto (OPS119 — supera o auto-envio incondicional do OPS31). 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; modelos por invocação via `--cheap/--pro/--zen/--go/--alibaba/--glm/--free` (mapa fixo `WORKTREE_MODEL_MAP`, at-most-one; sem flag o preset permanece); `--go` remapeado em OPS93 para o provider OpenCode Go, em OPS95 para `opencode-go/hy3` e em OPS112 para `opencode-go/deepseek-v4.1-flash` (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 + OPS93 + OPS95 + OPS110):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new`/`fix` imprimem também a diretiva `launch node <abs>/scripts/agent-session.mjs start --purpose=<next|plan|new|fix> --dir=<dir> --model <preset|map> [--issue <N>] [--argument "<bag>"]` **antes** do `cd`, e a função a executa depois do cd. Desde a OPS110 o launch não abre um TUI dono da sessão: o `agent-session` sobe/reaproveita o `opencode serve` compartilhado, cria a sessão endereçável, dispara o driver destacado (auto-submete `/work-issue --issue <N>` no `next`, `/plan-issue` no `plan`, `/bug-fix <bag>` no `fix`; `new` abre só a sessão) e anexa o TUI (`opencode attach -s <id>`) — fechar o terminal **não** encerra o run; reentre com `pnpm agent:session attach` e encerre com `pnpm agent:session stop` (ver a seção "Sessões persistentes" de `docs/AGENT-OPS.md`). Sem `--variant` na diretiva (OPS95: o yargs do TUI rejeita o flag desconhecido e só imprime o helper — variantes ficam na config global do opencode, selecionáveis via Ctrl+T; `opencode run --variant max` segue sendo a via CLI para runs headless). Modelo por invocação via `--cheap` (cheapestinference/deepseek-v4-flash), `--pro` (deepseek/deepseek-v4-pro), `--zen` (opencode/muse-spark-1.3-contributor-free), `--go` (opencode-go/deepseek-v4.1-flash), `--alibaba` (alibaba-token-plan/deepseek-v4-flash) — valores corrigidos no OPS95 (`--go`/`--zen` em OPS112) — e `--glm` (opencode-go/glm-5.3-flash, OPS100), `--free` (openrouter/openrouter/free, OPS100); sem flag o preset `deepseek/deepseek-flash` permanece. Sem `exec`: sair do opencode volta ao shell no worktree. Presets e mapa são constantes em `scripts/lib/worktree.mjs`; o ciclo de vida das sessões (serve/start/attach/stop/list) vive em `scripts/agent-session.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 + OPS95 + OPS110):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new`/`fix` imprimem também a diretiva `launch node <abs>/scripts/agent-session.mjs start --purpose=<next|plan|new|fix> --dir=<dir> --model <preset|map> [--issue <N>] [--argument "<bag>"]` **antes** do `cd`, e a função a executa depois do cd. Desde a OPS110 o launch não abre um TUI dono da sessão: o `agent-session` sobe/reaproveita o `opencode serve` compartilhado, cria a sessão endereçável, dispara o driver destacado (auto-submete `/work-issue --issue <N>` no `next`, `/plan-issue <bag>` no `plan` com bag, `/bug-fix <bag>` no `fix`; `new` e `plan` sem bag abrem só a sessão) e anexa o TUI (`opencode attach -s <id>`) — fechar o terminal **não** encerra o run; reentre com `pnpm agent:session attach` e encerre com `pnpm agent:session stop` (ver a seção "Sessões persistentes" de `docs/AGENT-OPS.md`). Sem `--variant` na diretiva (OPS95: o yargs do TUI rejeita o flag desconhecido e só imprime o helper — variantes ficam na config global do opencode, selecionáveis via Ctrl+T; `opencode run --variant max` segue sendo a via CLI para runs headless). Modelo por invocação via `--cheap` (cheapestinference/deepseek-v4-flash), `--pro` (deepseek/deepseek-v4-pro), `--zen` (opencode/muse-spark-1.3-contributor-free), `--go` (opencode-go/deepseek-v4.1-flash), `--alibaba` (alibaba-token-plan/deepseek-v4-flash) — valores corrigidos no OPS95 (`--go`/`--zen` em OPS112) — e `--glm` (opencode-go/glm-5.3-flash, OPS100), `--free` (openrouter/openrouter/free, OPS100); sem flag o preset `deepseek/deepseek-flash` permanece. Sem `exec`: sair do opencode volta ao shell no worktree. Presets e mapa são constantes em `scripts/lib/worktree.mjs`; o ciclo de vida das sessões (serve/start/attach/stop/list) vive em `scripts/agent-session.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)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ description: Claima a próxima Issue (next [--issue N] [--stay] [--cheap|--pro|-
|
|||
Rode o script determinístico abaixo (não improvise a lógica) e reporte o resultado de forma concisa.
|
||||
|
||||
- `next [--issue N] [--stay] [--cheap|--pro|--zen|--go|--alibaba|--glm|--free]`: **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/--glm/--free` escolhe o modelo por invocação no mapa fixo `WORKTREE_MODEL_MAP` (cheap=cheapestinference/deepseek-v4-flash, pro=deepseek/deepseek-v4-pro, zen=opencode/muse-spark-1.3-contributor-free, go=opencode-go/deepseek-v4.1-flash, alibaba=alibaba-token-plan/deepseek-v4-flash, glm=opencode-go/glm-5.3-flash, free=openrouter/openrouter/free); at-most-one (múltiplas → erro).
|
||||
- `plan [bag] [--stay] [--cheap|--pro|--zen|--go|--alibaba|--glm|--free]`: 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`.
|
||||
- `plan [bag] [--stay] [--cheap|--pro|--zen|--go|--alibaba|--glm|--free]`: 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. No terminal, o bag é também a mensagem de abertura: o launch carrega `--argument="<bag>"` e o driver auto-submete `/plan-issue <bag>`; **sem bag a sessão abre sem driver** (você digita o `/plan-issue` com contexto). 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] [--cheap|--pro|--zen|--go|--alibaba|--glm|--free]`: 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`.
|
||||
- `fix [bag] [--stay] [--cheap|--pro|--zen|--go|--alibaba|--glm|--free]`: cria um worktree de **correção de bug** (skill `/bug-fix`) **diferente a cada invocação** (branch `fix/<bag>`, ou `fix/<n>` sequencial sem bag; sufixo `-2`/`-3` em colisão); o prefixo minúsculo `fix/…` nunca conflita com `next`, `plan` nem `new`. **Não claima nem cria Issues** — o registro do bug é o post-mortem da skill. O bag é a descrição do bug: no terminal, o launch carrega `--argument="<bag>"` e o driver destacado auto-submete `/bug-fix <bag>` (a descrição chega com a skill). 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á, encerra a sessão persistente do run (OPS110) 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.
|
||||
|
|
|
|||
|
|
@ -72,4 +72,4 @@ src/
|
|||
|
||||
## Per-worktree environments
|
||||
|
||||
`pnpm worktree next` provisions isolated envs per worktree: dev port `3100+slot`, databases `teqo_wt<slot>` / `teqo_wt<slot>_test` on the shared container with full migration chain + minimal seed, `.env.local` + `.env.test.local` (gitignored), copies of `PAYLOAD_SECRET`/`S3_*`/VAPID from main repo. Same provisioning for `plan [bag]`, `new [bag]` and `fix [bag]` worktrees (`plans/plan-issue-…`, `work/…`, `fix/…` namespaces — `fix` opens `/bug-fix` with the bug description delivered; no claim, no Issue). No terminal, o launch de `next`/`plan`/`new`/`fix` roda a sessão no `opencode serve` compartilhado e anexa o TUI (OPS110): fechar o terminal **não** encerra o run — reentre com `pnpm agent:session attach` e encerre com `pnpm agent:session stop` (estado/endereçamento em `~/.local/state/teqo/agent-sessions`; contrato do painel OPS109). The skill `local-database` covers the full workflow.
|
||||
`pnpm worktree next` provisions isolated envs per worktree: dev port `3100+slot`, databases `teqo_wt<slot>` / `teqo_wt<slot>_test` on the shared container with full migration chain + minimal seed, `.env.local` + `.env.test.local` (gitignored), copies of `PAYLOAD_SECRET`/`S3_*`/VAPID from main repo. Same provisioning for `plan [bag]`, `new [bag]` and `fix [bag]` worktrees (`plans/plan-issue-…`, `work/…`, `fix/…` namespaces — `plan <bag>` opens `/plan-issue` with the bag as the opening message and `plan` without a bag is driverless so you type the context (OPS119), `fix` opens `/bug-fix` with the bug description delivered; no claim, no Issue). No terminal, o launch de `next`/`plan`/`new`/`fix` roda a sessão no `opencode serve` compartilhado e anexa o TUI (OPS110): fechar o terminal **não** encerra o run — reentre com `pnpm agent:session attach` e encerre com `pnpm agent:session stop` (estado/endereçamento em `~/.local/state/teqo/agent-sessions`; contrato do painel OPS109). The skill `local-database` covers the full workflow.
|
||||
|
|
|
|||
|
|
@ -64,13 +64,13 @@ Labels (GitHub): `ready|in-progress|blocked|done|in-prod`, `prio:*`, `kind:*`, `
|
|||
|
||||
O launch do terminal (`worktree next|plan|new|fix`) **não abre mais um TUI dono da sessão**: ele chama `pnpm agent:session start …`, que sobe/reaproveita o `opencode serve` compartilhado, cria a sessão endereçável, dispara o **driver destacado** (`opencode run --attach … --auto --command <skill>`, que auto-aprova permissões enquanto vive) e **anexa** o TUI (`opencode attach -s <sessionID>`). Fechar o terminal/TUI desconecta — o run continua; encerrar é ato explícito.
|
||||
|
||||
| Comando | Faz |
|
||||
| -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| `pnpm agent:session serve [--hostname=H] [--port=P]` | Sobe/reaproveita o servidor (loopback `127.0.0.1:4199`; `TEQO_AGENT_SERVER_HOST/PORT`) |
|
||||
| `pnpm agent:session start --purpose=<next\|plan\|new\|fix> --dir=<D> --model=<M> [--issue=N] [--argument="<bag>"] [--new]` | Cria a sessão, dispara o driver (quando há skill) e anexa o TUI |
|
||||
| `pnpm agent:session attach [--session=<ses_…>\|--branch=<B>\|--issue=N]` | (Re)entra na sessão — default: branch do cwd; sobe o servidor se ele estiver fora |
|
||||
| `pnpm agent:session stop [--session=<ses_…>\|--branch=<B>\|--issue=N]` | Ato explícito de encerrar: abort via API + fim do driver + `stoppedAt` |
|
||||
| `pnpm agent:session list [--json]` | Status dos runs (`working\|idle\|stopped\|unknown`); `--json` é o contrato do OPS109 |
|
||||
| Comando | Faz |
|
||||
| -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `pnpm agent:session serve [--hostname=H] [--port=P]` | Sobe/reaproveita o servidor (loopback `127.0.0.1:4199`; `TEQO_AGENT_SERVER_HOST/PORT`) |
|
||||
| `pnpm agent:session start --purpose=<next\|plan\|new\|fix> --dir=<D> --model=<M> [--issue=N] [--argument="<bag>"] [--new]` | Cria a sessão, dispara o driver (quando há comando a auto-submeter — `plan` sem bag é driverless) e anexa o TUI |
|
||||
| `pnpm agent:session attach [--session=<ses_…>\|--branch=<B>\|--issue=N]` | (Re)entra na sessão — default: branch do cwd; sobe o servidor se ele estiver fora |
|
||||
| `pnpm agent:session stop [--session=<ses_…>\|--branch=<B>\|--issue=N]` | Ato explícito de encerrar: abort via API + fim do driver + `stoppedAt` |
|
||||
| `pnpm agent:session list [--json]` | Status dos runs (`working\|idle\|stopped\|unknown`); `--json` é o contrato do OPS109 |
|
||||
|
||||
- **Endereçamento (OPS109):** `~/.local/state/teqo/agent-sessions/<slug>.json` (`TEQO_AGENT_SESSION_DIR` > `XDG_STATE_HOME`) guarda `version, code, issue, purpose, branch, dir, sessionID, url, model, driverPid, logPath, startedAt, stoppedAt`; `list --json` entrega `{version, server, sessions:[…status]}` — é a única fonte para "abrir a sessão da Issue".
|
||||
- **Sair ≠ encerrar:** fechar o TUI/terminal não mata o run (verificado: SIGKILL no cliente e `tmux kill-session` não abortam; `attach` replayer histórico e streaming ao vivo). `Ctrl+C` no TUI limpa o input; sair é fechar o terminal ou o keybind `app_exit` (`ctrl+c,ctrl+d,<leader>q`).
|
||||
|
|
|
|||
1
docs/changelog/2026-09-16-ops119.md
Normal file
1
docs/changelog/2026-09-16-ops119.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
**OPS119 — worktree plan abre sem auto-enviar o `/plan-issue` (mensagem opcional na linha) (2026-09-16):** `worktree plan` sem argumento agora cria a sessão persistente **sem driver** (mesmo contrato observável do `new`) — o humano digita o `/plan-issue` com o contexto que quiser; `worktree plan "<mensagem>"` segue auto-submetendo uma única `/plan-issue <mensagem>` e usa a mesma mensagem como bag do branch (`plans/plan-issue-<slug>`), espelhando o `fix <bag>`. A regra vive no dono do ciclo (`purposeInvocation` em `scripts/lib/agent-session.mjs`): o ramo `plan` reusa a sanitização do `fix` (`["\\]` + `trim`) e devolve `null` quando o bag fica vazio (divergência deliberada do `fix`, cujo bag vazio ainda submete o comando bare). `opencodeLaunchDirective` (`scripts/lib/worktree.mjs`) passa a emitir `--argument` para `fix || plan`, `cmdPlan` (`scripts/worktree.mjs`) repassa `argument: bag` e o aviso "sem comando/issue válida" do `cmdStart` isenta `plan` (driverless por desenho). `next`/`new`/`fix`, `--stay` e o comando `/worktree` intactos; supersede o auto-envio incondicional do OPS31 (#593). Sem migration, sem schema, sem UI. Testes: `agentSession.unit.spec.ts` e `worktree.unit.spec.ts` repinados (plan sem bag → `null`; plan com bag → `--argument` sanitizado; `new` segue ignorando o argumento); `pnpm gate:fast` verde e walkthrough manual do launch registrado no PR. Documentação viva atualizada em `scripts/worktree.mjs`, `.agents/shell/worktree.sh`, `.opencode/commands/worktree.md`, `.agents/skills/worktree-next-issue/SKILL.md`, `AGENTS.md` e `docs/AGENT-OPS.md`.
|
||||
64
docs/plans/escala-dry-pos-ops119.md
Normal file
64
docs/plans/escala-dry-pos-ops119.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# OPS119+ — launch do worktree: risk map das libs + pin do wiring do `plan`
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-16
|
||||
Issue: #1071
|
||||
Intenção: débito pós-entrega da OPS119 (#1066)
|
||||
Priority: P3
|
||||
Impeccable: A — N/A (CLI/shell, sem superfície de produto)
|
||||
Appetite: ~1h eng
|
||||
|
||||
## Origem
|
||||
|
||||
Triagem do `/simplify` da entrega OPS119 (#1066 — `worktree plan` sem bag abre
|
||||
driverless): dois achados de score 3 na **mesma superfície** (launch do worktree
|
||||
↔ `agent-session`), mesclados neste lote.
|
||||
|
||||
- **S6 — risk map (integridade do guard):** `HIGH_RISK_EXACT`
|
||||
(`scripts/lib/test-affected-core.mjs:25-53`) só marca `scripts/worktree.mjs`.
|
||||
Um diff que toca **apenas** `scripts/lib/worktree.mjs`,
|
||||
`scripts/lib/agent-session.mjs` ou `scripts/agent-session.mjs` classifica
|
||||
`classifyTestScope` como `none` — a suíte unit **não roda** no PR (e o static
|
||||
scope também é `none`). A entrega OPS119 só rodou full porque tocou
|
||||
`scripts/worktree.mjs`; um fix lib-only passaria verde sem teste. Fail-closed
|
||||
do blast radius: as libs puras são pinadas por specs unit que nunca seriam
|
||||
exercitados no diff que as quebra.
|
||||
- **S4 — pin do wiring:** o repasse `argument: bag` de `cmdPlan` →
|
||||
`cmdNamespaceBranch` (`scripts/worktree.mjs:764-768`) não tem pin unitário —
|
||||
só as libs puras (`purposeInvocation`, `opencodeLaunchDirective`) estão
|
||||
pinadas. Um `argument` dropado no runner não é pego por teste.
|
||||
|
||||
## Fases
|
||||
|
||||
1. **F1 — risk map (~30min):** incluir os arquivos do launch no piso de blast
|
||||
radius — `scripts/agent-session.mjs`, `scripts/lib/agent-session.mjs`,
|
||||
`scripts/lib/worktree.mjs` em `HIGH_RISK_EXACT`. **Alternativa rejeitada:**
|
||||
prefixo `scripts/lib/` inteiro — engoliria libs sem harness próprio e
|
||||
forçaria full em diffs triviais. Atualizar o pin do dono
|
||||
(`tests/unit/testAffected.unit.spec.ts` /
|
||||
`tests/unit/ciSkipInvariants.unit.spec.ts`). Verificação: o classificador
|
||||
devolve `full` para um diff só-lib.
|
||||
2. **F2 — pin do wiring (~30min):** tornar o mapeamento do `cmdPlan` testável.
|
||||
**Opções:** A) exportar o mapa purpose→opções (não o spawn); B) caso unit
|
||||
que injeta as opções em `cmdNamespaceBranch`. **Rejeitada:** teste que cria
|
||||
worktree de verdade (lento, side effects, colide no slot). Verificação:
|
||||
`pnpm test:unit`.
|
||||
|
||||
## Já resolvido no simplify (não reabrir)
|
||||
|
||||
- Doc drift de `opencodeLaunchDirective` e do comentário de
|
||||
`cmdNamespaceBranch`, e o fixture fantasma do spec `driverArgs`
|
||||
(`plan-issue` com `arguments:''` → hoje `bug-fix` com bag vazio) — corrigidos
|
||||
na própria OPS119.
|
||||
|
||||
## Explicitamente fora
|
||||
|
||||
- **S1** (`return { command, arguments: '' }` final de `purposeInvocation`
|
||||
inalcançável hoje): é o fallback fail-safe para um purpose mapeado sem args —
|
||||
removê-lo reintroduziria a classe de bug que ele evita. Descartado (YAGNI).
|
||||
- **S2** (sanitização `replace(/["\\]/g,'')` duplicada entre as duas libs):
|
||||
defer — gatilho: 3º call site ou import cruzado das libs.
|
||||
- **S3** (política driverless em `!==` no CLI): defer — gatilho: 3º purpose
|
||||
driverless/sem-args.
|
||||
- **Prefill-sem-submit no opencode** (upstream): fora — proposição
|
||||
`~/Code/propositions/opencode/prefill-prompt-sem-submit.md` já re-flagada.
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
# Impl: OPS119 — worktree plan: abrir sem auto-enviar o `/plan-issue` (mensagem opcional na linha)
|
||||
|
||||
Status: em execução
|
||||
Atualizado em: 2026-09-16
|
||||
Issue: #1066
|
||||
Intenção: docs/plans/ops119-worktree-plan-sem-auto-envio-plan-issue.md
|
||||
Appetite restante: herdado (~0,5 dia eng — 2 libs puras + 1 script + 2 specs + textos)
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** `worktree plan` (sem argumento) abre a sessão persistente **sem driver** (nenhuma mensagem auto-submetida, mesmo contrato observável do `new`); `worktree plan "<mensagem>"` auto-submete **uma** `/plan-issue <mensagem>` e usa a mesma mensagem como bag do branch — simétrico a `fix <bag>`. `next`/`new`/`fix`, `--stay` e o comando `/worktree` intactos.
|
||||
- **O que NÃO negociar:** a mensagem passa pela **mesma sanitização do `fix`** (`replace(/["\\]/g,'')` + `trim`); bag vazio **não** gera driver; `next` segue `/work-issue --issue <N>`; `new` segue driverless; `fix <bag>` segue `/bug-fix <bag>`; `--stay` e/ou `TEQO_WORKTREE_TERMINAL` ausente continuam sem diretiva. Sem migration/access/Consent/UI (Impeccable A).
|
||||
- **O que reavaliar (hipóteses da "Direção no codebase" que o explorador corrigiu):**
|
||||
- O plano de intenção não previu o aviso `[agent:session] purpose=… sem comando/issue válida` (`scripts/agent-session.mjs:536-539`): `plan` sem msg cairia no `else if (purpose !== 'new')` e **logaria um falso alarme**. Entra no escopo (é a mesma aceitação observável: "abre sem driver", sem ruído).
|
||||
- O docblock de `purposeInvocation` (`scripts/lib/agent-session.mjs:164-181`) e o USAGE do `start` (`:83`, "model obrigatório next/plan/fix") ficam **imprecisos** — o `plan` deixa de exigir `--model` quando não há driver. Ajuste de texto barato, feito na passagem.
|
||||
- `planBranchName`/`namespaceBranchName` (`scripts/lib/worktree.mjs:150-170,277-278`) já resolvem o branch a partir do bag (fallback `plano`, `slugify`) — **nada a mudar** ali; o bag vazio já cai no sequencial `plans/plan-issue-<n>`.
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["worktree plan [bag]"] --> B["cmdPlan (scripts/worktree.mjs)"]
|
||||
B -->|"argument: bag"| C["cmdNamespaceBranch"]
|
||||
C --> D["printLaunchDirective"]
|
||||
D --> E["opencodeLaunchDirective (scripts/lib/worktree.mjs)"]
|
||||
E -->|"plan + bag sanitizado → --argument=«JSON»"| F["launch agent-session start --purpose=plan [--argument]"]
|
||||
F --> G["cmdStart → purposeInvocation (scripts/lib/agent-session.mjs)"]
|
||||
G -->|"bag válido → {command:'plan-issue', arguments}"| H["spawn driverArgs (uma /plan-issue <msg>)"]
|
||||
G -->|"sem bag / bag vazio → null"| I["sessão criada, SEM driver; só attach"]
|
||||
H --> J["attach TUI"]
|
||||
I --> J
|
||||
```
|
||||
|
||||
**Opções consideradas:** A | B | C
|
||||
**Recomendação:** **A** — o dono da decisão "quando um purpose carrega skill" é `purposeInvocation` (`scripts/lib/agent-session.mjs`), e o ramo do `plan` é **cópia exata** do ramo `fix` (`:192-197`): sanitiza, vazio → `null`, senão `{ command, arguments }`. O `opencodeLaunchDirective` passa a emitir `--argument` para `fix || plan` e `cmdPlan` passa `argument: bag` — o restante do caminho (`cmdNamespaceBranch` runner, `printLaunchDirective`, `driverArgs`, dispatch) **não muda**.
|
||||
**Rejeitadas:** **B** — manter `purposeInvocation` devolvendo comando "bare" para `plan` e decidir o não-driver dentro do `cmdStart` (CLI): quebra a pureza do dono do ciclo (a lib passaria a mentir "sempre há comando"), e a regra ficaria em dois lugares quando um 5º purpose chegar. **C** — criar uma flag nova de driver (`--driver`/`--no-driver`) na diretiva: vocabulário novo sem necessidade, e destrói a simetria com `fix`, que já expressa "tem driver" pela presença do bag.
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`purposeInvocation`** (`scripts/lib/agent-session.mjs:184-199`): ramo `plan` antes do `return { command, arguments: '' }` final, espelhando `fix` — `const sanitized = typeof argument === 'string' ? argument.replace(/["\\]/g, '').trim() : ''`; **`if (!sanitized) return null`**; `return { command, arguments: sanitized }`. Ponto de **divergência deliberada** do `fix`: no `fix` bag vazio ainda auto-submete `/bug-fix` bare; no `plan` (sem contexto) vazio → **sem driver**.
|
||||
- **Docblock de `purposeInvocation`** (`:176-181`): descrever `plan` sem-arg → `null`, com-arg → `/plan-issue <msg>`; `fix` inalterado.
|
||||
- **`opencodeLaunchDirective`** (`scripts/lib/worktree.mjs:202-232`): condição `if (purpose === 'fix')` → `if (purpose === 'fix' || purpose === 'plan')` (`:227`); comentário do `:224-226` passa a citar o par. O sufixo do issue segue só do `next` (`:223`).
|
||||
- **`cmdPlan`** (`scripts/worktree.mjs:755-764`): acrescenta `argument: bag` ao `cmdNamespaceBranch` (como `cmdFix :810`). `cmdNamespaceBranch` (`:675-743`), `printLaunchDirective` (`:221-235`) e o dispatch (`:1037-1038`) **intocados**.
|
||||
- **`cmdStart`** (`scripts/agent-session.mjs:536-539`): `else if (purpose !== 'new')` → `else if (purpose !== 'new' && purpose !== 'plan')` + comentário ("`plan` sem bag é driverless por desenho").
|
||||
- **Textos do CLI:** header/USAGE de `scripts/agent-session.mjs` (`:9`, `:74-83` — `--model` obrigatório "quando há driver"); docblock/uso do `plan` em `scripts/worktree.mjs:68-83,979-992`; `.agents/shell/worktree.sh:9-31,43-49`; `.opencode/commands/worktree.md:8`; `.agents/skills/worktree-next-issue/SKILL.md:23,30,34`; `AGENTS.md:75`; `docs/AGENT-OPS.md:65,70`.
|
||||
- **Changelog:** `docs/changelog/2026-09-16-ops119.md` (uma entrada). Não editar o HISTORY nem o agregado.
|
||||
- **Migration:** sem migration (nenhum schema/payload é tocado).
|
||||
- **Access / Consent:** N/A.
|
||||
- **UI:** N/A (Impeccable A — CLI/shell).
|
||||
|
||||
### Dados → forma (se aplicável)
|
||||
|
||||
- N/A — sem superfície de dados. A "forma" é o contrato observável do launch: `plan` ≡ `new` sem bag; `plan <bag>` ≡ `fix <bag>` com a skill trocada. Rejeitada a forma "prefill-draft" (upstream inexistente no opencode 1.18.31 — proposição `~/Code/propositions/opencode/prefill-prompt-sem-submit.md` re-flagada fora do repo).
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **F1 — Sessão (owner) ~40% do appetite:** ramo `plan` em `purposeInvocation` + docblock; isenção do aviso em `cmdStart`. Prova: unit do `agentSession.unit.spec.ts` (plan sem arg → `null`; com arg → `{command:'plan-issue', arguments}`; bag vazio/só aspas → `null`; `next`/`fix`/`new`/unknown inalterados).
|
||||
2. **F2 — Launch ~20%:** `opencodeLaunchDirective` (`fix || plan`) + `cmdPlan(argument: bag)`. Prova: unit do `worktree.unit.spec.ts` (plan sem arg = diretiva de hoje; plan com bag carrega `--argument="…"`; plan ignora `issueNumber`; `new` segue ignorando `argument`).
|
||||
3. **F3 — Testes + textos ~30%:** repin dos dois specs; varredura dos textos listados por "plan auto-submete `/plan-issue`"; entrada de changelog.
|
||||
4. **F4 — Gates ~10%:** `pnpm gate:fast` (lint + typecheck + unit) verde; walkthrough manual (terminal com `opencode` instalado): `worktree plan` abre sem mensagem e `worktree plan "mensagem"` abre com `/plan-issue mensagem` + branch `plans/plan-issue-mensagem`; push via `pnpm push`. Este PR toca `scripts/worktree.mjs` (HIGH_RISK_EXACT) ⇒ CI = full de qualquer forma.
|
||||
|
||||
## Rabbit holes / Não escopo (engenharia)
|
||||
|
||||
- **Novo vocabulário de CLI** (subcomandos, `--message`, separar bag de mensagem): cortado — só o par sem-arg/arg, idêntico ao `fix`.
|
||||
- **Prefill-sem-submit no opencode:** upstream; apenas re-flagar a proposição (fora do repo).
|
||||
- **Gateway `--prompt`/`--variant`:** intocados (OPS95/OPS110).
|
||||
- **`kill`/`attach`/`resolveStartDecision`/`deriveSessionStatus`:** agnósticos ao driver (`driverPid:null` → `idle`); não mexer.
|
||||
- **`HIGH_RISK_EXACT` não cobre `scripts/lib/agent-session.mjs` nem `scripts/lib/worktree.mjs`** (`scripts/lib/test-affected-core.mjs:25-53`): gap **pré-existente e independente** desta entrega (o PR atual toca `scripts/worktree.mjs`, que já é high-risk). Não expandido aqui; registrado como Issue de follow-up **#1071** (plano `docs/plans/escala-dry-pos-ops119.md`, com o pin do wiring do `cmdPlan`).
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Regressão em `next`/`new`/`fix` ao mexer na condição da diretiva:** a mudança é aditiva (`|| purpose === 'plan'`) e os specs de `new`/`fix` ficam verdes sem edição no comportamento deles; teste explícito trava `new` ignorar `argument` e `fix` manter `--argument`.
|
||||
- **Divergência entre slug do branch e mensagem sanitizada:** branch usa `slugify` (existente), mensagem reusa a sanitização do `fix`; ambos idempotentes entre a camada diretiva e o `purposeInvocation`.
|
||||
- **Falso alarme "sem driver" no `plan`:** coberto pela isenção do `cmdStart` (F1) — é justamente o que a intenção exige ("mesmo contrato observável do `new`").
|
||||
- **`--model` dito obrigatório mas ausente no plan driverless:** o aviso/erro real (`cmdStart:476`) só dispara quando `invocation` existe; texto do USAGE ajustado em F3.
|
||||
- **Texto/doc remanescente afirmando auto-envio sem-arg:** varredura por `/plan-issue` nos arquivos listados antes do commit (histórico congelado intocado: `docs/plans/worktree-plan-abre-opencode-com-plan-issue*.md`, `docs/CHANGELOG-AGENTS-HISTORY.md:241`).
|
||||
|
||||
## Triage pós-simplify (capture-review-debts)
|
||||
|
||||
- **S4 (pin do wiring `cmdPlan` → `cmdNamespaceBranch`) + S6 (`HIGH_RISK_EXACT` das libs do launch)** — score 3, mesma superfície → **registrado** como Issue **#1071** (`docs/plans/escala-dry-pos-ops119.md`; `kind:chore`, P3, `depends: OPS119`).
|
||||
- **S1** (`return { command, arguments: '' }` final de `purposeInvocation`) — descartado: fallback fail-safe para um purpose mapeado sem args.
|
||||
- **S2** (sanitização `["\\]` duplicada entre as duas libs) — defer, gatilho: 3º call site ou import cruzado das libs.
|
||||
- **S3** (política driverless em `!==` no CLI) — defer, gatilho: 3º purpose driverless.
|
||||
- **S5** (doc drift de `opencodeLaunchDirective`/`cmdNamespaceBranch` + fixture fantasma do `driverArgs`) — já resolvido no simplify.
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [x] Aceite de produto da intenção coberto: `plan` sem arg = `null`/driverless; `plan <msg>` = `{command:'plan-issue', arguments:<sanitized>}`; `next`/`new`/`fix`/`--stay`/`/worktree` intactos.
|
||||
- [x] Invariantes AGENTS/engineering-standards: reusa o dono (`purposeInvocation`/caminho do `fix`), sem módulo/abstração nova; sem `push:false`/schema/migration; sem segredo.
|
||||
- [x] Testes de domínio (unit) repinados onde o contrato muda: `tests/unit/agentSession.unit.spec.ts` e `tests/unit/worktree.unit.spec.ts` (plan sem bag → `null`; plan com bag → `--argument` sanitizado; `new`/`fix`/`next` inalterados).
|
||||
- [x] Textos vivos atualizados; histórico congelado intocado; `docs/changelog/2026-09-16-ops119.md` criado.
|
||||
- [x] `pnpm gate:fast` verde; cadeia bag→diretiva→`purposeInvocation` verificada por execução direta das libs (walkthrough do launch registrado no PR).
|
||||
|
||||
## Self-score decision-quality
|
||||
|
||||
1. Decisões caras com rejeitadas? **Sim** — D1 (dono da decisão), D2 (bag vazio → `null`, divergência deliberada do `fix`), D4 (isenção do aviso), D6 (classifier deferido).
|
||||
2. Cabe no appetite (~0,5 dia)? **Sim** — 2 libs puras + 1 script + 2 specs + textos.
|
||||
3. Rabbit holes nomeados? **Sim** — vocabulário novo, prefill upstream, classifier, kill/attach.
|
||||
4. Depth check reusa? **Sim** — ramo `fix`, `slugify`, sanitização e `driverArgs` existentes; zero abstração nova.
|
||||
5. Intenção preservada? **Sim** — engenharia não reescreve outcome; só estende o caminho já provado do `fix`.
|
||||
|
||||
**Self-score: 5/5** (gate ≥4 atendido). Status inicial `rascunho` — a pausa do GATE de aprovação humana vale.
|
||||
|
|
@ -9,7 +9,8 @@
|
|||
* pnpm agent:session start --purpose=<next|plan|new|fix> --dir=<D> [--model=<M>] [--issue=N] [--argument="<bag>"] [--new] [--hostname=H] [--port=P]
|
||||
* Cria a sessão endereçável, sobe o driver destacado
|
||||
* (`opencode run --attach … --auto --command <skill> -- …`) quando o
|
||||
* purpose tem skill, e abre o TUI anexado (`opencode attach -s <id>`).
|
||||
* purpose tem skill invocável (`plan` sem bag é driverless), e abre o
|
||||
* TUI anexado (`opencode attach -s <id>`).
|
||||
* Fechar o TUI/terminal NÃO encerra o run; `stop` encerra. Reusa o run
|
||||
* existente quando o driver ainda vive ou a sessão está busy; `--new`
|
||||
* força sessão nova.
|
||||
|
|
@ -80,7 +81,7 @@ const USAGE = `Uso: pnpm agent:session <serve|start|attach|stop|list> [flags]
|
|||
stop [--session=<ses_…>|--branch=<B>|--issue=N] encerra de forma explícita (abort + fim do driver)
|
||||
list [--json] [--hostname=H] [--port=P] status dos runs registrados
|
||||
|
||||
--model é obrigatório quando o purpose tem skill (next/plan/fix); 'new' não dispara driver.`
|
||||
--model é obrigatório quando o purpose dispara driver (next/fix, plan com bag); 'new' e 'plan' sem bag não disparam driver.`
|
||||
|
||||
/** Loopback by default; the credential rides the env, never the state/log. */
|
||||
const serverPassword = () => process.env.OPENCODE_SERVER_PASSWORD ?? ''
|
||||
|
|
@ -533,7 +534,9 @@ const cmdStart = async (flags) => {
|
|||
driverArgs({ url: server.url, sessionID, dir, model, invocation }),
|
||||
{ cwd: dir, logPath: state.logPath, label: `o driver do run (log: ${state.logPath})` },
|
||||
)
|
||||
} else if (purpose !== 'new') {
|
||||
} else if (purpose !== 'new' && purpose !== 'plan') {
|
||||
// `plan` sem bag é driverless por desenho (o humano digita o
|
||||
// `/plan-issue`); só os demais purposes sem comando são anomalia.
|
||||
console.warn(
|
||||
`[agent:session] purpose=${purpose} sem comando/issue válida — sessão criada sem driver.`,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -163,8 +163,9 @@ export const parseServerState = (text) => {
|
|||
|
||||
/**
|
||||
* Skill command per launch purpose — the terminal launch auto-submits it on
|
||||
* the server-owned session. `new` deliberately has none ("apenas conversar"):
|
||||
* the session is created and attached, no driver.
|
||||
* the server-owned session. `new` deliberately has none ("apenas conversar");
|
||||
* `plan` only submits `/plan-issue` when a bag (the opening message) is given,
|
||||
* otherwise it is driverless like `new` so the human can type the context.
|
||||
*/
|
||||
export const SESSION_COMMAND_BY_PURPOSE = {
|
||||
next: 'work-issue',
|
||||
|
|
@ -175,10 +176,12 @@ export const SESSION_COMMAND_BY_PURPOSE = {
|
|||
|
||||
/**
|
||||
* `{ command, arguments }` for the purpose, or null when there is nothing to
|
||||
* auto-submit. `next` carries only a valid claimed issue (`--issue N`), `fix`
|
||||
* carries the sanitized bag (quotes/backslashes stripped — the value rides a
|
||||
* single argv token), `plan` sends the bare command, unknown purposes degrade
|
||||
* to no command (fail-safe, mirrors the old directive).
|
||||
* auto-submit. `next` carries only a valid claimed issue (`--issue N`); `fix`
|
||||
* and `plan` carry the sanitized bag (quotes/backslashes stripped — the value
|
||||
* rides a single argv token). `fix` with an empty bag still submits the bare
|
||||
* `/bug-fix`; `plan` with an empty bag is driverless by design (no context to
|
||||
* plan with — mirrors `new`). Unknown purposes degrade to no command
|
||||
* (fail-safe, mirrors the old directive).
|
||||
* @param {{ purpose?: string, issueNumber?: number | string | null, argument?: string | null }} [options]
|
||||
*/
|
||||
export const purposeInvocation = ({ purpose = null, issueNumber = null, argument = null } = {}) => {
|
||||
|
|
@ -189,10 +192,11 @@ export const purposeInvocation = ({ purpose = null, issueNumber = null, argument
|
|||
if (!Number.isInteger(number) || number <= 0) return null
|
||||
return { command, arguments: `--issue ${number}` }
|
||||
}
|
||||
if (purpose === 'fix') {
|
||||
if (purpose === 'fix' || purpose === 'plan') {
|
||||
// Mesma sanitização da diretiva de launch (fronteira dupla de propósito:
|
||||
// o xargs da camada shell e o argv do `opencode run` não honram escapes).
|
||||
const sanitized = typeof argument === 'string' ? argument.replace(/["\\]/g, '').trim() : ''
|
||||
if (purpose === 'plan' && !sanitized) return null
|
||||
return { command, arguments: sanitized }
|
||||
}
|
||||
return { command, arguments: '' }
|
||||
|
|
|
|||
|
|
@ -188,11 +188,11 @@ const namespaceBranchName = ({ prefix, bag = '', taken = new Set(), fallback })
|
|||
* is always `<root without spaces>/<slugified branch>`, so the line never needs
|
||||
* quoting for the path. `next` with an `issueNumber` carries `--issue=<N>`
|
||||
* (OPS33: the launch delivers the claimed issue to the agent; the skill reads
|
||||
* the rest from GitHub), `fix` with an `argument` carries `--argument="<bag>"`
|
||||
* (the bug description arrives with the skill; quotes/backslashes are stripped
|
||||
* because the shell layer tokenizes with xargs, which does not honor
|
||||
* backslash escapes). Returns `null` outside the terminal so the `/worktree`
|
||||
* opencode command never launches a nested TUI.
|
||||
* the rest from GitHub), `fix` (bug description) and `plan` (opening message)
|
||||
* with an `argument` carry `--argument="<bag>"` (the text arrives with the
|
||||
* skill; quotes/backslashes are stripped because the shell layer tokenizes
|
||||
* with xargs, which does not honor backslash escapes). Returns `null` outside
|
||||
* the terminal so the `/worktree` opencode command never launches a nested TUI.
|
||||
* `sessionScript` é o caminho do CLI de sessão — `worktree.mjs` passa o
|
||||
* ABSOLUTO do checkout que emitiu a diretiva (worktrees reabertos/criados
|
||||
* antes do merge não têm o arquivo novo; o relativo apontaria para o branch
|
||||
|
|
@ -221,10 +221,10 @@ export const opencodeLaunchDirective = ({
|
|||
// The issue suffix belongs to `next` alone — `plan`/`new`/`fix` never carry
|
||||
// a claimed issue (fail-safe: a stray issueNumber must not break them).
|
||||
if (purpose === 'next' && issueNumber) args.push(`--issue=${issueNumber}`)
|
||||
// The bag suffix belongs to `fix` alone — the bug description arrives with
|
||||
// the skill. JSON.stringify quotes the value (it carries spaces); xargs
|
||||
// strips the quotes at execution time.
|
||||
if (purpose === 'fix') {
|
||||
// The bag suffix belongs to `fix` (bug description) and `plan` (opening
|
||||
// message) — the text arrives with the skill. JSON.stringify quotes the
|
||||
// value (it carries spaces); xargs strips the quotes at execution time.
|
||||
if (purpose === 'fix' || purpose === 'plan') {
|
||||
const sanitized = typeof argument === 'string' ? argument.replace(/["\\]/g, '').trim() : ''
|
||||
if (sanitized) args.push(`--argument=${JSON.stringify(sanitized)}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,11 +76,14 @@
|
|||
* deles (branch nem slot) colide com um `next`
|
||||
* posterior (prefixo minúsculo `plans/…`).
|
||||
* Mesmo provisionamento isolado do `next`; no
|
||||
* terminal, mesma diretiva `launch` — o driver
|
||||
* destacado auto-submete `/plan-issue` (OPS31: a
|
||||
* sessão abre no fluxo de planejamento, sem
|
||||
* digitação) e `--model <map>` quando a flag de
|
||||
* modelo está presente (OPS110: attach/detach).
|
||||
* terminal, mesma diretiva `launch` — `--model`
|
||||
* `<map>` quando a flag está presente (OPS110:
|
||||
* attach/detach). Com `bag`, o driver destacado
|
||||
* auto-submete `/plan-issue <bag>`; sem `bag`, a
|
||||
* sessão abre SEM driver (como o `new`) para o
|
||||
* humano digitar o `/plan-issue` com contexto
|
||||
* (OPS119; supersede o auto-envio incondicional
|
||||
* do OPS31).
|
||||
* pnpm worktree new [bag] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba|--glm|--free]
|
||||
* cria um worktree NEUTRO novo — sem função
|
||||
* pré-definida (explorar ideia, conversar, ou
|
||||
|
|
@ -670,7 +673,8 @@ const buildTakenBranchNames = () => {
|
|||
* and prints the `cd <dir>` line by default (`--stay` suppresses). Every
|
||||
* invocation creates a DIFFERENT worktree — parallel sessions never share one.
|
||||
* `argument` (optional) rides the launch prompt — for `fix`, the bug
|
||||
* description the `/bug-fix` skill receives.
|
||||
* description the `/bug-fix` skill receives; for `plan`, the opening message
|
||||
* the `/plan-issue` driver auto-submits (absent → driverless session).
|
||||
*/
|
||||
const cmdNamespaceBranch = async ({
|
||||
stay,
|
||||
|
|
@ -761,6 +765,7 @@ const cmdPlan = async (stay, skipMigrate, bag, flags = {}) =>
|
|||
sessionLabel: bag && bag.trim() ? `lote "${bag}"` : 'sequencial',
|
||||
branchName: (taken) => planBranchName({ bag, taken }),
|
||||
flags,
|
||||
argument: bag,
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
@ -988,7 +993,7 @@ if (!subcommand) {
|
|||
' minúsculo plans/… nunca colide com o branch <code>-<slug> de `next`; no terminal,',
|
||||
)
|
||||
console.log(
|
||||
' mesma diretiva `launch` — o driver destacado auto-submete /plan-issue (abre no fluxo de planejamento, sem digitação) e --model <map> quando a flag está presente',
|
||||
' mesma diretiva `launch` — --model <map> quando a flag está presente; com bag o driver auto-submete /plan-issue <bag>, sem bag a sessão abre SEM driver (você digita o /plan-issue)',
|
||||
)
|
||||
console.log(
|
||||
`\n new [bag] [--stay] [--no-migrate] [--cheap|--pro|--zen|--go|--alibaba|--glm|--free]`,
|
||||
|
|
|
|||
|
|
@ -215,11 +215,20 @@ describe('purposeInvocation (mapa purpose→comando movido do worktree)', () =>
|
|||
expect(purposeInvocation({ purpose: 'next', issueNumber: 0 })).toBeNull()
|
||||
})
|
||||
|
||||
it('plan sends the bare /plan-issue command', () => {
|
||||
expect(purposeInvocation({ purpose: 'plan' })).toEqual({ command: 'plan-issue', arguments: '' })
|
||||
expect(purposeInvocation({ purpose: 'plan', issueNumber: 7 })).toEqual({
|
||||
it('plan without a bag starts no driver — the human types the context', () => {
|
||||
expect(purposeInvocation({ purpose: 'plan' })).toBeNull()
|
||||
expect(purposeInvocation({ purpose: 'plan', issueNumber: 7 })).toBeNull()
|
||||
expect(purposeInvocation({ purpose: 'plan', argument: ' "" ' })).toBeNull()
|
||||
})
|
||||
|
||||
it('plan carries the opening message as the sanitized bag (xargs/argv-safe)', () => {
|
||||
expect(purposeInvocation({ purpose: 'plan', argument: 'revisa o gate do designer' })).toEqual({
|
||||
command: 'plan-issue',
|
||||
arguments: '',
|
||||
arguments: 'revisa o gate do designer',
|
||||
})
|
||||
expect(purposeInvocation({ purpose: 'plan', argument: 'a"b\\c' })).toEqual({
|
||||
command: 'plan-issue',
|
||||
arguments: 'abc',
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -357,14 +366,14 @@ describe('driverArgs + attachArgs (o argv verificado ao vivo)', () => {
|
|||
])
|
||||
})
|
||||
|
||||
it('omits the `--` separator when there are no arguments (plan)', () => {
|
||||
it('omits the `--` separator when the invocation carries no arguments (fix with an empty bag)', () => {
|
||||
expect(
|
||||
driverArgs({
|
||||
url: 'http://127.0.0.1:4199',
|
||||
sessionID: 'ses_abc',
|
||||
dir: '/work/plan',
|
||||
dir: '/work/fix',
|
||||
model: 'm',
|
||||
invocation: { command: 'plan-issue', arguments: '' },
|
||||
invocation: { command: 'bug-fix', arguments: '' },
|
||||
}),
|
||||
).toEqual([
|
||||
'run',
|
||||
|
|
@ -373,12 +382,12 @@ describe('driverArgs + attachArgs (o argv verificado ao vivo)', () => {
|
|||
'-s',
|
||||
'ses_abc',
|
||||
'--dir',
|
||||
'/work/plan',
|
||||
'/work/fix',
|
||||
'--model',
|
||||
'm',
|
||||
'--auto',
|
||||
'--command',
|
||||
'plan-issue',
|
||||
'bug-fix',
|
||||
])
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ describe('opencodeLaunchDirective (terminal-only agent-session launch, OPS26 + O
|
|||
)
|
||||
})
|
||||
|
||||
it('plan launches through the same session CLI (the skill is auto-submitted by the driver)', () => {
|
||||
it('plan without a bag launches session-only — the human types /plan-issue', () => {
|
||||
expect(opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true })).toBe(
|
||||
`launch node scripts/agent-session.mjs start --purpose=plan --dir=${dir} --model=${presetInEffect()}`,
|
||||
)
|
||||
|
|
@ -252,11 +252,11 @@ describe('opencodeLaunchDirective (terminal-only agent-session launch, OPS26 + O
|
|||
)
|
||||
})
|
||||
|
||||
it('the argument belongs to fix alone — plan/new ignore it', () => {
|
||||
it('the argument belongs to fix and plan — new ignores it', () => {
|
||||
expect(
|
||||
opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true, argument: 'bag x' }),
|
||||
).toBe(
|
||||
`launch node scripts/agent-session.mjs start --purpose=plan --dir=${dir} --model=${presetInEffect()}`,
|
||||
`launch node scripts/agent-session.mjs start --purpose=plan --dir=${dir} --model=${presetInEffect()} --argument="bag x"`,
|
||||
)
|
||||
expect(
|
||||
opencodeLaunchDirective({ dir, purpose: 'new', terminal: true, argument: 'bag x' }),
|
||||
|
|
@ -265,6 +265,19 @@ describe('opencodeLaunchDirective (terminal-only agent-session launch, OPS26 + O
|
|||
)
|
||||
})
|
||||
|
||||
it('plan strips quotes/backslashes from the bag and drops --argument when it empties', () => {
|
||||
expect(
|
||||
opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true, argument: 'a"b\\c ideia' }),
|
||||
).toBe(
|
||||
`launch node scripts/agent-session.mjs start --purpose=plan --dir=${dir} --model=${presetInEffect()} --argument="abc ideia"`,
|
||||
)
|
||||
expect(
|
||||
opencodeLaunchDirective({ dir, purpose: 'plan', terminal: true, argument: ' "" ' }),
|
||||
).toBe(
|
||||
`launch node scripts/agent-session.mjs start --purpose=plan --dir=${dir} --model=${presetInEffect()}`,
|
||||
)
|
||||
})
|
||||
|
||||
it('pins the preset constants — fallback comum deepseek-flash, override via OPENCODE_WORKTREE_MODEL', () => {
|
||||
expect(OPENCODE_PRESET_MODEL).toBe(presetInEffect())
|
||||
expect(WORKTREE_TERMINAL_ENV).toBe('TEQO_WORKTREE_TERMINAL')
|
||||
|
|
|
|||
Loading…
Reference in a new issue