Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The bundled assets are for the iqual internal developer platform's Drupal integr
* Workflows for Drupal automation using **GitHub Actions**
* Integration for deployment to **Upsun** (formerly Platform.sh)
* `Makefile` **commands** for project and app tasks
* **AI integration** for coding agents

## Quick Start

Expand Down Expand Up @@ -118,13 +119,16 @@ assets/replace/
│ │ └── upgrader.sh
│ └── workflows
│ ├── config-pull.yml.twig
│ ├── copilot-setup-steps.yml.twig
│ ├── phpcs.yml.twig
│ ├── phpunit-functional-testing.yml.twig
│ ├── phpunit-unit-testing.yml.twig
│ ├── testing.yml.twig
│ ├── update.yml.twig
│ ├── upgrade.yml.twig
│ └── visual-regression-testing.yml.twig
├── AGENTS.md.twig
├── CLAUDE.md.twig
├── Makefile
├── README.md.twig
├── solr
Expand Down Expand Up @@ -158,6 +162,8 @@ Assets that will be merged into existing destination files or added if inexisten

```
assets/merge/
├── .claude
│ └── settings.json.twig
├── .dockerignore
├── .env.twig
├── .env.visreg.twig
Expand Down Expand Up @@ -185,6 +191,7 @@ assets/merge/
* [Service Deployment](./docs/deployment.md)
* [App Installation](./docs/installation.md)
* [Automation (CI/CD)](./docs/automation.md)
* [AI Integration](./docs/ai.md)
* DDEV
* [DDEV Usage](https://docs.ddev.com/en/stable/users/usage/)
* [DDEV Configuration](https://docs.ddev.com/en/stable/users/configuration/config/)
Expand Down
15 changes: 15 additions & 0 deletions assets/merge/.claude/settings.json.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% if ai.claude %}
{
"extraKnownMarketplaces": {
"iqual": {
"source": {
"source": "github",
"repo": "iqual-ch/claude-plugins"
}
}
},
"enabledPlugins": {
"iqual-developer@iqual": true
}
}
{% endif %}
3 changes: 3 additions & 0 deletions assets/merge/.gitignore.twig
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,6 @@ maintenance_*.html
playwright-report/
playwright-snapshots/
playwright-tmp/

# Ignore local AI agent settings
.claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% if workflows.update or workflows.vrt %}
{% if workflows.update or workflows.vrt or workflows.phpunit or ai.copilot %}
{%- verbatim -%}
name: Install Drupal locally
description: Checkout, configure, deploy and install a local drupal project
Expand Down
72 changes: 72 additions & 0 deletions assets/replace/.github/workflows/copilot-setup-steps.yml.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{% if ai.copilot %}
name: Copilot setup steps

{% if deployment == "platform.sh" %}
{% verbatim -%}
env:
DDEV_VERSION: ${{ vars.DDEV_VERSION || 'latest' }}

on:
workflow_dispatch:

jobs:
# Must be named exactly "copilot-setup-steps" to be picked up by Copilot.
copilot-setup-steps:
runs-on: {% endverbatim %}{{ workflows.runner | default('ubuntu-latest') }}{% verbatim %}

timeout-minutes: 45

permissions:
contents: read

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install agent skills from the iqual-developer plugin
uses: iqual-ch/claude-plugins/.github/actions/install-skills@main

- name: Install Drupal locally
uses: ./.github/actions/install-local
with:
ssh_key: ${{ secrets.SSH_KEY }}

# The coding agent gets a fully installed local site but must not be able
# to reach remote environments: remove the SSH key before handing over.
- name: Remove SSH access to remote environments
shell: bash
run: |
rm -f .ddev/homeadditions/.ssh/id_rsa
ddev exec 'rm -f ~/.ssh/id_rsa' || true
ddev restart

- name: Verify remote environment access is gone
shell: bash
run: |
if timeout 30 ddev drush @spot status 2>/dev/null; then
echo "::error::drush can still reach @spot over SSH after key removal."
exit 1
fi
{% endverbatim -%}
{% else %}
on:
workflow_dispatch:

jobs:
# Must be named exactly "copilot-setup-steps" to be picked up by Copilot.
copilot-setup-steps:
runs-on: {{ workflows.runner | default('ubuntu-latest') }}

timeout-minutes: 15

permissions:
contents: read

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install agent skills from the iqual-developer plugin
uses: iqual-ch/claude-plugins/.github/actions/install-skills@main
{% endif %}
{% endif %}
75 changes: 75 additions & 0 deletions assets/replace/AGENTS.md.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{% if ai.agents %}
# AGENTS.md

{{ title|default(name) }} — a Drupal website built on the iqual Drupal Platform stack.

> This file is generated by [`iqual/drupal-platform`](https://github.com/iqual-ch/drupal-platform) and will be overwritten on updates — do not edit it directly. Add project-specific knowledge to `docs/` and project-specific skills to `.agents/skills/` instead (both are picked up automatically).

## Facts

- **Stack:** Drupal, PHP {{ runtime.php_version }}, MariaDB {{ runtime.db_version }}, nginx-fpm — via [DDEV](https://ddev.readthedocs.io/) locally{% if deployment == "platform.sh" %}, [Upsun](https://docs.upsun.com/) (PaaS) in remote environments{% endif %}.
{% if runtime.solr_version %}
- **Search:** Apache Solr {{ runtime.solr_version }} via Search API.
{% endif %}
- **Docroot:** `app/{{ locations['web-root'] }}`. Composer root: `app` (`app/composer.json` — `ddev composer` operates there automatically). Custom code: `app/{{ locations['web-root'] }}/modules/custom/`, `app/{{ locations['web-root'] }}/themes/custom/`.
- **URLs:** local `https://{{ name }}.ddev.site/`{% if deployment == "platform.sh" %}, live {{ url }}/{% endif %}.
- **Runtime is already up** in this environment — don't run `make install` unless explicitly asked to (re)provision. Use `ddev drush status` to check site state first.
- **Workflow:** GitHub Flow — branch → develop → PR → review → merge{% if deployment == "platform.sh" %} → deploy. Only `main` deploys to production{% endif %}. Branch format: `issue/XXX-YYY_description`, commit format: `XXX-YYY: Description`, referencing a Jira issue.
- **Never push to `main`, and never merge a PR yourself.** Merging always requires an explicit, human-approved review — even if all checks pass.
- **Prefer review over publishing:** when a user is present, offer to push or open a PR rather than doing it unasked — committing and pushing to your own working branch is always fine when that's how you deliver your work.
- **Dependencies:** Composer (PHP) + npm/Bun (JS), locked in lock files. Config changes go through Drupal's config export/import (`drush cex`/`drush cim`), never hand-edited in the target environment.
- **CI (GitHub Actions), on every PR:** PHPCS linting{% if workflows.phpunit %}, PHPUnit (unit/kernel/functional/browser){% endif %}{% if workflows.vrt %}, visual regression testing{% endif %}. Must pass before merge.

## Commands

Run `make help` for the full list; `ddev drush <command>` for anything Drupal-specific once the environment is up.

| Task | Command |
|---|---|
| Check site status | `ddev drush status` |
| Start local runtime | `make runtime` |
| Build & deploy Drupal locally | `make drupal` |
| Lint (PHPCS + PHPStan, custom code only) | `make lint` |
| Auto-fix lint issues | `make beauty` |
| Run tests | `make test`{% if workflows.phpunit %} (see the `drupal-testing` skill for granular targets){% endif %} |
{% if workflows.phpunit %}
| List available Drupal tests | `make drupal-test-list` |
{% endif %}
{% if deployment == "platform.sh" %}
| Pull config from remote | `make config-pull` |
{% endif %}
| Open project in browser (for user) | `make launch` |
| Open project logged in as admin (for user) | `make login` |

## Skills

Reusable Drupal engineering knowledge is distributed as Agent Skills through the `iqual-developer` Claude plugin in [iqual-ch/claude-plugins](https://github.com/iqual-ch/claude-plugins).

Don't guess at Drupal-specific implementation details — check the installed skills and read the relevant one first. Core skills include:

| Skill | Use for |
|---|---|
| `drupal-expert` | Modules, themes, hooks, services, plugins, config schema, caching, security, Drush codegen, version compatibility. |
| `drupal-testing` | Writing/placing/running PHPUnit + DTT tests, deciding what belongs in this project vs. a shared test suite vs. upstream OSS. |
| `drupal-contrib` | Changing a Composer dependency (contrib module/theme, iqual package): source checkouts, GitHub vs. Drupal.org issue-fork workflows, safe Composer handling. |

The plugin is the canonical source — it may ship more (or newer) skills than listed here. Project-specific skills, if any, live in `.agents/skills/` in this repository.

## Where code belongs

Code specific to this project lives in this repository (custom modules/themes, config, project-level tests). If a change is actually about fixing or extending a dependency's own behavior — a contributed package or an internal/shared package — don't work around it here: contribute the fix upstream and consume the update instead of patching it locally. Decision rules live in the `drupal-expert` skill; the hands-on workflow for changing a dependency lives in the `drupal-contrib` skill — read it **before** editing any composer-installed code.
{% if (locations['project-root'] ~ '/docs') is existing_file %}

## Project documentation

Project-specific documentation lives in [`docs/`](./docs). Read it before making architectural decisions — it takes precedence over the generic guidance in this file.
{% endif %}

## Definition of done

- Implementation matches the described scope.
- `make lint` passes{% if workflows.phpunit %}; relevant tests exist and pass (see the `drupal-testing` skill){% endif %}.
- PR is linked to a Jira issue, reviewed, and approved before merging.
- Config exported (`drush cex`) if site configuration changed.
- Documentation updated if behavior or setup changed.
{% endif %}
4 changes: 4 additions & 0 deletions assets/replace/CLAUDE.md.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% if ai.claude %}
@AGENTS.md

{% endif %}
5 changes: 5 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
"vrt": true,
"phpunit": false
},
"ai": {
"agents": true,
"claude": true,
"copilot": true
},
"deployment": "platform.sh",
"platformsh_config": {
"region": "ch-1",
Expand Down
49 changes: 49 additions & 0 deletions docs/ai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# AI Integration

The Drupal Platform scaffolds an AI/agent integration into every project so that coding agents (Claude Code, GitHub Copilot coding agent, and other AGENTS.md-compatible tools) get project context, shared skills and a working environment out of the box.

The integration consists of three managed assets, each of which can be disabled individually with a [package variable](./configuration.md#drupal-platform-package-variables):

| Asset | Variable | Purpose |
|---|---|---|
| `AGENTS.md` | `ai.agents` | Generated agent instruction file with project facts, commands and conventions. |
| `.claude/settings.json` | `ai.claude` | Recommends the `iqual-developer` Claude Code plugin (marketplace: [iqual-ch/claude-plugins](https://github.com/iqual-ch/claude-plugins)). |
| `CLAUDE.md` | `ai.claude` | Claude Code entry point that imports `AGENTS.md`. |
| `.github/workflows/copilot-setup-steps.yml` | `ai.copilot` | Prepares the GitHub Copilot coding agent's cloud environment. |

## AGENTS.md

`AGENTS.md` is a **replaced** (fully managed) asset: it is templated from the package variables (PHP/DB versions, deployment type, enabled workflows, URLs, …) and overwritten on every scaffold run. Do not edit it in the project.

Project-specific knowledge belongs next to it instead:

* **`docs/`** — project-specific documentation. If the directory exists, `AGENTS.md` automatically points agents to it (re-run `composer project:scaffold` after creating it).
* **`.agents/skills/`** — project-specific [Agent Skills](https://agentskills.io) committed to the repository.

## Shared skills

Reusable Drupal engineering knowledge (skills such as `drupal-expert`, `drupal-testing`, `drupal-contrib`) is **not** committed to projects. It is maintained centrally in [iqual-ch/claude-plugins](https://github.com/iqual-ch/claude-plugins) and distributed per agent:

* **Claude Code**: the scaffolded `.claude/settings.json` registers the `iqual` marketplace and enables the `iqual-developer` plugin — developers are prompted once to install it when they open the repository.
* **GitHub Copilot coding agent**: `copilot-setup-steps.yml` installs the plugin's skills into `.agents/skills/` (excluded from git via `.git/info/exclude`) before the agent starts.
* **Other agents/CI**: use the reusable [`install-skills` action](https://github.com/iqual-ch/claude-plugins#readme).

This keeps skills versioned and updated in one place, instead of drifting copies in every project repository.

## Copilot coding agent environment

The Copilot cloud agent runs `.github/workflows/copilot-setup-steps.yml` before it starts working (the job must be named exactly `copilot-setup-steps`). The scaffolded workflow:

1. Installs the shared agent skills.
2. Installs the project locally (DDEV runtime + Drupal, using the `SSH_KEY` secret to sync from the SPOT) — `platform.sh` deployments only.
3. **Removes the SSH key** and verifies the remote environment is unreachable, so the agent has a full local site and toolchain but no access to remote environments.

For `local-only` deployments only the skills are installed.

The workflow respects the `workflows.runner` variable for custom runner labels and can be disabled entirely with `ai.copilot: false`.

## Claude Code settings

`.claude/settings.json` is a **merged** asset: the plugin recommendation keys are merged into the file, while any additional project-specific settings (e.g. `permissions`) are preserved. Local per-developer overrides belong in `.claude/settings.local.json` (git-ignored by the scaffolded `.gitignore`).

`CLAUDE.md` is a **replaced** (fully managed) asset that imports `AGENTS.md` (`@AGENTS.md`), so Claude Code picks up the same instructions as other agents — do not add project-specific content to it.
14 changes: 13 additions & 1 deletion docs/automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ There are multiple GitHub Action workflows for running common automation tasks.
* [PHPUnit: Unit Testing](#phpunit-unit-testing)
* [PHPUnit: Functional Testing](#phpunit-functional-testing)
* [Visual Regression Testing: Comparing reference website to local test deployment](#visual-regression-testing)
* [Copilot: Set up the GitHub Copilot coding agent environment](#copilot-setup-steps)

## Update Drupal Project

Expand Down Expand Up @@ -286,4 +287,15 @@ This workflow requires a full build of Drupal.

This workflow will install the project in a GitHub Actions runner environment and run a visual regression test using the node package [`iqual/playwright-vrt`](https://github.com/iqual-ch/playwright-vrt). The workflow will look for the website's sitemap, and fall back to crawling to gather relevant links. If the test fails (when there are visual differences between the local and live/reference version), then it will upload a Playwright report as a workflow artifact.

The VRT configuration can be customized in the `playwright-vrt.config.json` file in the root of the project.
The VRT configuration can be customized in the `playwright-vrt.config.json` file in the root of the project.
## Copilot Setup Steps

* Workflow: `copilot-setup-steps.yml`
* Config variable: `ai.copilot`
* Runs on:
* Manual dispatch
* Automatically by the GitHub Copilot coding agent before it starts working

This workflow prepares the ephemeral GitHub Actions environment for the [GitHub Copilot coding agent](https://docs.github.com/en/copilot/using-github-copilot/coding-agent). It installs the shared agent skills from [`iqual-ch/claude-plugins`](https://github.com/iqual-ch/claude-plugins) and — for `platform.sh` deployments — a full local Drupal installation. Afterwards the SSH key is removed and verified to be gone, so the agent can build, lint and test the project but cannot reach remote environments.

See [AI Integration](./ai.md) for the full concept.
4 changes: 4 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ Assets and configuration managed by the Drupal Platform have to be customized us
* `workflows.phpunit`: Enable/Add the Drupal testing workflow
* `workflows.vrt`: Enable/Add the visual regression testing workflow
* `workflows.runner`: Custom GitHub Actions runner label for DDEV-based workflows (e.g. `ubuntu-latest-m`, default: `ubuntu-latest`)
* AI integration settings (see [AI Integration](./ai.md))
* `ai.agents`: Enable/Add the generated `AGENTS.md` agent instruction file (default: `true`)
* `ai.claude`: Enable/Add the Claude Code integration — plugin recommendation in `.claude/settings.json` and `CLAUDE.md` importing `AGENTS.md` (default: `true`)
* `ai.copilot`: Enable/Add the GitHub Copilot coding agent setup workflow (default: `true`)
* `deployment`: Deployment integration type, see [available remote deployment options](./deployment.md#remote-deployment)
* Platform.sh config
* `platformsh_config.region`: Deployment region (e.g. `ch-1`)
Expand Down