seo-drift — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited seo-drift (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Example output: examples/seo-drift-wix-com-20260514/compare/DRIFT-REPORT.md
Git for SEO. Capture a snapshot of a domain or URL's SEO state ("baseline"), then on later runs diff the current state against the baseline and surface regressions. Catches the things that get worse silently after a deploy, redesign, or content cull.
Acknowledgements: drift-as-an-SEO-skill framework originated in claude-seo by AgriciDaniel (with the original concept credited to Dan Colta, Pro Hub Challenge). MIT-licensed both directions; this implementation is independent but the framing is theirs.WebFetch tool available (for URL-mode page fingerprinting).baseline, compare, history).| Flag | Mode | Effect |
|---|---|---|
--no-firecrawl | baseline, compare | Skip Firecrawl-based <head> + JSON-LD capture even when Firecrawl is installed (saves credits at the cost of canonical / robots / og:* / JSON-LD diff coverage). |
--skip-cwv | baseline, compare | Skip the Google CrUX capture (step 4b) even when google-api.json is configured. Useful when you only care about content/structural drift, or when CrUX rate-limit concerns outweigh CWV coverage. Mirrors theirs at seo-drift/SKILL.md:107, 131. |
--baseline-id <n> | compare | Compare against a specific baseline by ID rather than the most recent. |
--limit <n> | history | Cap the number of historical entries shown. |
baseline <target>Capture the current SEO state and write it to a snapshot file. No diff produced.
compare <target>Load the most recent baseline for the target. Capture the current state. Diff. Produce DRIFT-REPORT.md.
history <target>List all stored baselines for the target with their dates and key metrics (DA, traffic, keyword count). No diff produced.
example.com; URL = anything starting with http(s)://.python3 -c "from scripts.google_auth import validate_url; import sys; sys.exit(0 if validate_url('{target}') else 1)" (or import validate_url directly in any helper script). Reject loopback (127.0.0.1, ::1, localhost), private IP ranges (10/8, 172.16/12, 192.168/16), link-local (169.254/16), and Google metadata endpoints. If validation fails, abort with a clear error and don't proceed to fetch — feeding an unvalidated URL into Firecrawl / WebFetch / Google APIs would risk SSRF against internal services. Mirrors theirs at seo-drift/SKILL.md:97.skills/seo-firecrawl/references/preflight.md for the canonical 3-stage preflight (credit balance, Firecrawl availability, Google APIs). Skill-specific notes:<head> + JSON-LD content so canonical / robots / og:* / JSON-LD changes are detectable on diff. Without it the snapshot is partial. Pass --no-firecrawl to skip Firecrawl even when available (saves credits at the cost of diff coverage).indexStatusVerdict, googleCanonical, lastCrawlTime) so subsequent compares can flag field-data and indexation drift. See skills/seo-google/references/cross-skill-integration.md § "seo-drift" for the full recipe.DATA_getDomainOverviewWorldwide — DA, traffic, organic + paid keyword counts.DATA_getDomainKeywords — top 100 organic keywords with positions.DATA_getBacklinksSummary — backlinks total, referring domains total.DATA_getBacklinksRefDomains — top 20 referring domains with authority.WebFetch (always) + mcp__firecrawl-mcp__firecrawl_scrape (when available)<title>, all <h1..h6>, lang, word count, internal-link count, image count, body markdown for prose-level diff.<head> and <script> content WebFetch strips:metadata: canonical URL, robots meta, og:title, og:description, og:image, twitter:card.html: every <script type="application/ld+json"> block. Capture both detected @types and a hash of the full block content (so any schema-content change is detected on diff, not just type-list changes).BASELINE.md notes: Snapshot fields recovered via WebFetch only — canonical, robots, og:*, twitter:*, and JSON-LD changes will not be detected on subsequent compares. Install Firecrawl for full coverage.DATA_getPageAuthority.4b. Google field-data snapshot (only if google-api.json is present AND `--skip-cwv` not set)
python3 scripts/pagespeed_check.py "{target}" --crux-only --json (URL mode) or python3 scripts/pagespeed_check.py "https://{domain}" --crux-only --json (domain mode, origin-level CrUX). Store the resulting p75 LCP / INP / CLS / FCP / TTFB and the source label ("URL" or "origin").python3 scripts/crux_history.py "{target_or_origin}" --json for the 25-week trend window snapshot — store as crux_history_baseline. Subsequent compares can detect drift against the most recent week.python3 scripts/gsc_inspect.py "{target_url}" --site-url "{config.default_property}" --json. Store indexStatusVerdict, coverageState, googleCanonical, userCanonical, lastCrawlTime.--skip-cwv was passed, skip this step entirely and store null for cwv / crux_history fields. The compare-mode rules then surface "Field-data drift: skipped — --skip-cwv flag passed at baseline."null for the affected metrics and continue.seo-drift-{target-slug}-{YYYYMMDD}/snapshot.json.seo-drift-{target-slug}/baselines.json — append {date, snapshot_path} entry.seo-drift-{target-slug}/baselines.json.validate_url() check as baseline mode. Refuses to fetch private/loopback/metadata addresses.references/drift-thresholds.md:not comparable — Firecrawl-only fields missing from {baseline | current} snapshot rather than as a green pass.INDEXED to anything else → red.googleCanonical changed → yellow.lastCrawlTime >60 days old → yellow.Field-data / indexation drift: not comparable — Google fields missing from {baseline | current} snapshot.DRIFT-REPORT.md — red findings first, then yellow, then green/positive deltas. End with a "what to investigate first" recommendation.baselines.json.HISTORY.md with the table.seo-drift-{target-slug}-{YYYYMMDD}/:
seo-drift-{target-slug}-{YYYYMMDD}/
├── snapshot.json (the captured state)
└── BASELINE.md (one-page human summary of what was captured)seo-drift-{target-slug}-{YYYYMMDD}/:
seo-drift-{target-slug}-{YYYYMMDD}/
├── DRIFT-REPORT.md (synthesised: red/yellow/green changes — primary deliverable; inlines 01-domain-deltas, 02-keyword-churn, 03-backlink-deltas, 04-page-deltas as sections)
└── evidence/
├── baseline-snapshot.json (the prior reference — kept for replay)
├── current-snapshot.json (today's state — kept for replay)
├── 01-domain-deltas.md (DA, traffic, keyword count changes — raw step output)
├── 02-keyword-churn.md (top-100 entries/exits)
├── 03-backlink-deltas.md (new + lost backlinks/domains)
└── 04-page-deltas.md (URL mode only: HTML fingerprint diff)Top-level: DRIFT-REPORT.md only. The four delta step files are inlined into named sections in DRIFT-REPORT.md; evidence/ keeps the raw delta dumps and both snapshot JSONs so a future re-diff or audit can replay against them.
DRIFT-REPORT.md shape:
# Drift Report: {target}
> Baseline: {baseline date} · Current: {today's date}
## RED — investigate today
- {finding} ({severity rationale})
- ...
## YELLOW — investigate this week
- {finding}
- ...
## GREEN — positive deltas
- {finding}
- ...
## Field-data drift (CrUX + URL Inspection)
- LCP p75: {baseline} → {current} ms ({Δ%}) {↑ red / ↑ yellow / stable / ↓ green}
- INP p75: {baseline} → {current} ms ({Δ%}) {…}
- CLS p75: {baseline} → {current} ({Δ absolute}) {…}
- Indexation status: {baseline INDEXED → current EXCLUDED} (URL mode)
- googleCanonical: {baseline → current} (if changed)
- (Or: `Field-data / indexation drift: not configured` / `not comparable — missing from {snapshot}`)
## What to investigate first
1. {prioritised action with reasoning}
2. ...seo-drift-{target-slug}-{YYYYMMDD}/HISTORY.md:
# History: {target}
| Date | DA | Traffic | Keywords | Top-3 |
|---|---|---|---|---|
| 2026-04-27 | 42 | 18,500/mo | 1,247 | 89 |
| 2026-03-15 | 41 | 17,200/mo | 1,213 | 85 |
| ...DATA_getCreditBalance before running. Domain baseline ~10–15 SE Ranking credits; URL baseline ~15–20 SE Ranking credits + 1 Firecrawl credit; compare ~20–30 SE Ranking credits + 1 Firecrawl credit (current-state capture).seo-drift-{target-slug}/ directory in a shared filesystem or commit it to a private repo. Baselines are JSON — git-friendly.DATA_getPageAuthorityHistory (URL mode) or DATA_getDomainAuthorityHistory returns flat-zero values across the window, treat as "insufficient history" — don't compute a delta or surface a regression based on missing data. Cross-check the current-value endpoint (DATA_getPageAuthority / DATA_getDomainOverviewWorldwide) — if the current value is meaningful but history is flat, surface that as a data-quality flag in DRIFT-REPORT.md rather than fabricating a trend.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.