A Claude Code skill that cleans up years of content debt on a Drupal site — over core JSON:API, with zero contrib modules, and a human approval gate in front of every change.
you: My blog has hundreds of old posts with messed up formatting —
bold text instead of headings, broken images, missing alt text.
Can you clean this up?
The skill then:
- Asks before it acts — scope (full site / demo pages / audit only), content types, dead-image handling, cosmetic depth, apply mode, model choice (with a cost estimate), and whether to back up the DB first.
- Audits every node over JSON:API: pseudo-headings, missing/junk alt
text, dead images (HEAD-checked), spacer paragraphs, suspect
<br>s. - Fixes with judgment — the AI rewrites each body: bold section titles
become
<h2>/<h3>while bold inline labels stay bold; alt text is written by looking at the image (vision), never from filenames; dead images are flagged for a human, not guessed at. - Applies safely — dry-run diff first, then JSON:API PATCH with a revision log message, so every change is one click from rollback in Drupal. A content hash guards against editing conflicts.
- Reports — a marketer-readable HTML report: what was found, what was fixed, what needs a human.
Deterministic mechanics live in scripts; judgment lives in the AI.
| Piece | Role |
|---|---|
SKILL.md |
The workflow + the judgment rules (see below) |
scripts/audit.py |
Crawl via JSON:API, detect issues, HEAD-check images |
scripts/apply.py |
Dry-run diffs; --live PATCHes with revisions + conflict detection |
scripts/estimate.py |
Token/cost estimate shown before the user commits |
scripts/report.py |
Before/after HTML report |
approved-commands.md |
Every shell command a run uses, for permission pre-approval |
The interesting part is the judgment rules in SKILL.md, each learned from a
real page and a human review catch:
- Judge the rendered page, not the database — this site's
full_htmlformat strips styles/classes on output, so stored markup lies. - Spacer
<p> </p>removal is contextual: sometimes the fix is converting bare divs to real paragraphs, or stripping legacymargin:0inline styles — not deleting the spacers. <br>tags are read in context: leading/doubled ones are cruft; breaks in addresses, signatures, and botanical names are content.- Anything the AI judges too risky to restructure is flagged "needs a human" instead of touched.
Requires Claude Code, uv, and a Drupal
9+ site (local example uses DDEV).
- Copy (or symlink) this directory to
~/.claude/skills/drupal-content-cleanup/(available everywhere) or<project>/.claude/skills/(per-project). - On the target site (dev/local only — never production directly):
drush en jsonapi basic_auth -y drush cset jsonapi.settings read_only 0 -y drush role:create content_bot && drush user:create content-bot --password=… drush role:perm:add content_bot 'access content,edit any blog content,view media,use text format full_html'
- Create a
.envnext toSKILL.md(or at the project root):CLEANUP_BASE_URL=https://your-site.ddev.site CLEANUP_USER=content-bot CLEANUP_PASS=… CLEANUP_SITE_DIR=/path/to/local/ddev/project # for backups & drush - Ask Claude Code to clean up your content, or run
/drupal-content-cleanup.
Site-specific bits to adjust for other sites: IMAGE_FIELDS in audit.py
(this site's blog uses field_image_feature/field_image; the audit
degrades gracefully if a type lacks them) and the full_html format check.
- Nothing is applied without a dry-run diff and an explicit
--livestep. - Every applied change is a Drupal revision with a descriptive log.
- DB backup (local dump and/or remote via terminus) is offered — and gated — before the first apply.
- The skill can't silently expand its own permissions: on first run it shows the exact command allowlist and asks what you're comfortable pre-approving. Content writes always prompt.
estimate.py prices the job from the audit before anything runs. For this
site's ~700 flagged posts: ~6.8M input / ~4.0M output tokens — roughly $80
with bulk batches on Sonnet (recommended; the premium model writes the
rules, a cheaper model executes them), vs ~175 hours of manual editing.