Skip to content

Commit 5f76b87

Browse files
andypostclaude
andcommitted
Add project docs, agent harness, worktree helper, and validation CI
Documentation, an inheritable Claude Code harness, and a Tier 0 validation gate for this template. No runtime/Drupal behavior changes. - docs/: review-apps, ci-pipeline, architecture, local-development, upgrading (D10->11->12), observability, parallel-environments, delivery-and-ops, testing, backlog, and an index. - .claude/: settings allowlist, 5 skills, 3 subagents, 2 workflows. - scripts/makefile/worktree.mk: `make worktree-name` for parallel per-branch local stacks. - scripts/ci/validate-harness.mjs + scripts/makefile/validate.mk: Tier 0 static gate (links, settings.json, workflow modules, frontmatter, doc/CI drift, EOF newlines, self-consistency). - .github/workflows/validation.yml: runs the harness gate + Makefile sniffers (clang/compval/phpcs/newlineeof) on pull requests. - .gitlab-ci.yml: sniffers:harness job. - CONTRIBUTING.md; expand CLAUDE.md and README with doc/harness links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pv3rn53U7YYUuYFutvFCzD
1 parent 1f2316e commit 5f76b87

31 files changed

Lines changed: 2018 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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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: Behat writes PNGs to `features/` (FailAid `screenshot.directory` in
21+
`behat.default.yml`); in CI the `test:behat` job moves them to `web/screenshots/` and serves them on
22+
the review URL.
23+
3. Correlate with recent changes (`git log --oneline -15`, `git diff` against last green) to point at a
24+
suspect file/commit.
25+
4. Note environment-specific causes: SAPI mismatch (Unit vs FrankenPHP), DB engine, basic-auth on the
26+
review URL, missing CI variable (REVIEW_DOMAIN, runner tag, RA_BASIC_AUTH).
27+
28+
Report:
29+
- Failing job + `make` target.
30+
- The exact first error (quoted).
31+
- Most-likely root cause and the file/commit to look at.
32+
- A reproduction command (`make <target>` locally).
33+
34+
Do not propose code edits beyond naming the file/line; do not run destructive commands. If logs are
35+
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.

0 commit comments

Comments
 (0)