Skip to content

Commit de46c40

Browse files
andypostclaude
andcommitted
Add project docs, agent harness, and worktree helper
Document the GitLab CI review-app mechanism and the overall architecture, add an inheritable Claude Code harness (.claude/) for agent-driven maintenance, and seed the Drupal 11/12 transitional upgrade runbook. - docs/: review-apps, ci-pipeline, architecture, local-development, upgrading (D10->11->12), observability, parallel-environments, delivery-and-ops, backlog triage, index. - .claude/: settings allowlist, 5 skills (drupal-upgrade, dep-bump, review-app-triage, backlog-grooming, observability-up), 3 subagents, 2 workflows (drupal-upgrade, newrelic-bump). - scripts/makefile/worktree.mk: `make worktree-name` for parallel per-branch local stacks. - CONTRIBUTING.md; expand CLAUDE.md and README with doc/harness links. Verified facts: druxxy (latest v1.5.1) requires core ^10.6 and is the D11 gate; skilldlabs/php:84-* images exist for D12; Nginx Unit is already the default (#467). No runtime behavior changed; docs + scaffolding only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pv3rn53U7YYUuYFutvFCzD
1 parent 1f2316e commit de46c40

26 files changed

Lines changed: 1584 additions & 10 deletions

.claude/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Agent harness
2+
3+
This directory makes the template **agent-manageable** with Claude Code. It ships skills, subagents,
4+
workflows, and a permission allowlist for the recurring maintenance jobs this project needs.
5+
6+
Because the template is consumed as a **rolling master** (forks/seeds track branches, not releases),
7+
keeping the harness *in the repo* means projects seeded from it **inherit agent-driven management for
8+
free**. Existing projects copy `.claude/` once (see
9+
[CONTRIBUTING.md → Agent harness](../CONTRIBUTING.md#agent-harness)).
10+
11+
## Contents
12+
13+
| Path | What |
14+
| --- | --- |
15+
| [`settings.json`](settings.json) | Allowlist for the read-mostly `make` / `docker` / `git` / `gh` / `composer validate` calls these jobs use, to cut permission prompts. **`make:*` is allowed intentionally**`make` is this project's primary interface (it includes `make clean`, so review destructive runs). |
16+
| `skills/` | User-invokable runbooks (`/<name>`) wrapping existing `make` targets |
17+
| `agents/` | Subagent definitions used by the skills/workflows |
18+
| `workflows/` | Deterministic multi-step Workflow scripts for the heavy jobs |
19+
20+
## Skills (`/<name>`)
21+
22+
- [`review-app-triage`](skills/review-app-triage/SKILL.md) — read pipeline + Behat/Lighthouse/watchdog
23+
output and localize failures.
24+
- [`drupal-upgrade`](skills/drupal-upgrade/SKILL.md) — execute the
25+
[upgrade runbook](../docs/upgrading.md) (D10 → 11 → 12).
26+
- [`dep-bump`](skills/dep-bump/SKILL.md) — bump a dependency; NewRelic agent is the first-class case.
27+
- [`backlog-grooming`](skills/backlog-grooming/SKILL.md) — triage open PRs/issues, regenerate
28+
[docs/backlog.md](../docs/backlog.md).
29+
- [`observability-up`](skills/observability-up/SKILL.md) — bring the logs/traces stack up and print the
30+
Grafana URL.
31+
32+
## Agents
33+
34+
- [`upgrade-validator`](agents/upgrade-validator.md) — runs `upgradestatusval` + `drupalrectorval`,
35+
reports blockers (read/much-analysis).
36+
- [`ci-log-analyzer`](agents/ci-log-analyzer.md) — read-only triage of CI / container logs.
37+
- [`dep-bumper`](agents/dep-bumper.md) — edits a single dependency pin and validates.
38+
39+
## Workflows
40+
41+
Run via the Workflow tool (or `/workflows`). Opt-in — they only run when invoked.
42+
43+
- [`drupal-upgrade.workflow.js`](workflows/drupal-upgrade.workflow.js) — bump → remove dropped modules
44+
→ rector → `upgrade_status` → iterate, in an isolated worktree.
45+
- [`newrelic-bump.workflow.js`](workflows/newrelic-bump.workflow.js) — bump the pinned NewRelic agent
46+
version and validate.
47+
48+
## Conventions
49+
50+
- Project context lives in this repo's [`CLAUDE.md`](../CLAUDE.md) and [`docs/`](../docs/). The
51+
parent-directory `CLAUDE.md` (a "Plasma" doc) is **unrelated** — don't rely on it here.
52+
- Skills wrap **existing `make` targets** rather than reimplementing logic; keep that contract so the
53+
harness stays correct as the Makefile evolves.
54+
- Heavy/parallel jobs run in **git worktrees** (see [parallel-environments.md](../docs/parallel-environments.md))
55+
so multiple branches can build at once without colliding.

.claude/agents/ci-log-analyzer.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
name: ci-log-analyzer
3+
description: Read-only triage of CI pipeline output and Docker/Drupal logs for a failing review app. Finds the first real error, the failing job/target, and the likely root cause. Use when a pipeline or local stack is red and you need the cause, not a fix.
4+
tools: Bash, Read, Grep, Glob
5+
---
6+
7+
You are a read-only log triager for the skilld-docker-container review-app pipeline. You find root
8+
causes; you do not edit code.
9+
10+
Sources you can read:
11+
- GitLab job logs (if given a URL/ID, use `gh`/`glab` or the provided text) and JUnit artifacts
12+
(`junit/*.xml`).
13+
- A running stack: `docker compose logs --tail=300 php`, `docker compose ps`,
14+
`make drush -- watchdog:show --severity=Error --count=100`.
15+
- The repo, to map a failing job to its `make` target via docs/ci-pipeline.md.
16+
17+
Method:
18+
1. Identify the failing **stage/job** and its underlying `make` target.
19+
2. Find the **first** error (not the cascade after it). For Behat, read the JUnit failure message and
20+
the matching screenshot name under `web/screenshots/`.
21+
3. Correlate with recent changes (`git log --oneline -15`, `git diff` against last green) to point at a
22+
suspect file/commit.
23+
4. Note environment-specific causes: SAPI mismatch (Unit vs FrankenPHP), DB engine, basic-auth on the
24+
review URL, missing CI variable (REVIEW_DOMAIN, runner tag, RA_BASIC_AUTH).
25+
26+
Report:
27+
- Failing job + `make` target.
28+
- The exact first error (quoted).
29+
- Most-likely root cause and the file/commit to look at.
30+
- A reproduction command (`make <target>` locally).
31+
32+
Do not propose code edits beyond naming the file/line; do not run destructive commands. If logs are
33+
insufficient, say what additional log/artifact is needed.

.claude/agents/dep-bumper.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
name: dep-bumper
3+
description: Mechanically bumps a single dependency pin (composer package, Docker image tag, or the NewRelic agent version) and validates it. Use for one-at-a-time, reviewable dependency updates.
4+
tools: Bash, Read, Edit, Grep, Glob
5+
---
6+
7+
You apply one dependency bump at a time for the skilld-docker-container template and validate it.
8+
9+
Given a target (package + version, image tag, or "latest NewRelic agent"):
10+
1. Locate the pin:
11+
- composer package → `composer.json` `require`/`require-dev`.
12+
- PHP image → `IMAGE_PHP` in `.env.default` (and the CI default in `.gitlab-ci.yml`).
13+
- NewRelic agent → the version string in `scripts/makefile/newrelic.sh`.
14+
2. For composer, check feasibility first: `composer why-not <pkg> <version>`.
15+
3. Make the **minimal** edit (one pin).
16+
4. Validate:
17+
- composer: `composer validate` then `composer update <pkg> --with-dependencies` (in the php
18+
container via `make` if a stack is up).
19+
- image/agent: confirm the tag/version exists, then `make provision reload` (image) or
20+
`make newrelic reload` (agent) and smoke-check.
21+
5. Report the diff, the validation output, and whether it's safe to commit.
22+
23+
Constraints:
24+
- Exactly one dependency per run; keep the diff small and reviewable.
25+
- Never add a committed patch file (`extra.patches` must reference upstream URLs only — `make patchval`
26+
enforces this).
27+
- If the bump pulls a Drupal major (D11/D12), stop and defer to the `drupal-upgrade` skill — don't do
28+
a core jump as a "dependency bump".
29+
- Don't bump unrelated packages to make resolution work; report the conflict instead.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
name: upgrade-validator
3+
description: Runs the Drupal upgrade validation gates (upgrade_status + rector + config schema) and reports blockers with file-level detail. Use during a Drupal 10→11→12 upgrade to check whether the current tree is ready.
4+
tools: Bash, Read, Grep, Glob
5+
---
6+
7+
You validate Drupal upgrade-readiness for the skilld-docker-container template. You do not change
8+
production code; you run the project's own gates and report precisely.
9+
10+
Run, in this order, and capture full output:
11+
1. `make upgradestatusval` — the authority. It enables `upgrade_status`, runs
12+
`drush upgrade_status:analyze --all --ignore-contrib --ignore-uninstalled`, and fails on any `FILE:`
13+
line. Extract each reported file + deprecation.
14+
2. `make drupalrectorval``rector` dry-run over `web/modules/custom` + `web/themes/custom` using
15+
`rector.php`. List each suggested change (these are mostly auto-fixable in custom code).
16+
3. `make cinsp` — config schema errors (run before anything that uninstalls modules).
17+
18+
Then report:
19+
- **Blockers**: deprecations `upgrade_status` flags in custom code, grouped by file, each with the
20+
deprecated API and the D11/D12 replacement.
21+
- **Auto-fixable**: what rector can rewrite (note: `vendor/bin/rector process` would apply them).
22+
- **Contrib/core gaps**: anything pointing at contrib or core constraints (feed back to the upgrade
23+
skill — e.g. a module that needs a D11 release, or the druxxy profile gate).
24+
- **Verdict**: READY / NOT READY, with the shortest path to green.
25+
26+
Constraints:
27+
- Don't edit `composer.json` or run `composer update` — that's the upgrade skill's job; you only
28+
validate the current state.
29+
- If a gate can't run because the stack isn't installed, say so and stop — don't guess.
30+
- Quote real output; never claim green without the target exiting 0.

.claude/settings.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
3+
"permissions": {
4+
"allow": [
5+
"Bash(make:*)",
6+
"Bash(docker compose:*)",
7+
"Bash(docker ps:*)",
8+
"Bash(docker logs:*)",
9+
"Bash(docker inspect:*)",
10+
"Bash(git status:*)",
11+
"Bash(git log:*)",
12+
"Bash(git diff:*)",
13+
"Bash(git branch:*)",
14+
"Bash(git worktree:*)",
15+
"Bash(gh pr list:*)",
16+
"Bash(gh pr view:*)",
17+
"Bash(gh pr diff:*)",
18+
"Bash(gh issue list:*)",
19+
"Bash(gh issue view:*)",
20+
"Bash(composer validate:*)",
21+
"Bash(composer show:*)",
22+
"Bash(composer why-not:*)"
23+
]
24+
}
25+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
name: backlog-grooming
3+
description: Triage the open GitHub PRs and issues — classify into adopt-now / rebase / fold-into-docs / close, dedupe, and regenerate docs/backlog.md. Use for "groom the backlog", "triage issues", "what should we do with the open PRs".
4+
---
5+
6+
# Backlog grooming
7+
8+
Keep [docs/backlog.md](../../../docs/backlog.md) current and the queue actionable. Read-only against
9+
GitHub except for editing `docs/backlog.md`.
10+
11+
## Steps
12+
13+
1. **Enumerate** (read-only):
14+
- `gh pr list --state open --limit 100`
15+
- `gh issue list --state open --limit 200`
16+
2. **Inspect** the substantive ones: `gh pr view <n> --comments`, `gh pr diff <n> --stat`,
17+
`gh issue view <n> --comments`. For each, note: what it does, files touched, staleness, and whether
18+
master already solved it.
19+
3. **Classify** into the buckets used in `docs/backlog.md`:
20+
- **adopt-now** — small, low-risk, still applies.
21+
- **rebase & finish** — valuable but stale/conflicting.
22+
- **fold into docs** — no code needed; the answer is documentation.
23+
- **close** — obsolete, superseded, or abandoned.
24+
4. **Dedupe** — link PRs to their issues (e.g. an "auto shm size" PR and its issue) and collapse.
25+
5. **Cross-check against the repo**: before recommending adopt, confirm the change isn't already in
26+
master (`git log`, current `composer.json`/`Makefile`). Several issues are already resolved (e.g.
27+
#467 Unit default, #341 shm size).
28+
6. **Regenerate** `docs/backlog.md` with the updated table and a suggested order. Keep the markdown
29+
table shape.
30+
31+
## Output
32+
- Updated `docs/backlog.md`.
33+
- A short summary: counts per bucket, and the top 3 recommended actions.
34+
35+
## Guardrails
36+
- Don't close/comment on GitHub from here — produce the recommendation; a human (or an explicit
37+
follow-up) acts on it.
38+
- Note any PR that conflicts with in-flight work (e.g. #403 k3s vs the worktree helper).

.claude/skills/dep-bump/SKILL.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
name: dep-bump
3+
description: Bump a dependency and validate it. First-class case is the recurring NewRelic PHP agent version bump; also handles composer packages and Docker image tags. Use for "bump NewRelic", "update dependency X", "upgrade the php image".
4+
---
5+
6+
# Dependency bump
7+
8+
Small, validated version bumps — the steady-state maintenance this template needs most (NewRelic
9+
agent bumps dominate its git history).
10+
11+
## NewRelic agent (first-class)
12+
13+
The pinned version lives in [`scripts/makefile/newrelic.sh`](../../../scripts/makefile/newrelic.sh).
14+
15+
1. Find the latest agent release (NewRelic PHP agent release notes / download index).
16+
2. Update the version string in `scripts/makefile/newrelic.sh` (and any matching version in
17+
`.env.default` / docker config if present).
18+
3. Validate: `composer validate`, and if a stack is up, `make newrelic reload` then confirm the agent
19+
loads (`make drush -- php-eval "var_dump(extension_loaded('newrelic'));"` or check
20+
`docker compose logs php`).
21+
4. Commit with the release tag in the message (matches the repo's existing
22+
`NR: <url>` convention — see `git log`).
23+
24+
For an unattended run use the [`newrelic-bump` workflow](../../workflows/newrelic-bump.workflow.js).
25+
26+
## Composer package
27+
28+
1. `composer why-not <pkg> <target-version>` to see blockers.
29+
2. Edit the constraint in `composer.json`; `composer update <pkg> --with-dependencies`.
30+
3. `composer validate` + `make tests` (or at least `make cinsp upgradestatusval`).
31+
4. If it's a Drupal contrib bump for D11, route through [`drupal-upgrade`](../drupal-upgrade/SKILL.md).
32+
33+
## Docker PHP image
34+
35+
`IMAGE_PHP` in `.env.default` (and the CI default in `.gitlab-ci.yml`). Confirm the tag exists
36+
(`skilldlabs/php` tags), bump, `make provision reload`, smoke-test the site.
37+
38+
## Guardrails
39+
- One dependency per change; keep the diff reviewable.
40+
- Never commit a local patch file (`test:patch` forbids it) — patches go to upstream URLs in
41+
`composer.json`'s `extra.patches`.
42+
- Delegate the mechanical edit+validate to the [`dep-bumper`](../../agents/dep-bumper.md) subagent when
43+
doing several.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
name: drupal-upgrade
3+
description: Move the template toward Drupal 11 (transitional ^10.3 || ^11) or stage Drupal 12. Bumps core constraints, removes modules dropped from core, updates rector, and iterates to a clean upgrade_status. Use for "upgrade to Drupal 11", "D11 compatibility", "prepare D12".
4+
---
5+
6+
# Drupal upgrade
7+
8+
Execute the runbook in [docs/upgrading.md](../../../docs/upgrading.md). This skill is the interactive
9+
entry point; for an unattended, isolated run use the
10+
[`drupal-upgrade` workflow](../../workflows/drupal-upgrade.workflow.js).
11+
12+
## Before you start — check the gate
13+
The default profile `skilldlabs/druxxy` gates D11. Verify first:
14+
15+
```sh
16+
composer show skilldlabs/druxxy --all # is there a release allowing drupal/core ^11?
17+
```
18+
19+
As of the last check, the newest druxxy (`v1.5.1`) only allows `^10.6`**no D11 yet**. If still
20+
blocked, either obtain a D11-capable druxxy major or temporarily set `PROFILE_NAME=standard` on a
21+
throwaway branch to validate the rest.
22+
23+
## Steps (transitional D11)
24+
25+
1. Create/checkout a throwaway branch (ideally an isolated worktree —
26+
[parallel-environments.md](../../../docs/parallel-environments.md)).
27+
2. `composer.json`: core scaffold/hardening → `^10.3.1 || ^11`; bump druxxy when available.
28+
3. Remove `drupal/ckeditor`, `drupal/color`, `drupal/seven` (dropped from D11 core). Switch the admin
29+
theme to **claro**, rely on core **CKEditor 5**. `git grep` for stragglers first.
30+
4. Bump `palantirnet/drupal-rector` (current `^0.20.3` is old → `^0.21`/`1.0.x`); add `Drupal11SetList`
31+
to `rector.php` **only if** the installed release ships it.
32+
5. Re-verify or drop the `drupal/default_content` patch in `composer.json`.
33+
6. `composer update --with-all-dependencies`, then run the gates and iterate:
34+
`make upgradestatusval` (authority) → `make drupalrectorval``make cinsp``make tests`.
35+
Delegate the run/parse loop to the [`upgrade-validator`](../../agents/upgrade-validator.md) subagent.
36+
7. Open an MR; the definition of done is a **green D11 review app**.
37+
38+
## D12 (staged, separate branch)
39+
Switch `IMAGE_PHP` to a PHP 8.4 image (`skilldlabs/php:84-unit` exists), widen constraints to include
40+
`^12`, add the D12 rector set if present, re-run the gates. Don't fold D12 into the D11 branch.
41+
42+
## Guardrails
43+
- Never bypass `upgrade_status`/rector to "make it green" — fix the deprecations.
44+
- Keep the change transitional (`|| ^11`) so downstream D10.3 projects keep resolving.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
name: observability-up
3+
description: Get logs and traces out of the running stack. Tier 1 is Docker logs (no extra services); Tier 2 brings up the opt-in OpenTelemetry trace stack (Tempo + Grafana) and prints the Grafana URL. Use for "show me the logs", "enable tracing", "bring up observability".
4+
---
5+
6+
# Observability up
7+
8+
Implements [docs/observability.md](../../../docs/observability.md).
9+
10+
## Tier 1 — logs (always available)
11+
12+
```sh
13+
docker compose logs -f php # access + PHP error log (stdout/stderr)
14+
make drush -- watchdog:tail # live Drupal log
15+
make watchdogval # fail on Emergency|Alert|Critical|Error
16+
```
17+
18+
Mail: open Mailpit at `mail-${MAIN_DOMAIN_NAME}`. This is the default answer to "get me the logs".
19+
20+
## Tier 2 — traces (opt-in OpenTelemetry)
21+
22+
Trace tooling comes from PR #466 (Tempo + Grafana + the OTel PHP extensions). Steps:
23+
24+
1. Ensure the OTel pieces are present (rebase/land PR #466 if not): Tempo + Grafana services in
25+
`docker/docker-compose.override.yml`, `docker/tempo.yml`, `docker/grafana-datasources.yml`, the
26+
`php83-pecl-opentelemetry|grpc|protobuf` extensions in `ADDITIONAL_PHP_PACKAGES`, and the `OTEL_*`
27+
env vars on the `php` service.
28+
2. Enable the opt-in profile/flag (keep it **off by default** so plain review apps stay cheap), then
29+
`make provision reload` (or `docker compose up -d`) to start Tempo + Grafana and install the
30+
extensions.
31+
3. Print the Grafana URL (via `make info` / the Traefik host label) and tell the user to open
32+
**Explore → Tempo**.
33+
4. Generate a trace: load a page, then search in Grafana.
34+
35+
## Hosted alternative
36+
NewRelic: set `NEW_RELIC_LICENSE_KEY`, `make newrelic reload`. Bumping the agent is the
37+
[`dep-bump`](../dep-bump/SKILL.md) skill's job.
38+
39+
## Guardrails
40+
- Don't enable Tier 2 by default in CI/review apps — it adds two services per app.
41+
- If PR #466 isn't landed yet, say so and offer to run the rebase rather than hand-rolling config.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
name: review-app-triage
3+
description: Triage a failing review app or local stack — pull pipeline, Behat (junit), Lighthouse, and watchdog output, then localize the failure and propose a fix. Use when a review app build or a test:* job is red, or when "why did the pipeline fail".
4+
---
5+
6+
# Review-app triage
7+
8+
Goal: turn a red pipeline / broken stack into a localized root cause and a concrete fix. Wraps the
9+
project's own validation targets — see [docs/ci-pipeline.md](../../../docs/ci-pipeline.md) and
10+
[docs/review-apps.md](../../../docs/review-apps.md).
11+
12+
## Inputs
13+
- A GitLab pipeline/job URL or ID, **or** a local stack that is misbehaving.
14+
- The branch/MR under review.
15+
16+
## Steps
17+
18+
1. **Identify the failing stage.** Map the job to its `make` target using the table in
19+
[ci-pipeline.md](../../../docs/ci-pipeline.md#job--make-target--script-map). The likely culprits:
20+
- `build:*` red → install failed: re-run `make all_ci` (or `make all` locally) and read the first
21+
error; usually composer resolution or `drush si`.
22+
- `test:behat` red → read the **JUnit** artifact (`junit/*.xml`) and the screenshots under
23+
`web/screenshots/`. Reproduce locally with `make behat`.
24+
- `test:cinsp` / `test:upgradestatus` / `test:statusreport` / `test:watchdog` red → run the same
25+
target locally (`make cinsp`, `make upgradestatusval`, …); each prints the offending file/config/log.
26+
- `test:lighthouse` red → a category dropped below the `lighthouserc.yml` threshold; read
27+
`web/lighthouseci/*.html`.
28+
2. **Pull logs.** For a running stack: `docker compose logs --tail=200 php` and
29+
`make drush -- watchdog:show --severity=Error --count=50`. See
30+
[docs/observability.md](../../../docs/observability.md).
31+
3. **Reproduce locally** in an isolated worktree if possible
32+
([parallel-environments.md](../../../docs/parallel-environments.md)) so you don't disturb other work.
33+
4. **Localize** to a file/commit. Use `git log -p` on the suspect path; compare against the last green
34+
pipeline.
35+
5. **Report**: the failing job, the exact error, the root-cause file/line, and a proposed fix. For deep
36+
log reading, delegate to the [`ci-log-analyzer`](../../agents/ci-log-analyzer.md) subagent.
37+
38+
## Notes
39+
- Respect the test-ordering constraint (#323): if you re-order jobs, keep `cinsp` before the
40+
schema-mutating checks.
41+
- Don't mark anything fixed until the relevant `make <target>` passes locally.

0 commit comments

Comments
 (0)