Imported from VictorStochero/laravel-worktree (
skills/laravel-worktree/SKILL.md). Install upstream withnpx skills add VictorStochero/laravel-worktree --skill laravel-worktree. Copyright stays with the author.
Laravel Worktree (multi-runtime)
Cria e opera git worktrees isolados para projetos Laravel — um ambiente paralelo completo por branch, sem tocar no checkout original. O runtime local (Herd / DDEV / Sail) muda só os detalhes de shell, domínio, banco e ciclo de container; o esqueleto (git worktree, base branch, .env, verificação da suíte) é comum. O foco é provisionamento rápido: subir o ambiente paralelo pronto para uso. O provisionamento nunca executa testes — apenas confirma que a suíte está descobrível.
Três operações: criar, inspecionar, limpar.
Validação: o branch Herd foi exercitado ponta a ponta. Os branches DDEV e Sail tiveram os comandos conferidos contra a documentação oficial (03/09/2026), mas não foram executados num ambiente real — confirmar o primeiro uso em cada um e abrir issue com o que divergir.
Passo 0 — Detectar o runtime
Decidir o runtime ativo (primeira condição que casar; em ambiguidade, perguntar):
- DDEV → existe
.ddev/config.yamlno repo. - Sail → existe
vendor/bin/sail(ou./vendor/bin/sail) e umdocker-compose.yml/compose.yamlreferenciandolaravel/sail, e não há.ddev/. - Herd → caso contrário (PHP no host; macOS/Windows com Herd). É o default.
Cada runtime define quatro coisas, usadas pelas operações abaixo:
| Herd | DDEV | Sail | |
|---|---|---|---|
| Shell p/ php/composer/npm | PowerShell (Windows) / shell do host. Não Git Bash no Windows | ddev composer, ddev artisan, ddev exec |
./vendor/bin/sail composer, sail artisan |
| Domínio | {pasta}.test |
{pasta}.ddev.site |
http://localhost:{APP_PORT} (porta única por worktree) |
| Servir | pasta na raiz parkeada do Herd (auto) | ddev config + ddev start na pasta |
sail up -d (compose project único) |
| Banco | DB dedicado no host (127.0.0.1, criar/dropar manualmente) |
isolado por projeto DDEV (auto — não criar à mão) | isolado no compose (volume -v no teardown) |
Os comandos git rodam sempre no shell do host (nunca dentro do container), em qualquer runtime. No Windows isso significa PowerShell: instalações com MinGit não trazem bash, então não assumir que existe uma ferramenta Bash disponível.
Conceitos comuns (todos os runtimes)
- Entradas:
flag∈ { feature, fix, hotfix, chore, docs, test, refactor };contextoem kebab-case;data= hojeAAAAMMDD;repo= nome do diretório do repositório. - Pasta:
{repo}-{flag}-{contexto}— sem data: a pasta vira o domínio local ({pasta}.testno Herd) e a data só polui a URL. Branch:{flag}/{AAAAMMDD}-{contexto}— a data fica aqui, onde é útil (histórico, ordenação, PRs). O par pasta↔branch se recupera degit worktree list --porcelain, nunca de parsing do nome da pasta. - Não aninhar: se
git rev-parse --git-dir≠--git-common-dire não for submódulo (git rev-parse --show-superproject-working-treevazio), já se está num worktree — parar. - Colisão (checar antes de criar): pasta de destino já existente, ou branch já existente (
git rev-parse --verify {branch}), ou worktree já registrado para essa branch (git worktree list) → parar e perguntar (reaproveitar o worktree existente? outro contexto? limpar o antigo primeiro?). Sem a data no nome da pasta, repetir o mesmo{flag} {contexto}colide de propósito — é o comportamento desejado, não um erro a contornar sozinho. - Nomes de banco (Herd):
{ctx_}=contextocom-trocado por_(hífen em nome de banco obriga backtick e quebra em vários pontos). Dev ={db}_wt_{ctx_}, teste ={db}_wt_{ctx_}_test; multi-tenant →TENANCY_DB_PREFIX={db}_wt_{ctx_}_tenant_. MySQL/MariaDB limitam nome de banco a 64 caracteres: se estourar, truncar o{ctx_}(nunca o sufixo_test, que é o que separa os dois bancos) e reportar o nome final ao usuário. - Base remota:
git fetch; derivar a base na primeira que existir emorigin/:desenvolvimento→dev→develop→main. Gravar em{pasta}/.worktree-basee, uma única vez, acrescentar a linha.worktree-basea.git/info/excludedo repo — oinfo/excludeé comum a todos os worktrees, e sem isso o arquivo aparece untracked nogit statusde cada um (e acaba commitado por acidente). .env(chaves presentes apenas):APP_URL→ domínio do runtime; multi-tenant (detectarconfig/tenancy.phpoustancl/tenancy) →CENTRAL_DOMAIN/TENANT_BASE_DOMAIN= domínio,SESSION_DOMAIN=.{domínio-base}; prefixos únicos (REDIS_PREFIX/CACHE_PREFIXouREDIS_DBdistinto); Warden child (detectarconfig/warden.php+WARDEN_MODE=child) →WARDEN_ENABLED=false; manterAPP_KEY.- Localização: worktree sempre fora do repo. No Herd, pasta-irmã na raiz parkeada. Nunca usar ferramenta de worktree nativa que ignore o setup do runtime (domínio/
.env/DB).
Operação A — CRIAR
- Detectar runtime (Passo 0), não aninhar e checar colisão (pasta/branch/worktree já existentes).
- Base remota +
git worktree add <destino> -b {branch} origin/{base}; gravar.worktree-base.- Herd:
<destino>=<raiz_herd>/{pasta}. - DDEV/Sail:
<destino>pode ser pasta-irmã qualquer fora do repo.
- Herd:
.env: copiar da origem e aplicar os ajustes comuns. Banco por runtime:- Herd:
DB_DATABASE(eTENANCY_DB_DATABASE/TENANCY_DB_PREFIXse existirem) pelos nomes de banco definidos nos conceitos comuns. - DDEV/Sail: banco é isolado pelo container — não apontar para um DB compartilhado; manter o esquema do runtime (DDEV injeta
db; Sail usa o serviço do compose). No Sail, todo worktree que sobe ao mesmo tempo precisa deCOMPOSE_PROJECT_NAMEe de todas as portas publicadas únicas:APP_PORT,VITE_PORT,FORWARD_DB_PORTe asFORWARD_*_PORTdos serviços presentes no compose (Redis, Mailpit, Meilisearch…). Basta uma repetida para oupfalhar com port is already allocated.
- Herd:
- Subir o ambiente do runtime:
- Herd: nada a subir (a pasta já é servida). Criar o DB dedicado (PDO via
.phptemporário →CREATE DATABASE IF NOT EXISTS, apagar o temp). - DDEV:
ddev config --project-name={pasta} --project-type=laravel --docroot=public(ou copiar.ddev/config.yamle trocar oname) →ddev start. - Sail: garantir
vendor/(passo 5) esail up -d.
- Herd: nada a subir (a pasta já é servida). Criar o DB dedicado (PDO via
- Dependências:
- Herd:
composer install. Em Windows, se falhar porext-pcntl/ext-posix(Horizon e libs Unix-only), repetir com--ignore-platform-req=ext-pcntl --ignore-platform-req=ext-posix(+ as extensões reportadas). - DDEV:
ddev composer install(container tem pcntl/posix — sem flags). - Sail:
composer installno host para obtervendor/bin/sail, depoissail composer installse preferir paridade de container.
- Herd:
- Migrar:
migrate --seedpelo shell do runtime (limpo + seed — não clonar dados). Herd:php artisan migrate --seed. DDEV:ddev artisan migrate --seed. Sail:sail artisan migrate --seed. storage:linkpelo shell do runtime.- Front-end: se houver
package.json,npm install(Herd) /ddev npm install/sail npm install(+ build se necessário). - Ambiente de teste: se houver
.env.testingversionado, alinhar o host de DB ao runtime ativo. Um.env.testingapontando paradb/mysql(DDEV/Docker) quebra no Herd (getaddrinfo for db failed) e vice-versa. Se existir um exemplo do runtime (.env.testing.example-herd,.env.testing.example-ddev, etc.), sobrepor o correto como.env.testinge usar um banco de teste dedicado ao worktree (nomes nos conceitos comuns; no DDEV/Sail o do próprio container); criá-lo e migrar commigrate --env=testing. Banco de teste compartilhado entre worktrees é falha garantida:RefreshDatabase/migrate:freshde um derruba o schema do outro, e o sintoma (Table ... already exists, coluna desconhecida, suíte inteira vermelha) não parece concorrência. - Verificar a suíte — nunca rodá-la: o provisionamento jamais executa a suíte de testes, nem inteira, nem por
--testsuite, nem "só os Unit". A verificação é... artisan test --list-tests: a listagem sozinha já faz o boot da app e valida autoload,.env.testing, conexão com o banco de teste e descoberta dos testes — em segundos, sem executar um único teste. Se a listagem falhar (fatal, host de DB errado, autoload quebrado), reportar e perguntar. Rodar a suíte é decisão do usuário, depois, sob demanda — e aí não confundir regressão nova com falha pré-existente (URL/host hardcoded, ou teste que depende da suíte inteira ter migrado o banco de teste).
Reportar (criação)
Pasta + URL; branch + base (.worktree-base); bancos criados (dev e teste, com os nomes finais); verificação da suíte (descoberta OK / falha + causa). Dizer explicitamente que nenhum teste foi executado — rodar a suíte fica a cargo do usuário.
Operação B — INSPECIONAR
git worktree list --porcelain→ pasta, branch, HEAD.- Enriquecer (best-effort): URL (pelo runtime + nome da pasta), base (
.worktree-base), banco/projeto, e estado sujo (git -C <pasta> status -sb). - Tabela: pasta | branch | base | URL | banco/projeto | sujo?. Sinalizar órfãos (pasta sumiu, registro existe) → sugerir
git worktree prune. Para DDEV, complementar comddev list.
Operação C — LIMPAR
Confirmar antes de qualquer passo destrutivo.
- Identificar alvo (pasta/branch). Ler
{pasta}/.env(banco) e.worktree-baseantes de remover. - Salvaguarda:
git -C <pasta> status -sb— havendo mudanças não commitadas/não enviadas, avisar e exigir confirmação explícita. - Teardown do runtime (libera o banco isolado):
- DDEV:
ddev delete --omit-snapshot --yes {pasta}(remove projeto + banco). Sem--yeso comando pede confirmação interativa e trava numa sessão não-interativa;ddev deletenão apaga a pasta de código — quem faz isso é o passo 4. - Sail:
sail down -vna pasta (remove containers + volumes). - Herd: dropar os bancos dedicados — dev,
_teste, em multi-tenant, todos os que começam com o prefixo de tenant do worktree (SHOW DATABASES LIKE '{db}_wt_{contexto}%'e conferir a lista com o usuário antes) — via.phptemporário (DROP DATABASE IF EXISTS, apagar o temp).
- DDEV:
git worktree remove <pasta>(--forcesó se necessário e confirmado). No Windows, oremovepode esvaziar o registro mas deixar a pasta (lock de arquivo) — conferir e apagar o residual comRemove-Item -Recurse -Force <pasta>.git branch -d <branch>(-Dsó se não mergeada e confirmado).git worktree prune.
Reportar (limpeza)
O que foi removido (pasta, branch, banco/projeto) e o que foi preservado/abortado por salvaguarda.
Pull Request
PR sempre com alvo na base de .worktree-base (nunca main se a origem foi develop/dev/desenvolvimento): gh pr create --base <base> --head <branch>.
Red flags
- Worktree dentro do repo ou aninhado.
- Misturar shells/domínios de runtimes (ex.:
php artisanno host num projeto DDEV;.testno Sail). - Clonar dados da origem em vez de
migrate --seedlimpo. - Executar testes no provisionamento — suíte inteira,
--testsuite=Unit,--filter"só para conferir": nada disso. A verificação é--list-testse só. Também não tratar falha pré-existente como bloqueio. - Banco de teste compartilhado entre worktrees (ou com o checkout original) — cada worktree tem o seu, senão um
RefreshDatabasederruba o outro. - Datar o nome da pasta (polui a URL) ou tentar deduzir a branch a partir do nome da pasta em vez de
git worktree list --porcelain. - Dropar banco /
ddev delete/sail down -v/--forcesem confirmar mudanças não salvas.