Skip to content

Development

The repository is a DDEV project built with ddev-drupal-contrib.

Documentation

The documentation uses the shared Drupal Pages theme, pinned to a revision in docs/requirements.txt. Its package supplies the stylesheet, fonts, JavaScript, and templates; project logos and content live here.

python3 -m venv /tmp/site-agent-docs-venv
/tmp/site-agent-docs-venv/bin/python -m pip install -r docs/requirements.txt
/tmp/site-agent-docs-venv/bin/mkdocs serve

Update the revision in both parts of the archive URL to upgrade the theme. Changing that file triggers the existing Pages job. CI installs the package in pages.before_script, then uses Drupal's usual Pages build and publishing rules. Assets are copied into each built site and served locally.

Local Drupal site

ddev start
ddev poser
ddev symlink-project
ddev exec "cd web/core && yarn install"
ddev drush site:install standard -y

Run ddev symlink-project again after adding a top-level file or directory.

Content Deployment comes from its git repository, through a vcs entry in composer.json's repositories, until it has a release.

Real providers

The Anthropic, Gemini, OpenRouter and Cloudflare AI Gateway provider modules are dev dependencies, for manual runs only. Put the keys in the gitignored .ddev/.env and run ddev restart:

Variable Used by
ANTHROPIC_API_KEY Anthropic, through a Key entity with the Environment key provider
GEMINI_API_KEY Gemini Provider, the same way
OPENROUTER_API_KEY OpenRouter, the same way
CF_ACCOUNT_ID, CF_API_KEY, CF_GATEWAY Cloudflare AI Gateway Provider, through settings.php

The Cloudflare provider reads its gateway from a cloudflare_gateway entity and its account and token from a cloudflare_credentials set resolved from settings.php. On the dev site those are site_agent and ai_gateway, with:

$settings['cloudflare']['credentials']['ai_gateway'] = [
  'account_id' => (string) getenv('CF_ACCOUNT_ID'),
  'token' => (string) getenv('CF_API_KEY'),
];
$config['cloudflare_ai.gateway.site_agent']['gateway_slug'] = (string) getenv('CF_GATEWAY');

Checks

Check Command
PHPUnit ddev phpunit
JavaScript stream regressions node --test tests/js/chat-stream.test.cjs
PHP_CodeSniffer (Drupal, DrupalPractice) ddev phpcs, ddev phpcbf to fix
PHPStan ddev phpstan
CSpell, as the GitLab CI job runs it ddev cspell
ESLint ddev eslint
Stylelint ddev stylelint

FunctionalJavascript tests use the Selenium container from ddev-selenium-standalone-chrome.

Locked-config site

tests/e2e/locked-config.sh builds "live", a second site in the DDEV project at live.site-agent.ddev.site, from a copy of the dev site, with $settings['config_readonly'] = TRUE;, and checks that config-write tools are offered on dev and absent on live. It then runs a scripted structure session, tests/e2e/structure-session.php, which adds a field to a content type and places it: on dev, config:export must write exactly the field storage, the field and the two displays; on live, every call must be refused. Last, on live, a scripted Editor session, tests/e2e/editor-session.php, has an editor change a page with Content Deployment's tools: the edit must run without a prompt and leave a draft, and publishing it as the card's Publish does must make it live. web/ isn't in the repository, so the script builds the site again on every run. Address the live site with ddev drush -l live.site-agent.ddev.site.

Core versions

  • Floor: ddev core-version '~11.3.0', then ddev poser --prefer-lowest --prefer-stable.
  • Current: ddev core-version ^11.
  • Next major: CI's next-major jobs run Drupal 12 on every push. Their PHPUnit and PHPStan jobs are allowed to fail; their Composer job is not. _LENIENT_ALLOW_LIST in .gitlab-ci.yml names the dependencies that don't declare Drupal 12 yet; drop each one when its release does. .deprecation-ignore.txt lists the dependencies' deprecations the Drupal 12 PHPUnit job ignores; drop each line when its project fixes it. The Composer job first removes the Cloudflare AI Gateway Provider, which is only used for manual runs: its cloudflare_api dependency requires guzzlehttp/psr7 ^2, and Drupal 12 requires ^3. It also requires Pathauto's 1.x-dev, since 8.x-1.15 doesn't declare Drupal 12 and its list builder fatals on core 12; remove that line from the job once a Pathauto release has the fix.

require-dev constrains two packages the module doesn't use itself, so that --prefer-lowest skips releases that can't run: composer/installers 1.0.0, allowed by the CI template's ^1 || ^2, doesn't support the drupal-core package type, and grasmash/yaml-cli 3.2.0, allowed by every Drush 13 release, is incompatible with Symfony Console 7. Drupal 12 needs Drush 14, which has no stable release yet and takes yaml-cli 4, so both constraints allow the next major too.

Composer refuses releases with security advisories, so the floor runs the lowest 11.3 release without one: 11.3.17 on 2026-10-01.