teqo-backup/docs/plans/worktree-new-worktree-generico-impl.md

9.3 KiB
Raw Blame History

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

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