Over the past month I have been working with agentic development using GitHub Copilot in agent mode. This article covers what worked, what did not, and how I refined the setup to get consistent results.
Before moving to agentic development, I had already been using AI to generate tickets for a couple of months. Instead of writing them manually, I used AI to produce tickets with a standard structure. I built a custom agent in Microsoft Copilot for this. With very little input, I get a complete ticket that includes context, requirements, and expected outcomes.
These tickets are passed to GitHub Copilot in plan mode for the AI agent to review, ask questions and make a plan for us to iterate over until it's something that will work. This step removed a lot of ambiguity before any code is written and gave me a chance to validate the approach before implementation starts. The LLM I'm currently using is Claude Sonnet 4.6 as it has a 1x token usage as apposed to Claude Opus 4.6 which is 3x.
Starting point
I began with the DDEV AGENTS.md file provided by amazee.io: https://github.com/amazeeio/drupal-agents-md
This gave me a working baseline, but I found I needed to add two important rules early on:
- Always import configuration before starting development
- Always export configuration when completing tasks
These rules reduced mistakes and made sure the agent was working with up to date configuration. Instead of writing this myself, I told the agent to add them to my AGENTS.md file.
Early problems
Even with a good starting file, the agent had to read a large amount of configuration and code to understand the system. This slowed it down and sometimes led to incorrect assumptions. It was trying to build a mental model from scratch each time.
Rather than manually fixing this, I asked the AI how to improve the setup. The AI agent provided some really useful points after complimenting my AGENTS.md file.
Introducing AI-focused documentation
The main recommendation was to create dedicated documentation for the agent. I used GitHub Copilot in agent mode to generate the files under docs/ai and then referenced them in AGENTS.md.
The generated files were:
- architecture.md
- changes.md
- content-model.md
- deployment.md
- environment.md
- integrations.md
- testing.md
This changed how the agent worked. Instead of scanning large parts of the codebase, it could read focused summaries. The result was faster execution and fewer incorrect assumptions.
Refining AGENTS.md
AGENTS.md is not a static file. It should evolve as you work, so when the agent does something incorrect, I don't just fix the output. I get the AI to update AGENTS.md with clearer instructions and fix the issue. For example:
- If it skips a required step, I tell it to add that step to AGENTS.md and run the step.
- If it uses the wrong approach, I define the preferred method and tell it to add it to AGENTS.md
- If it misinterprets context, I point it to the correct documentation and to expand the documentation to include this.
Over time, this builds a feedback loop. The agent improves because the instructions improve by evolving the base prompt.
Using planning mode
I started off by passing the information to agent mode, but after a while I started to use planning mode and instantly the results were better.
Tickets are passed into plan mode first. This allows the agent to produce a structured plan before any code is written.
In this step the agent often asks questions where the requirements are unclear. This is useful. It forces missing details to be addressed early.
Reviewing and adjusting the plan reduces rework later. It also makes the agent’s behaviour more predictable, since it is working from an agreed approach rather than making assumptions.
Multiple AGENTS.md files
Now within a Drupal site we typically have two focuses, backend development and front end development. Thankfully we can have different AGENTS.md files to cover both aspects that define how that development should be done.
You can place multiple AGENTS.md files across your repository. The agent will use the one closest to the area it is working in. This allows you to define context specific rules without overloading a single file.
For example, I have an AGENTS.md file inside my custom theme directory. This file defines how theme development should be handled, including build steps and conventions. For example I have defined that every new component should be a single directory component and that all styles relating to a component should live within that component directory.
Now I didn't write this manually. I asked GitHub Copilot to create it based on the existing custom themes and the commands used to compile them. This gave me a starting point that reflected how the project already worked.
This approach keeps instructions focused:
- Root AGENTS.md handles global rules
- Feature or module level files handle specific behaviour
- The agent uses the nearest file, which reduces irrelevant context
This structure improved accuracy. The agent no longer needed to interpret unrelated parts of the system when working on a specific area and reduces the overall context window.
Working with the agent
First, always review the output. Do not assume correctness, AI agents tend to do anything to make a test pass, even if it's cutting it short and returning TRUE. Follow the reasoning and check that it aligns with the task.
Second, watch for drift. Agents sometimes move away from the original goal. When that happens, stop and redirect them with a clear instruction.
Third, be explicit when something is wrong. Do not just say it failed. Explain what should have happened and why, then ask it to update the AGENTS.md with this information.
Changing expectations
The biggest shift is how you think about AI in development. It should not be treated as a tool that always produces perfect output. It behaves more like a team member. It needs guidance, correction, and context.
When something is wrong:
- Correct it
- Explain the expected behaviour
- Ask it to document the improvement
This last step is important. The agent can update documentation and AGENTS.md, which means the same mistake is less likely to happen again.
Closing thoughts
After a month, reflecting back has been eye opening to the possibilities. Agentic development works best when you invest in structure and information. It comes back to the age old saying of Garbage In, Garbage Out.
You need:
- Clear tickets
- A well maintained AGENTS.md
- Focused AI documentation
Admittedly the setup is not perfect, but it improves over time and it's working really well. The more you guide it and capture those decisions in documentation, the better it performs. I know other people use skills for specific tasks, but I found this provides a well rounded output.
Here is my current AGENTS.md file for this project.
# AGENTS.md: AI Agent Guide for Drupal Development with DDEV
**AI Agent Instructions**: This guide provides comprehensive instructions for AI coding agents working on Drupal projects using DDEV. Follow these guidelines for consistent, high-quality contributions. Human contributors should use README.md instead.
## Project Overview
- **Project**: Project name — project description.
- **Core Technology**: Drupal 11.x (verify via `composer show drupal/core`)
- **Development Environment**: DDEV (Docker-based development environment)
- **DDEV Project Name**: `sitename` → site URL: `https://sitename.ddev.site`
- **Key Components**: Custom modules, themes, configuration management, Composer dependencies
- **Environment**: PHP 8.3, MariaDB 10.11, Nginx-FPM (all managed by DDEV)
- **Development Tools**: Composer, Drush 12+, Git, DDEV CLI
- **Important**: All DDEV commands should be run from project root. Use `ddev exec` for Drupal-specific commands.
- **Custom modules**: Located in `web/modules/custom/` — see that directory for the full list.
## Pre/Post Work
### Before Starting Work
Run deploy to ensure the database, configuration, and caches are up to date before making any changes:
```bash
ddev drush deploy
```
### After Completing Work
Export any configuration changes to `config/sync` so they are captured in version control:
```bash
ddev drush cex -y
```
Stage and commit the exported config files alongside any code changes.
## DDEV Quick Setup
### Prerequisites
```bash
# Install DDEV (macOS)
brew install ddev/ddev/ddev
# Or download from https://ddev.readthedocs.io/en/stable/users/installation/
# Verify installation
ddev --version
```
### Initialize DDEV Project
```bash
# Clone the repository
git clone <repository-url> my-drupal-project
cd my-drupal-project
# Initialize DDEV configuration
ddev config --project-type=drupal11 --docroot=web --php-version=8.3
# Start DDEV environment
ddev start
# Install Composer dependencies
ddev composer install
# Install Drupal
ddev exec drush site:install standard \
--db-url=mysql://db:db@db/db \
--account-name=admin \
--account-pass=admin \
--yes
# Enable development modules
ddev exec drush pm:enable devel kint webprofiler -y
# Clear caches
ddev exec drush cr
# Launch site in browser
ddev launch
```
### Essential DDEV Commands
```bash
# Environment management
ddev start # Start development environment
ddev stop # Stop environment
ddev restart # Restart environment
ddev delete # Delete environment (careful!)
# Database operations
ddev snapshot # Create database snapshot
ddev restore-snapshot # Restore database snapshot
ddev import-db # Import database from file
ddev export-db # Export database to file
# Development tools
ddev exec <command> # Execute command in container
ddev ssh # SSH into web container
ddev logs # View container logs
ddev describe # Show environment details
ddev launch # Open site in browser
```
### DDEV Configuration
Create `.ddev/config.yaml` for project-specific settings:
```yaml
# .ddev/config.yaml
name: sitename
type: drupal11
docroot: web
php_version: "8.3"
webserver_type: nginx-fpm
database:
type: mariadb
version: "10.11"
xdebug_enabled: false
additional_hostnames: []
additional_fqdns: []
composer_version: "2"
corepack_enable: true
```
## Code Style and Standards
Adhere to Drupal coding standards (PSR-12 with Drupal extensions). Use Coder and PHPCS for enforcement.
- **PHP**:
- Indentation: 2 spaces (no tabs)
- Line length: ≤ 80 characters
- Naming: CamelCase classes/methods, snake_case variables/functions
- Always use braces; prefer early returns
- Full PHPDoc blocks with `@param`, `@return`, `@throws`
- **YAML**: 2-space indentation, lowercase keys
- **Twig**: `{{ }}` for output, `{% %}` for logic; always escape with `|e`
- **Linting** (project config in `phpcs.xml` — do not pass `--standard` or
`--extensions` manually, the file already defines them):
```bash
# Check a specific module (scans web/modules/custom and web/themes/custom
# by default if no path given)
ddev exec vendor/bin/phpcs web/modules/custom/<module_name>
# Auto-fix what can be fixed automatically
ddev exec vendor/bin/phpcbf web/modules/custom/<module_name>
```
**Reject any code that fails Drupal Coder sniffs.**
## Drupal Development Patterns
### Services & Dependency Injection
- **Create services** in `modulename.services.yml` file for reusable logic
- **Use dependency injection** to inject services into controllers, forms, and plugins
- **Core services** like `@current_user`, `@entity_type.manager`, `@database` are available
- **Best practice**: Avoid static `\Drupal::` calls in favor of dependency injection
- **Service discovery**: Use `drush eval "print_r(\Drupal::getContainer()->getServiceIds());"` to see available services
- **Location**: Place service classes in `src/` directory with proper namespace
### Entity API & Queries
- **Entity loading**: Use `Entity::load($id)` for single entities or `entityTypeManager()->getStorage()` for multiple
- **Entity queries**: Use `\Drupal::entityQuery()` for database operations instead of raw SQL
- **Query conditions**: Chain multiple conditions with `->condition()`, `->sort()`, `->range()`
- **Entity creation**: Create entities with `Entity::create(['type' => 'bundle_name'])`
- **Field access**: Use entity field API instead of direct property access
- **Performance**: Use entity query cache tags and contexts for optimal caching
### Plugin System
- **Plugin types**: Blocks, field formatters, field widgets, menu links, and more
- **Plugin discovery**: Use annotation-based discovery in docblocks
- **Plugin configuration**: Define plugin ID, label, and other metadata in annotations
- **Plugin base classes**: Extend appropriate base classes (BlockBase, FormatterBase, etc.)
- **Plugin placement**: Place plugins in `src/Plugin/Type/` directory structure
- **Derivative plugins**: Use for creating multiple plugins from one definition
### Hooks
- **Hook implementation**: Implement hooks in `modulename.module` file
- **Hook naming**: Follow pattern `hook_modulename_action()` for custom hooks
- **Hook parameters**: Use type hints and proper parameter documentation
- **Core hooks**: Common hooks include `hook_form_alter()`, `hook_theme()`, `hook_menu_links_discovered_alter()`
- **Hook order**: Hooks fire in module weight order (lowest first)
- **Best practice**: Keep hook implementations focused and use services for complex logic
### Forms API
- **Form classes**: Extend `FormBase` for simple forms or `ConfigFormBase` for configuration forms
- **Form structure**: Use render array structure with `#type`, `#title`, `#description` properties
- **Form validation**: Implement `validateForm()` method for custom validation
- **Form submission**: Implement `submitForm()` method for processing form data
- **Form elements**: Use proper form element types (textfield, select, checkbox, etc.)
- **AJAX forms**: Add `#ajax` property to form elements for dynamic behavior
- **Form caching**: Forms are automatically cached with CSRF protection
### Routes & Controllers
- **Routing file**: Define routes in `modulename.routing.yml` with path, defaults, and requirements
- **Controllers**: Create controller classes extending `ControllerBase` in `src/Controller/`
- **Route parameters**: Use `{parameter}` placeholders in paths and inject into controller methods
- **Access control**: Implement `_permission`, `_role`, or custom access callbacks
- **Route naming**: Use `modulename.action` naming convention for clarity
- **Controller injection**: Use constructor injection for dependencies
- **Return values**: Return render arrays or Symfony Response objects
## Security & Performance Guidelines
### Security Requirements
- **Always sanitize user input**: Use `#plain_text` for untrusted content
- **CSRF protection**: Include `#token` for forms with side effects
- **Permissions**: Implement proper access checks and route requirements
- **SQL Injection**: Use Entity Query or proper parameter binding
- **XSS Prevention**: Always use `|e` filter in Twig, `#markup` for trusted HTML only
### Performance Best Practices
- **Render caching**: Always add `#cache` array to render arrays with appropriate `tags` and `contexts`
- **Cache tags**: Use entity-based tags like `['node:123']` or list-based tags like `['node_list']`
- **Cache contexts**: Apply user-specific contexts like `['user.roles']` for personalized content
- **Lazy loading**: Use `#lazy_builder` for expensive operations that can be loaded separately
- **Placeholder strategy**: Set `#create_placeholder: TRUE` for lazy builders to improve initial page load
- **Cache max-age**: Set appropriate `max-age` values based on content freshness requirements
- **Avoid premature optimization**: Profile first, then optimize based on actual bottlenecks
- **Database queries**: Use entity queries instead of raw SQL for better caching and security
- **Entity loading**: Load multiple entities at once when possible for better performance
### Caching Strategies
- **Render cache**: Cache complex markup with proper tags/contexts
- **Dynamic page cache**: Configure for anonymous users
- **Internal page cache**: Enable for authenticated users
- **Entity cache**: Leverage core entity caching
- **Redis/Memcache**: Configure for distributed caching
## DDEV Development Workflow
### Project Structure
- **Modules** → `web/modules/custom/<module_name>`
- **Themes** → `web/themes/custom/<theme_name>`
- **Configuration** → Export with `ddev exec drush config:export`
- **Profiles** → `web/profiles/custom/<profile_name>`
### Essential Development Commands
```bash
# Cache management (run inside DDEV)
ddev exec drush cr # Clear all caches
ddev exec drush cache:rebuild # Alternative cache clear
# Configuration management
ddev exec drush config:export # Export configuration
ddev exec drush config:import # Import configuration
# Database operations
ddev snapshot # Create snapshot before changes
ddev exec drush updatedb # Run database updates
```
### Debugging in DDEV
#### Core Debugging & Information Commands
| Command | Purpose | Why it's useful for debugging |
|----------------------------------|-------------------------------------------------------------------------|--------------------------------------------------------------------|
| `ddev exec drush status` | Shows Drupal root, site path, database connection, Drush version, etc. | Quickly verify that DDEV is pointing to the correct site and DB is connected. |
| `ddev exec drush core-status` | Same as above but more detailed in newer versions. | |
| `ddev exec drush watchdog:show` | Lists recent log messages (dblog entries). | Primary command to read the Drupal error/log messages without going to /admin/reports/dblog. Supports filters: `--severity=Error`, `--type=php`, etc. |
| `ddev exec drush watchdog:delete all` | Clears the watchdog log. | Useful when logs become huge and slow down watchdog operations. |
| `ddev exec drush sql:query "SELECT * FROM watchdog ORDER BY wid DESC LIMIT 50"` | Direct SQL access to logs when the database is very large. | Faster than watchdog:show on sites with millions of log entries. |
#### Cache Debugging
| Command | Purpose |
|----------------------------------|-------------------------------------------------------------------------|
| `ddev exec drush cache:rebuild` | Rebuilds all caches (equivalent to "drush cc all" in D7). |
| `ddev exec drush cr` | Alternative cache rebuild command. |
| `ddev exec drush cache:get <bin>:<cid>` | Retrieve a specific cache item (e.g., `ddev exec drush cache:get config:core.extension`). |
| `ddev exec drush cache:clear <bin>` | Clear only one cache bin (render, config, discovery, etc.). |
#### Configuration Debugging
| Command | Purpose |
|----------------------------------------------|-------------------------------------------------------------------------|
| `ddev exec drush config:get <name>` | Show a single configuration value (e.g., `ddev exec drush config:get system.site`). |
| `ddev exec drush config:set <name> <key> <value>` | Temporarily change a config value without using the UI. |
| `ddev exec drush config:export` | Export active config to sync directory. |
| `ddev exec drush config:import` | Import config – very useful to test if config issues cause errors. |
| `ddev exec drush config:delete <name>` | Remove a config object (helps when orphaned config causes fatal errors).|
#### Module/Theming Debugging
| Command | Purpose |
|----------------------------------------|-------------------------------------------------------------------------|
| `ddev exec drush pm:list --type=module --status=enabled` | List enabled modules. |
| `ddev exec drush pm:enable <module>` | Enable a module. |
| `ddev exec drush pm:uninstall <module>` | Fully uninstall a module (removes config and data). |
| `ddev exec drush pm:uninstall` without arguments → interactive mode is excellent for disabling suspected problematic modules quickly. |
| `ddev exec drush theme:debug` (Drupal 9.4+) | Lists all theme suggestions for a given route or render array. |
#### Database & Entity Debugging
| Command | Purpose |
|----------------------------------------------|-------------------------------------------------------------------------|
| `ddev exec drush sql:connect` | Outputs the CLI command to connect to the DB (useful for manual queries). |
| `ddev exec drush sql:query` | Run arbitrary SQL. |
| `ddev exec drush entity:info` | Show entity type definitions (useful when entity schema errors occur). |
| `ddev exec drush php` | Opens an interactive PHP shell with Drupal bootstrapped (like `ddev exec drush php:eval`). |
| `ddev exec drush php:eval "code"` | Execute arbitrary PHP code in Drupal context (great for quick debugging). Example: `ddev exec drush php:eval "dpm(\Drupal::state()->get('system.cron_last'));"` (with Devel) |
#### Development & Error Reproduction
| Command | Purpose |
|----------------------------------------|-------------------------------------------------------------------------|
| `ddev exec drush php:eval "var_dump(function_exists('my_problematic_function'));"` | Quick test if a function exists or what it returns. |
| `ddev exec drush state:edit` / `ddev exec drush state:get/set/delete` | Inspect or override Drupal state values (often used by broken modules).|
| `ddev exec drush variable:get/set/delete` (D7 only) | Legacy equivalent of state commands. |
| `ddev exec drush twig:debug` | Turn Twig debugging on/off and verify template suggestions. |
| `ddev exec drush eval` | Same as above (alias of php:eval). |
#### Performance & Query Debugging
| Command | Purpose |
|----------------------------------------|-------------------------------------------------------------------------|
| `ddev exec drush sql:query --db-prefix` | See queries with table prefixes expanded (helps reading raw SQL). |
| Enable Devel + `ddev exec drush kint` or `ddev exec drush dpm()` in code → instant output in terminal. |
#### DDEV-Specific Debugging
```bash
# Enable Xdebug debugging
# Add to .ddev/config.yaml:
# xdebug_enabled: true
# DDEV container debugging
ddev logs -f web # Follow web container logs
ddev logs -f db # Follow database container logs
ddev describe # Show environment details and status
# Access PHP error logs
ddev exec tail -f /var/log/apache2/error.log
# Debugging functions (use with devel module)
ddev exec php -r "kint(\Drupal::config('system.site')->get());"
# Database connection debugging
ddev exec drush sql:connect # Test database connection
ddev describe # Check environment status
```
### Performance Profiling in DDEV
```bash
# Performance analysis
ddev exec drush cr # Rebuild caches
ddev exec drush sql:query "EXPLAIN ANALYZE SELECT ..." # Query analysis
ddev exec drush site:status # System status check
# Use Webprofiler module for detailed profiling
# Access at https://sitename.ddev.site/admin/config/development/devel/webprofiler
```
### Version Control Workflow
- **Commit messages**: Format `[#123456] Brief descriptive title`
- **Branch from**: `develop` branch for features
- **Atomic commits**: One logical change per commit
- **Before pushing**: Run linting and tests
## Testing & Quality Assurance
### PHPUnit Testing Framework
Aim for ≥ 80% code coverage. Drupal provides multiple test types:
```bash
# Run all tests with coverage
ddev exec vendor/bin/phpunit -v --coverage-html coverage/
# Run specific test suites
ddev exec vendor/bin/phpunit --testsuite unit # Unit tests (fast)
ddev exec vendor/bin/phpunit --testsuite kernel # Kernel tests
ddev exec vendor/bin/phpunit --testsuite functional # Functional tests (slower)
ddev exec vendor/bin/phpunit --testsuite javascript # JavaScript tests
# Run specific tests
ddev exec vendor/bin/phpunit --filter MyModuleUnitTest
ddev exec vendor/bin/phpunit web/modules/custom/my_module/tests/src/Unit/
# Run with custom configuration
SIMPLETEST_DB=sqlite://localhost/tmp.sqlite ddev exec vendor/bin/phpunit
```
### Test Types and Examples
#### Unit Tests (fastest)
- **Purpose**: Test individual classes and methods in isolation
- **Base class**: Extend `UnitTestCase` from `Drupal\Tests\UnitTestCase`
- **Speed**: Fastest test type, no Drupal bootstrap required
- **Isolation**: Test one piece of functionality at a time
- **Dependencies**: Mock external dependencies and services
- **Location**: Place in `tests/src/Unit/` directory
- **Use cases**: Service logic calculations, utility functions, data transformations
- **Best practices**: Keep tests small, focused, and deterministic
#### Kernel Tests (with database)
- **Purpose**: Test Drupal interactions with minimal Drupal environment
- **Base class**: Extend `KernelTestBase` from `Drupal\KernelTests\KernelTestBase`
- **Environment**: Partial Drupal bootstrap with in-memory database
- **Modules**: Declare required modules in `$modules` static property
- **Database**: Uses SQLite in-memory database for speed
- **Location**: Place in `tests/src/Kernel/` directory
- **Use cases**: Entity CRUD operations, configuration validation, service registration
- **Setup**: Install modules and configuration in `setUp()` method
#### Functional Tests (with browser)
- **Purpose**: Test complete user interactions through browser simulation
- **Base class**: Extend `BrowserTestBase` from `Drupal\Tests\BrowserTestBase`
- **Environment**: Full Drupal bootstrap with real browser
- **Speed**: Slowest test type, full page loads required
- **Theme**: Set `$defaultTheme` property (usually 'stark' or 'claro')
- **Location**: Place in `tests/src/Functional/` directory
- **Use cases**: Form submissions, page access, user permissions, JavaScript interactions
- **Browser simulation**: Uses Goutte/ChromeDriver for browser automation
- **Assertions**: Use `$this->assertSession()` for web assertions
### Code Quality Tools in DDEV
```bash
# Static analysis (add to composer require)
ddev exec vendor/bin/phpstan analyse # PHPStan analysis
ddev exec vendor/bin/psalm # Psalm analysis
# Security scanning
ddev exec vendor/bin/drupal-check # Check for deprecated code
ddev exec composer audit # Check for security advisories
# Accessibility testing
ddev exec vendor/bin/phpunit --group accessibility # Accessibility tests
```
### JavaScript Testing
```bash
# Install JavaScript dependencies
ddev exec npm install
# Run JavaScript tests
ddev exec npm run test # Jest tests
ddev exec npm run test:a11y # Accessibility tests
```
### Before Submitting Code
**A task is not complete until all of the following steps pass with zero errors.**
Run them in order and fix any issues before committing.
```bash
# 1. Auto-fix style violations first (uses phpcs.xml — no extra flags needed)
ddev exec vendor/bin/phpcbf web/modules/custom/<module_name>
# 2. Check for remaining style violations — must produce zero errors AND zero
# warnings. phpcs.xml defines the standards (Drupal + DrupalPractice),
# extensions, and scanned paths; do NOT pass --standard or --extensions.
ddev exec vendor/bin/phpcs web/modules/custom/<module_name>
# 3. Static analysis — must produce "[OK] No errors".
# phpstan.neon defines level (1) and paths; no extra args needed.
# Targeting a specific module is fine for faster feedback.
ddev exec vendor/bin/phpstan analyse web/modules/custom/<module_name>
# 4. Automated tests — must all pass.
# Tests must be run from inside the web/ directory with --no-configuration
# and the Drupal bootstrap. The @group annotation on the test class drives
# the --group filter. Do NOT use ddev exec vendor/bin/phpunit directly.
ddev exec bash -c "cd web && php -d memory_limit=-1 \
/var/www/html/vendor/bin/phpunit \
--no-configuration \
--bootstrap core/tests/bootstrap.php \
--group <module_name> \
--testdox \
modules/custom/<module_name>/tests/"
# 5. Cache rebuild and database updates
ddev exec drush cr # Clear caches
ddev exec drush updatedb # Run updates
```
**Do not consider any task finished if:**
- `phpcs` reports any errors or warnings
- `phpstan` reports any errors
- Any automated tests fail
- The site has unrun database updates after the change
## DDEV-Specific Troubleshooting
### Common DDEV Issues
```bash
# DDEV won't start
ddev poweroff && ddev start
# Port conflicts
# Edit .ddev/config.yaml to change ports
router_http_port: "8080"
router_https_port: "8443"
# Memory issues
# Increase PHP memory in .ddev/php/php.ini
memory_limit = 512M
# Composer memory issues
ddev exec php -d memory_limit=-1 /usr/local/bin/composer install
# Database connection issues
ddev describe # Check environment status
ddev exec drush sql:connect # Test database connection
```
### Performance Issues in DDEV
```bash
# Identify slow queries
ddev exec drush sql:query "SELECT * FROM watchdog WHERE type = 'php' ORDER BY wid DESC LIMIT 10"
# Check cache settings
ddev exec drush config:get system.performance
# Enable performance modules
ddev exec drush pm:enable memcache redis -y
```
### Module/Theme Development Issues in DDEV
```bash
ddev exec drush cr
# Service not found
ddev exec drush config:get core.extension
# Twig template not loading
ddev exec drush cr
# Cron issues
ddev exec drush cron
ddev exec drush watchdog:show --type=cron
```
### Testing Issues in DDEV
```bash
# PHPUnit configuration problems
# Ensure phpunit.xml.dist exists and is configured
cp web/core/phpunit.xml.dist phpunit.xml
# Database setup for testing
# Edit phpunit.xml for SIMPLETEST_DB and SIMPLETEST_BASE_URL
SIMPLETEST_DB=mysql://db:db@db/db
SIMPLETEST_BASE_URL=https://sitename.ddev.site
# Browser tests failing
# Install Selenium or ChromeDriver
# Ensure test environment variables are set
```
## Advanced Development Patterns
### Batch API for Long Operations
- **Purpose**: Process large datasets without PHP timeout issues
- **Use cases**: Data migration, bulk updates, file processing, API calls
- **Batch structure**: Create associative array with title, operations, and finished callback
- **Operations**: Array of callable methods and their arguments
- **Progress tracking**: Automatically shows progress bar to users
- **Error handling**: Implement proper exception handling in batch operations
- **User experience**: Provides real-time feedback during long operations
- **Memory management**: Processes data in chunks to prevent memory exhaustion
### Queue API for Background Processing
- **Purpose**: Process tasks in the background without blocking user interaction
- **Queue creation**: Use `\Drupal::queue('queue_name')` to get queue instance
- **Item addition**: Use `createItem()` to add tasks to the queue
- **Processing**: Claim items with `claimItem()` and delete with `deleteItem()`
- **Cron integration**: Process queue items during cron runs for regular background tasks
- **Reliability**: Failed items can be released back to the queue
- **Worker plugins**: Create QueueWorker plugins for structured queue processing
- **Logging**: Implement proper logging for queue processing monitoring
- **Performance**: Process multiple items per cron run for efficiency
### AJAX Forms
- **Trigger elements**: Add `#ajax` property to form elements (select, checkbox, button)
- **Callback method**: Reference callback method using `::methodName` syntax
- **Wrapper element**: Specify target element ID for AJAX response replacement
- **Response format**: Return form element or render array from callback
- **Event types**: Use 'change', 'click', 'blur' events as needed
- **Progress indicator**: Automatically shows loading indicator during AJAX requests
- **Error handling**: Implement try-catch blocks in AJAX callbacks
- **Form state**: Use `$form_state->getTriggeringElement()` to identify trigger
- **Multiple triggers**: Can have multiple AJAX elements in same form
- **Dynamic forms**: Update form options, show/hide fields based on user input
## Additional Resources
### Project-Specific Documentation
AI agents should consult the `docs/ai/` directory for detailed project context:
- **[docs/ai/README.md](docs/ai/README.md)** — Index and usage guide
- **[docs/ai/overview.md](docs/ai/overview.md)** — Project purpose, features and repository structure
- **[docs/ai/architecture.md](docs/ai/architecture.md)** — Custom modules, services, theme structure and testing patterns
- **[docs/ai/content-model.md](docs/ai/content-model.md)** — Content types, taxonomies, fields, paragraphs, media and user roles
- **[docs/ai/environment.md](docs/ai/environment.md)** — DDEV local setup, config management, debugging and quality gates
- **[docs/ai/deployment.md](docs/ai/deployment.md)** — Platform.sh / Upsun hosting, build/deploy hooks, release process
- **[docs/ai/integrations.md](docs/ai/integrations.md)** — CARE NG SOAP, SAML SSO, Search API, Webform and all other integrations
- **[docs/ai/testing.md](docs/ai/testing.md)** — Test patterns, shared infrastructure (RcpchBrowserTestBase, UsesConfigSyncTrait), content_access gotchas, quality gates
- **[docs/ai/changes.md](docs/ai/changes.md)** — Changelog; add an entry after completing significant work
### DDEV Documentation
- **DDEV Official Docs**: https://ddev.readthedocs.io
- **DDEV Quick Start**: https://ddev.readthedocs.io/en/stable/users/quickstart/
- **DDEV Drupal Guide**: https://ddev.readthedocs.io/en/stable/users/topics/drupal/
### Drupal Documentation
- **Drupal API**: https://api.drupal.org
- **Developer Guide**: https://www.drupal.org/docs/develop
- **Coding Standards**: https://www.drupal.org/docs/develop/standards
- **Security Best Practices**: https://www.drupal.org/docs/develop/security
### Community Resources
- **DrupalAtYourFingertips**: https://www.drupalatyourfingertips.com
- **Drupal Answers**: https://drupal.stackexchange.com
- **Drupal.org**: https://www.drupal.org
- **Drupal Slack**: https://drupal.slack.com
- **Hashbangcode**: https://www.hashbangcode.com