skills: add SEO/AEO audit and grounded-JSON-LD skills from citeworthyio/seo-agent

Evaluated citeworthyio/seo-agent for reusable process knowledge. Relevant
to this fleet's WordPress content sites (granja travel news, content-agent
pipeline): a crawl-diagnose-approve-recrawl-measure loop for SEO metadata
changes, a three-layer AI-crawler accessibility audit (robots policy, bot
delivery, readable content), and a fail-closed method for generating and
safely injecting Article JSON-LD. Keyword research and citation-probe
pieces of the source repo were skipped as underdeveloped or too
infra-coupled to be reusable here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 13:28:29 +02:00
co-authored by Claude Sonnet 5
parent c7ce105e00
commit ae61c7ce7b
4 changed files with 225 additions and 0 deletions
+10
View File
@@ -100,6 +100,15 @@ than assuming the delegate can fetch it itself.
- `finishing-development-branches` -- verify the exact branch tip, confirm
its base, and require an explicit integration choice before merge, Gitea
push/PR, or cleanup.
- `seo-change-lifecycle` -- audit the metadata crawlers actually receive, move
fixes through approval and recrawl verification, then evaluate delayed
page-level Search Console signals without claiming causation.
- `ai-crawler-accessibility-audit` -- distinguish answer-engine bots from
training crawlers, detect robots/CDN blocks and misleading llms.txt soft
404s, and verify that non-JS fetchers receive readable page content.
- `grounded-article-jsonld` -- generate Article structured data only from
observed page facts, validate it at every boundary, and serialize it safely
for additive script injection.
## Provenance
@@ -115,6 +124,7 @@ each skill's frontmatter:
- [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill) (MIT)
- [JuliusBrussee/caveman](https://github.com/JuliusBrussee/caveman) (MIT;
evaluated, no skill retained)
- [citeworthyio/seo-agent](https://github.com/citeworthyio/seo-agent) (MIT)
## Vetting external skills
@@ -0,0 +1,68 @@
---
name: ai-crawler-accessibility-audit
description: Use when auditing whether a site can be crawled, read, and cited by AI answer engines, especially for JavaScript-rendered sites, CDN bot controls, robots.txt policy, llms.txt, or unexplained absence from AI citations.
license: MIT
source: adapted from https://github.com/citeworthyio/seo-agent/blob/b78bb487f2819c3c7cc2ba6f3f5626ea0b841840/src/aeo.ts
---
# AI Crawler Accessibility Audit
Audit three separate layers: policy, fetch delivery, and readable content. A
healthy `<head>` does not make a client-rendered page citable because AI crawlers
and user-fetchers generally do not execute the site's JavaScript.
## 1. Separate answer-engine bots from training bots
Read the live `/robots.txt` and evaluate its actual groups using longest-match
robots rules; an exact user-agent group takes precedence over `*`, and `Allow`
wins equal-length ties.
Treat blocking answer/search fetchers as a discoverability defect. Check at
least OAI-SearchBot, ChatGPT-User, Claude-SearchBot, Claude-User, PerplexityBot,
Perplexity-User, Googlebot, Bingbot, Meta-WebIndexer, DuckAssistBot,
MistralAI-User, Amazonbot, and Applebot. Googlebot and Bingbot also feed AI
answer surfaces.
Treat training-only crawlers separately: GPTBot, ClaudeBot, CCBot,
Google-Extended, Applebot-Extended, meta-externalagent, and Bytespider. Allowing
or denying these is a content-policy choice; do not claim that blocking them
alone removes the site from citations. Explicit groups are preferable to an
accidental allow-all because they document intent and survive managed
`robots.txt` defaults.
## 2. Validate agent-facing text resources by content
Fetch `/llms.txt` and, if advertised, `/llms-full.txt`. A `404` is an honest
absence. A `200` containing an HTML application shell or `text/html` is a soft
404 and worse: an agent asked for a text map and received misleading content.
Serve a real plain-text/Markdown resource or a clean absence. Treat these files
as cheap insurance, not as a proven ranking lever.
## 3. Compare ordinary and AI-user-agent delivery
From successful content pages, rotate a small daily sample rather than checking
the same first URLs forever. Fetch each sampled URL once normally and once with
the relevant AI user agent.
- If the normal request is `200` but the AI request is `403`, `429`, or `451`,
investigate CDN/WAF/bot-management policy. The controlled user-agent
difference is stronger evidence than either status alone.
- If the AI response succeeds, strip scripts, styles, and markup and inspect the
visible body. Fewer than roughly 200 characters, without full content in
`Article.articleBody`, is a useful empty-shell tripwire; inspect borderline
pages rather than treating the number as a universal quality score.
- Record transient fetch errors as unknown/check errors. Do not turn a failed
measurement into a claim that the site is blocked or healthy.
## 4. Fix the serving layer, then verify it
Prefer server rendering or static rendering. If that is not viable, serve a
real, fetchable HTML or Markdown content lane from the same source data, with
correct content negotiation and `Vary` headers. `articleBody` can be a fallback,
but visible server-delivered content is stronger.
Re-run both the ordinary and AI-user-agent requests against the public URL.
Only suppress an empty-body finding when there is positive evidence that an
alternate readable twin is live for that exact path. A configured twin that
cannot actually be fetched must not hide a true failure.
+61
View File
@@ -0,0 +1,61 @@
---
name: grounded-article-jsonld
description: Use when generating, reviewing, storing, or injecting Article JSON-LD from page content. Prevent fabricated machine-readable claims and script-context injection while preserving existing structured data.
license: MIT
source: adapted from https://github.com/citeworthyio/seo-agent/blob/b78bb487f2819c3c7cc2ba6f3f5626ea0b841840/README.md#structured-data-json-ld and src/overrides.ts
---
# Grounded Article JSON-LD
Structured data is a factual publication, not decorative copy. An invented
author or date becomes a machine-readable claim under the site's name and may
be consumed without a reader ever seeing and correcting it.
## Generate only from observed page facts
Give the generator only the page content actually fetched: title, description,
canonical, visible article content, and explicit author/date/image fields. Tell
it to omit anything it cannot point to in that material. Never infer an author,
publisher, organization, date, image, rating, price, or other plausible default.
For an article page missing structured data, a minimal correct `Article` object
with `@context`, `@type`, and a grounded headline is better than a rich-looking
object containing guesses. Keep automated generation narrow to page-level types
such as `Article`; organization, product, rating, and similar business claims
need their own authoritative data source and review process.
## Validate at every boundary
Use the same fail-closed validator for generated drafts, manual proposals, and
the final publishing write:
1. Parse as JSON.
2. Accept one object or a non-empty array of objects, never primitives or null.
3. Require every node to have an `@context` referencing `schema.org` and a
non-empty `@type`.
4. Enforce a bounded canonical serialization (the source implementation uses
8,192 characters).
5. Reject the value rather than storing it if any check fails.
Using one validator matters: otherwise a draft can pass review and fail at
publish time, or a manual path can bypass the protections applied to AI output.
## Canonicalize for safe script embedding
Store JSON text without a `<script>` wrapper. After parsing, serialize it again
and replace literal `<` and `>` in the serialized bytes with JSON escapes
`\u003c` and `\u003e`. A JSON parser reconstructs the original characters, but
the stored bytes cannot close an enclosing script element or open an HTML
comment—even when the input expressed `<` through a JSON escape.
Do not use HTML entities such as `&lt;` inside JSON: they change the data rather
than safely encoding JSON for a script context. At injection time, reject any
stored value that unexpectedly contains a literal `<`; it did not pass the
canonical write path.
Add the validated block alongside existing JSON-LD instead of replacing it.
Multiple blocks are valid, while overwriting an origin block can silently erase
publisher-authored facts. Verify the public response contains a parseable block
of the intended type; presence proves less than byte-for-byte comparison, so do
not overstate what that check establishes.
+86
View File
@@ -0,0 +1,86 @@
---
name: seo-change-lifecycle
description: Use when auditing a content site's SEO, proposing metadata changes, or evaluating whether an applied SEO change helped. Treat findings as measured conditions and close the loop through approval, delivered-output verification, and delayed Search Console comparison.
license: MIT
source: adapted from https://github.com/citeworthyio/seo-agent/blob/b78bb487f2819c3c7cc2ba6f3f5626ea0b841840/README.md (How it works, Findings lifecycle, Analytics)
---
# SEO Change Lifecycle
An SEO edit is not complete when source content changes. It is complete only
after the served page is recrawled and the outcome has had time to emerge in
search data. Use this loop for metadata and technical on-page changes.
## 1. Snapshot what crawlers actually receive
Start from the sitemap and fetch every listed final URL. Record the delivered
status, title, meta description, canonical, robots directive, OpenGraph image,
and JSON-LD types. Do not audit templates or CMS fields in place of the response:
an edge rewrite, stale cache, redirect, or failed deployment can make the served
page disagree with its source.
If no usable sitemap exists, use a bounded, same-origin link crawl and label the
result incomplete. A link crawl cannot find orphan pages, so absence from that
sample is not evidence that a page does not exist.
## 2. Diagnose deterministic conditions
Prefer repeatable rules whose evidence can be preserved and checked again. At a
minimum, inspect:
- sitemap URLs returning errors or redirects;
- missing titles, duplicate titles, and titles whose core exceeds 60 characters
or whose delivered total exceeds 80;
- missing descriptions, descriptions shorter than 70 or longer than 160
characters, and descriptions ending in `...` because upstream truncation can
hide an over-long source value;
- missing or off-page canonicals;
- `noindex` pages still present in the sitemap;
- missing `og:image` and missing `Article` JSON-LD on known article routes.
Decode HTML entities before length checks so `&amp;` counts as the one character a
reader sees. Key each finding by `(path, rule)`: keep it open while the condition
persists and auto-resolve it only when a fresh crawl no longer reproduces it.
This makes findings sensors, not tickets someone can close without fixing the
underlying page.
## 3. Draft a constrained proposal
Give a drafter the page's observed content, the exact failed rule, the current
served value, and the site's positive style guidance. Require the smallest field
change that can clear the finding. Use deterministic surgery when there is one
right answer (for example, removing a duplicated brand suffix or replacing a
canonical with the page's own normalized URL); a model adds risk without value.
Validate model output outside the model. Reject an unchanged value, forbidden
terms, a title that includes a suffix the publishing layer will append, or copy
outside the agreed length and sentence constraints. An invalid proposal is no
proposal, not a reason to relax the gate.
## 4. Review, apply, journal, and recrawl
Require human approval by default. Record the old value, approved value, field,
path, timestamp, and a reversible change identifier. After applying, fetch the
public URL again and compare the delivered field with the approved value. Keep
the finding in an `applied, awaiting recrawl` state until that comparison passes.
This extra verification catches the failures source inspection misses: a wrong
cache key, route gap, stale CDN response, lost edge binding, or deployment that
never reached the serving path. A successful write to a CMS or override store is
not proof that crawlers see the change.
## 5. Measure impact without claiming causation
Use page-level Search Console data, not site-wide totals. Leave a three-day
settling gap after the change, then compare equal 14-day before/after windows;
repeat with 28-day windows when enough data exists. Compare clicks and
impressions as per-day rates and CTR/position as impression-weighted averages.
Do not calculate a verdict if either window is absent or the two windows total
fewer than 50 impressions.
Treat a relative CTR movement of about 15% or an average-position movement of
about 1.0 as a review signal, with impression shifts checked for confounding.
Call the result correlation, never causation: seasonality, algorithm changes,
competitors, and query-mix changes remain plausible explanations. A negative
signal should open a review/revert decision, not trigger an automatic rollback.