| name | description | user_invocable |
|---|---|---|
ddev-worktree |
Spin up an isolated DDEV site from a git worktree so multiple features can run side-by-side on the same Drupal codebase. |
true |
Spin up a fresh, conflict-free DDEV site for a feature branch. Each environment is a git worktree with its own DDEV project, database, and URL — completely independent from the main site.
Before any operation, detect the current project's DDEV name and directory:
PROJECT_NAME=$(grep '^name:' .ddev/config.yaml | awk '{print $2}')
PROJECT_DIR=$(basename "$PWD")Use $PROJECT_NAME everywhere below instead of hardcoding any project name.
Main site: <project>.ddev.site ← current directory
Feature A: <project>-ams-720.ddev.site ← ../<project>-ams-720 (worktree)
Feature B: <project>-ams-721.ddev.site ← ../<project>-ams-721 (worktree)
Each is a full Drupal site running independently. No branch switching, no conflicts.
When the user asks to spin up a new environment (e.g., /ddev-worktree ams-720):
PROJECT_NAME=$(grep '^name:' .ddev/config.yaml | awk '{print $2}')ddev export-db --file=/tmp/${PROJECT_NAME}-db.sql.gzgit worktree add ../${PROJECT_NAME}-<slug> -b <branch-name> main<slug> is a short kebab-case identifier from the feature name or ticket number.
The main project has a name: in .ddev/config.yaml. Each worktree needs a unique name to avoid port conflicts.
cd ../${PROJECT_NAME}-<slug>
sed -i '' "s/^name: ${PROJECT_NAME}$/name: ${PROJECT_NAME}-<slug>/" .ddev/config.yamlddev start
ddev import-db --file=/tmp/${PROJECT_NAME}-db.sql.gz
ddev composer install
ddev drush crTell the user:
- URL:
https://${PROJECT_NAME}-<slug>.ddev.site - Path:
../${PROJECT_NAME}-<slug> - Branch:
<branch-name>
When the user provides multiple features, spin up one environment per feature and dispatch agents in parallel.
For each feature, launch an Agent in a single message (all concurrent):
Agent(
subagent_type: "general-purpose",
isolation: "worktree",
run_in_background: true,
description: "<3-5 word summary>",
prompt: <see Agent Prompt below>
)
Fill PROJECT_NAME with the detected value before dispatching:
You are working on a Drupal 10 feature in an isolated git worktree.
## Project
- Drupal 10, docroot: `web/`, PHP 8.2, MariaDB 10.11, nginx-fpm
- Composer root is the project root
- DDEV for local dev
- Main DDEV project name: <PROJECT_NAME>
## Setup (run first)
1. sed -i '' 's/^name: <PROJECT_NAME>$/name: <PROJECT_NAME>-<SLUG>/' .ddev/config.yaml
2. ddev start
3. ddev import-db --file=/tmp/<PROJECT_NAME>-db.sql.gz (skip if file missing)
4. ddev composer install
5. ddev drush cr
## Task
Branch: <BRANCH>
<FEATURE_DESCRIPTION>
## Rules
- Write Drupal-standards-compliant code
- Run `ddev drush cr` after changes
- Commit with a clear message when done
- Do NOT push
As agents finish, summarize:
| Feature | Branch | Status | URL | Path |
|---|---|---|---|---|
| Ticket-1 | feature-1 | Done | <project>-feature-1.ddev.site |
../<project>-feature-1 |
| Ticket-2 | feature-2 | Done | <project>-feature-2.ddev.site |
../<project>-feature-2 |
git worktree list
ddev listcd ../${PROJECT_NAME}-<slug> && ddev stop --remove-data
cd - && git worktree remove ../${PROJECT_NAME}-<slug>
git branch -d <branch-name> # only if mergedPROJECT_NAME=$(grep '^name:' .ddev/config.yaml | awk '{print $2}')
for wt in $(git worktree list --porcelain | grep '^worktree' | grep -v "/${PROJECT_NAME}$" | awk '{print $2}'); do
(cd "$wt" && ddev stop --remove-data 2>/dev/null)
done
git worktree prune
rm -f /tmp/${PROJECT_NAME}-db.sql.gz- 3-4 concurrent environments max (~512MB RAM each DDEV instance)
- Every worktree MUST have a unique
namein.ddev/config.yaml - Agents commit but never push — user reviews and pushes manually
- DB export is a point-in-time snapshot; worktree DBs diverge from there