# Impl: Worktree `new`: worktree genérico sem função pré-definida Status: aprovado Atualizado em: 2026-08-10 Issue: #572 Intenção: docs/plans/worktree-new-worktree-generico.md Appetite restante: ~0,5–1 dia eng (herdado — scripts/docs/testes, sem runtime nem DB de dados) ## Leitura da intenção - **Outcome:** `worktree new [bag]` cria um worktree **neutro** a partir de `origin/main` com branch `work/` (colisão → sufixo `-2`/`-3`) ou `work/` sequencial livre, provisionado de forma idêntica ao `plan` (slot/porta/bancos próprios, migrations aplicadas, `--no-migrate` respeitado, comentário `gerado por pnpm worktree new`), cada invocação cria UM DIFERENTE, herda o default-go da OPS24 (`--stay` suprime, `--go` no-op), `kill` funciona de dentro dele, help/uso + AGENTS.md atualizados. - **O que NÃO negociar:** prefixo minúsculo `work/…` que nunca colide com `-` do `next` nem com `plans/plan-issue-…` do `plan`; provisionamento idêntico ao `plan`; `new` **não** é um segundo `plan` (sem função de planejamento embutida); OPS26 (launch do opencode) é dependência **opcional e não está em main** — o `new` nasce só com o default-go, sem duplicar o launch. - **O que reavaliar:** - A hipótese "generalizar `planBranchName` se valer a pena" — **vale**: a matemática de sufixo de colisão (`-2`/`-3`), sequencial livre e truncamento é idêntica e sutil entre os dois namespaces; um helper único impede drift (mesma família de guarda que o repo já aplica nos prefixos next/plan). - **`GENERATED_ENV_MARKER` é congelado:** o unit test pina o texto exato `generated by pnpm worktree next` e `readLiveSlots`/`worktreeDatabaseNamesOf`/`envFileIsGenerated` dependem dele para detectar envs geradas (inclusive de worktrees `next` vivos). O `new` **não** parametriza o marker — o propósito real aparece na linha `generatedBy` do header (`gerado por pnpm worktree new`), que o `provision` já deriva de `purpose`. ## Abordagem recomendada ```mermaid flowchart LR A[pnpm worktree new [bag]] --> B[namespaceBranchName: work/ ou work/] A2[pnpm worktree plan [bag]] --> B2[planBranchName: plano existente] B --> C[runner compartilhado cmdNamespaceBranch: fetch → taken-set → add → provision → cd] B2 --> C C --> D[worktree.mjs: purpose 'new'/'plan' + linhas cd por padrão] D --> E[superfícies: worktree.sh + /worktree opencode aplicam cd] ``` **Decisão 1 — nomeação de branch (`scripts/lib/worktree.mjs`).** Opções: A) extrair helper interno `namespaceBranchName({ prefix, bag, taken, maxLength, fallback })`; `planBranchName` vira wrapper fino (contrato + testes intactos) e novo `workBranchName` com `WORK_BRANCH_PREFIX = 'work'`; B) parametrizar `planBranchName` com prefixo opcional; C) duplicar a lógica em `workBranchName`. **Recomendação: A** — a matemática de colisão/sequencial/truncamento é idêntica e sutil; um único lugar evita drift (os dois namespaces vivem no mesmo arquivo há uma semana de evolução ativa). **Rejeitadas:** B — `planBranchName` produzindo `work/…` mente no nome e obriga default paramétrico; C — duplica ~25 linhas de truncamento delicado (budget 60, `keep` mínimo 1). Detalhes do contrato `workBranchName`: sem bag → próximo `work/` sequencial livre (começa em `work/1`); com bag → `work/`; colisão → `work/-2`, `-3`, …; slug vazio → fallback `'work'` (o `plan` preserva o fallback `'plano'`); bag numérico (`work new 3`) compartilha o namespace sequencial (precedente uniforme do plan); budget total 60 (espelha `branchNameForIssue`/`PLAN_BRANCH_MAX_LENGTH`); o prefixo `work/` é lowercase-led → estruturalmente disjunto do `-` uppercase-led e do `plans/plan-issue-…`. **Decisão 2 — `cmdNew` (`scripts/worktree.mjs`).** Opções: A) extrair de `cmdPlan` o runner compartilhado `cmdNamespaceBranch({ purpose, branchName, label })` (fetch → taken-set → `check-ref-format` → `worktree add` → `provision` → summary do ambiente → `cd`), com `cmdPlan` refatorado para o runner e `cmdNew` como segundo call site; B) clonar `cmdPlan` em `cmdNew` (~60 linhas); C) mode flag dentro de `cmdPlan`. **Recomendação: A** — o CLI está em evolução ativa (OPS24/OPS26/OPS27 na mesma semana); duplicar ~60 linhas de git/provisionamento drifta. **Rejeitadas:** B — duplicação cara de manter; C — embaralha propósito em flag e cresce o teste mental do dispatch. **Decisão 3 — marker de env.** Opções: A) `GENERATED_ENV_MARKER` inalterado (texto histórico `…next`) para todos os purposes; B) parametrizar o marker por purpose. **Recomendação: A** — o marker é contrato congelado e pinado: `readLiveSlots`, `worktreeDatabaseNamesOf`, `envFileIsGenerated` e o unit test dependem do texto exato; mudar quebraria a detecção de envs de worktrees `next` vivos e exigiria "migração" de marker. O comentário real por-worktree já diz o propósito (`generatedBy`). **Rejeitada:** B. ### Componentes / mudanças - **`scripts/lib/worktree.mjs`**: helper interno `namespaceBranchName`; exports `WORK_BRANCH_PREFIX = 'work'` e `workBranchName`; `planBranchName` refatorado para wrapper (contrato idêntico, testes intactos). - **`scripts/worktree.mjs`**: runner `cmdNamespaceBranch` extraído de `cmdPlan` (taken-set, branch, add, provision, summary, cd); `cmdNew(stay, skipMigrate, bag)` com `purpose: 'new'`; dispatch com `new`; bloco de uso (`pnpm worktree` sem args) e docblock do header atualizados (`new [bag] [--stay] [--no-migrate]`). - **`tests/unit/worktree.unit.spec.ts`**: describe novo `workBranchName` — sequencial livre (skip de taken), bag → `work/`, colisão `-2`/`-3`, slug vazio → fallback, bag numérico compartilha o namespace, truncamento ≤60, nunca colide com `next` nem `plan` (regex de lowercase-led), invocações paralelas → branches diferentes. - **`.agents/shell/worktree.sh`**: header de uso com `new [bag] [--stay]` (a função já é genérica — repassa args e aplica o `cd`). - **`.opencode/commands/worktree.md`**: frontmatter `description` + corpo com `new [bag] [--stay]` (mesmo contrato de `cd` do `plan`). - **`AGENTS.md`** (seção "Per-worktree environments"): subcomando `new [bag]` + namespace `work/…` + `purpose: 'new'`. - **`.agents/skills/worktree-next-issue/SKILL.md`**: linha dos subcomandos com `new [bag] [--stay] [--no-migrate]`. - **`docs/CHANGELOG-AGENTS.md`**: entrada curta da entrega. - **Migration:** sem migration (nenhum schema). **Access / Consent:** N/A — CLI de dev fora do runtime. **UI:** Impeccable A — N/A (sem superfície de produto). ## Fases verificáveis 1. **Lib pura + testes** — `namespaceBranchName`/`workBranchName`/`WORK_BRANCH_PREFIX` + `workBranchName.unit.spec.ts`; `pnpm test:unit` no arquivo. 2. **Runner + comando** — `cmdNamespaceBranch` (refactor de `cmdPlan`) + `cmdNew` + dispatch + uso + docblock; superfícies (shell/opencode) + docs (AGENTS.md, skill, CHANGELOG). 3. **Verificação manual do caminho feliz** — um `worktree new teste-gate` real (cria worktree + bancos + migrations no container local), conferir branch `work/teste-gate`, env com `gerado por pnpm worktree new` e porta própria, e derrubar com `pnpm worktree kill --force` de dentro (valida também o `kill` num branch `work/…`). `pnpm worktree` sem args mostra o `new` no uso. 4. **Gates** — `pnpm gate:fast` na iteração; `pnpm push` na entrega (format/knip/cycles/test/build). ## Rabbit holes / Não escopo (engenharia) - **Launch do opencode no `new`**: OPS26 é o dono e não está em main — o `new` nasce com default-go; a superfície é a mesma que a OPS26 estenderá (nada a construir). - Não tocar `scripts/lib/worktree-env.mjs` nem `GENERATED_ENV_MARKER` (pinados por teste). - Não criar terceira função embutida (`worktree new --issue` etc.) — para isso existem `next` e `plan`. - Não renomear/normalizar branches `work/…` manuais existentes nem "migrar" markers de envs vivas. - Sem e2e: CLI fora do runtime; cobertura = unit da lib pura + verificação manual descartável. ## Riscos e mitigação - **Refatorar `cmdPlan` introduz regressão no fluxo existente** — mitigação: extração com diff minimal (movimentação pura de corpo + parâmetros `purpose`/`label`/branch fn), mensagens de saída idênticas; conferir por diff e pelo caminho feliz do `new` (compartilha o runner). - **`work/…` colide com branch manual do usuário** — taken-set local+origin já cobre (sufixo `-2`/`-3`); comportamento idêntico ao `plan`, documentado. - **Slot do `new` colide com worktrees vivos** — herdado: `readLiveSlots` + bump no `worktreeEnvironment` (já testado). - **Texto do marker `next` em envs de `new` confundir leitura** — a linha `generatedBy` carrega o propósito real; registrado neste impl plan. - **`worktree new` sem Docker degrada para env compartilhado** — herdado do `provision` (mesmo aviso do `next`/`plan`). ## Aceite de engenharia - [ ] Aceite de produto da intenção ainda coberto (`new [bag]` de `origin/main`; branch `work/`/`work/` sem colisão; provision igual ao `plan` com `purpose: 'new'` e `--no-migrate` respeitado; default-go/`--stay`; `kill` funciona; uso + AGENTS.md atualizados) - [ ] Invariantes AGENTS/engineering-standards (marker e `worktree-env.mjs` intocados; nenhum DB de prod; libs puras unit-testadas) - [ ] Testes de domínio previstos: `workBranchName` (unit) + caminho feliz manual descartável