81 lines
9.3 KiB
Markdown
81 lines
9.3 KiB
Markdown
# 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/<bag>` (colisão → sufixo `-2`/`-3`) ou `work/<n>` 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 `<Code>-<slug>` 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/<bag> ou work/<n>]
|
||
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/<n>` sequencial livre (começa em `work/1`); com bag → `work/<slug>`; colisão → `work/<slug>-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 `<Code>-<slug>` 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/<slug>`, 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/<bag>`/`work/<n>` 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
|