seo-social-cards (M8)
Social cards control how a URL renders when shared on social, chat, and AI assistants — a CTR/sharing signal, not a ranking one (see Honesty). Open Graph metadata is the same <head>-level hygiene tier as the structured-data work in references/schema-tier1.md, so audit it alongside that module.
Inputs
Work from the PageSnapshot named in your dispatch envelope: read parsed from <run_dir>/pages/<slug>.json (metas[] for og:*/twitter:*, canonicals[], images[]); Grep pages/<slug>.html for verbatim evidence; site artifacts live in <run_dir>/site/{robots.json,sitemaps.json,discovery.json}. Deterministic findings already emitted by audit.mjs are listed in <run_dir>/findings.deterministic.json — do not re-emit those ids; add model-judged findings only. If invoked directly with a URL/path and no snapshot exists, first run node "${CLAUDE_PLUGIN_ROOT}/scripts/snapshot.mjs" <target> --out "${CLAUDE_PLUGIN_DATA}/runs" and use the printed snapshot path.
Audits
Working from the PageSnapshot (parsed_rendered when render.used is not none, else parsed):
- OG content:
og:title, og:description, og:url, og:type present and non-empty; og:url matches the canonical; og:type sane for the page (e.g. article vs website).
- OG image:
og:image present, ~1200x630 (1.91:1) and >=200x200, and reachable (HTTP 200, image content-type, absolute URL).
- Twitter/X:
twitter:card (prefer summary_large_image); twitter:title, twitter:description, twitter:image. Twitter falls back to OG when these are absent — note when it is relying on OG.
- Fallback behavior: when OG is absent entirely, record what crawlers would derive (
<title>, meta description, first content <img>) so the finding shows the actual degraded card, not a guess.
Fixes
- AUTO (
fixable: auto): inject a complete OG + Twitter card block derived from the page's <title>/<h1>, meta description, canonical URL, and first in-content image. Additive, deterministic, emitted as a diff for fix.
- The tool cannot create an image asset. It wires
og:image/twitter:image once a real path exists; until then it leaves a clearly-marked TODO placeholder (no invented URL or dimensions). Never fabricate title/description text — derive from on-page content or leave the user a TODO.
Verification
dom_assert: the required <meta property="og:*"> / <meta name="twitter:*"> tags exist and are non-empty.
header_check: og:image returns 200 with an image content-type, and dimensions are ~1200x630.
- When the image fetch or dimension probe is blocked (no network/egress), status is
needs_api, never a false pass.
Findings
Emit findings per schema/finding.schema.json. Examples:
M8.og.missing_image — no og:image (status fail, severity 2, fixable: auto, axis search, confidence established).
M8.og.image_unreachable — og:image set but returns non-200 / wrong type (status fail, severity 2, fixable: proposed, axis search, confidence established).
M8.twitter.no_card — no twitter:card, page falls back to OG only (status warn, severity 2, fixable: auto, axis search, confidence directional).
Each finding: evidence.observed quotes the tag(s) (or their absence) on the page; verification.reproduce is a runnable dom_assert/header_check command; expected_impact is banded + confidence-tagged (no naked %).
Honesty
Social cards are a sharing / click-through signal with near-zero direct ranking impact — Google does not use OG tags as a ranking factor. They improve how shared links and AI-assistant previews render, not where the page ranks. Fix them for CTR and brand consistency; do not over-invest expecting SERP movement.
1---2name: seo-social-cards3description: Audit and generate Open Graph and Twitter/X social-card tags for a page — check og:title/description/url/type, og:image presence/size/reachability, and twitter:card/title/description/image, then inject complete card tags inferred from the page. Module M8. Feeds the Search SEO score.4---56# seo-social-cards (M8)78Social cards control how a URL renders when shared on social, chat, and AI assistants — a CTR/sharing signal, not a ranking one (see Honesty). Open Graph metadata is the same `<head>`-level hygiene tier as the structured-data work in `references/schema-tier1.md`, so audit it alongside that module.910## Inputs11Work from the PageSnapshot named in your dispatch envelope: read `parsed` from `<run_dir>/pages/<slug>.json` (`metas[]` for `og:*`/`twitter:*`, `canonicals[]`, `images[]`); Grep `pages/<slug>.html` for verbatim evidence; site artifacts live in `<run_dir>/site/{robots.json,sitemaps.json,discovery.json}`. Deterministic findings already emitted by `audit.mjs` are listed in `<run_dir>/findings.deterministic.json` — do not re-emit those ids; add model-judged findings only. If invoked directly with a URL/path and no snapshot exists, first run `node "${CLAUDE_PLUGIN_ROOT}/scripts/snapshot.mjs" <target> --out "${CLAUDE_PLUGIN_DATA}/runs"` and use the printed snapshot path.1213## Audits14Working from the PageSnapshot (`parsed_rendered` when `render.used` is not `none`, else `parsed`):151. **OG content**: `og:title`, `og:description`, `og:url`, `og:type` present and non-empty; `og:url` matches the canonical; `og:type` sane for the page (e.g. `article` vs `website`).162. **OG image**: `og:image` present, ~1200x630 (1.91:1) and >=200x200, and **reachable** (HTTP 200, image content-type, absolute URL).173. **Twitter/X**: `twitter:card` (prefer `summary_large_image`); `twitter:title`, `twitter:description`, `twitter:image`. Twitter falls back to OG when these are absent — note when it is relying on OG.184. **Fallback behavior**: when OG is absent entirely, record what crawlers would derive (`<title>`, meta description, first content `<img>`) so the finding shows the actual degraded card, not a guess.1920## Fixes21- **AUTO** (`fixable: auto`): inject a complete OG + Twitter card block derived from the page's `<title>`/`<h1>`, meta description, canonical URL, and first in-content image. Additive, deterministic, emitted as a diff for `fix`.22- The tool **cannot create an image asset**. It wires `og:image`/`twitter:image` once a real path exists; until then it leaves a clearly-marked `TODO` placeholder (no invented URL or dimensions). Never fabricate title/description text — derive from on-page content or leave the user a `TODO`.2324## Verification25- `dom_assert`: the required `<meta property="og:*">` / `<meta name="twitter:*">` tags exist and are non-empty.26- `header_check`: `og:image` returns 200 with an image content-type, and dimensions are ~1200x630.27- When the image fetch or dimension probe is blocked (no network/egress), status is `needs_api`, never a false `pass`.2829## Findings30Emit findings per `schema/finding.schema.json`. Examples:31- `M8.og.missing_image` — no `og:image` (status `fail`, severity 2, `fixable: auto`, axis `search`, confidence `established`).32- `M8.og.image_unreachable` — `og:image` set but returns non-200 / wrong type (status `fail`, severity 2, `fixable: proposed`, axis `search`, confidence `established`).33- `M8.twitter.no_card` — no `twitter:card`, page falls back to OG only (status `warn`, severity 2, `fixable: auto`, axis `search`, confidence `directional`).34Each finding: `evidence.observed` quotes the tag(s) (or their absence) on the page; `verification.reproduce` is a runnable `dom_assert`/`header_check` command; `expected_impact` is banded + confidence-tagged (no naked %).3536## Honesty37Social cards are a sharing / click-through signal with **near-zero direct ranking impact** — Google does not use OG tags as a ranking factor. They improve how shared links and AI-assistant previews render, not where the page ranks. Fix them for CTR and brand consistency; do not over-invest expecting SERP movement.