Seo audit
Local-first, free-tier SEO + GEO/AEO audit: brand scan, entity/citability signals, JSON-LD schema, external probes (Lighthouse/GSC/PageSpeed), /100 scored report, optional --push. Use when you need an SEO audit, /seo-audit, GEO-Audit, Schema-Check.From its SKILL.md
npx -y skills add silvio-l/skills --skill seo-auditAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
14.8 KB, ~3.8k tokens by cl100k_base, as published. Nobody here has run it
seo-audit — Local-First SEO Audit
You are the auditor. You inventory the repo, scan the built HTML for brand-consistency violations, synthesize the findings into a prioritized list, and write a single Markdown report. You do not fix the findings — that is the user's call after they read the report.
Where things live
| Concern | File |
|---|---|
| Inventory phase — framework / pages / SEO assets / app-store / domain doc | inventory.md |
| Brand-consistency phase — glossary parser, scanner, suppression rules | brand.md |
| GEO/AEO scan phase — entity page, citable prose, FAQ, heading structure, llms.txt | geo.md |
| Schema/JSON-LD audit phase — presence, required fields, deprecated types, sameAs | schema.md |
| External-probes phase — seven adapters, parallel runner, live-smoke | probes.md |
| Push phase — IndexNow, Bing Webmaster, llms.txt; confirmation flow | push.md |
Setup-Onboarding phase — --doctor / --setup <tool> / --verify | setup.md |
| Synthesis phase — weights, score formula, dedup, tiebreaker, headline score | synthesis.md |
| Report phase — sections, template, diff mode, Strategisch/Technisch split | report.md |
| Report template (Markdown) | templates/report.md |
| Dispatcher (single entry point) | scripts/audit.py |
| Glossary parser | scripts/glossary_parser.py |
| HTML scanner | scripts/brand_scan.py |
| Inventory scanner | scripts/inventory.py |
| GEO/AEO scanner | scripts/geo_scan.py |
| Schema/JSON-LD scanner | scripts/schema_scan.py |
Positioning-brief loader (--brief) | scripts/positioning_brief.py |
| External-probes adapters | scripts/probes/ |
| Push adapters (IndexNow / Bing / llms.txt) | scripts/push/ |
| Setup-Onboarding (doctor + wizards + verify) | scripts/setup/ |
| Synthesis (pure logic) | scripts/synthesis.py |
Read the phase doc when you enter that phase. SKILL.md is the
always-on layer — keep it minimal.
Quick start
S=~/.claude/skills/seo-audit/scripts/audit.py
# Audit the current repo, write into .scratch/seo-audit/.
python3 "$S" --root .
# Audit a different repo, custom dist, diff against the prior report.
python3 "$S" --root ~/code/whispaste --dist ~/code/whispaste/build \
--report-dir ~/code/whispaste/.scratch/seo-overhaul \
--compare-last
The dispatcher prints the absolute path of the written report on stdout. Open that file and verify it before declaring the run done — confirm it actually contains all four canonical sections (per report.md) and the findings table; exit 0 alone is not proof the report is well-formed. Only then read it back to summarize for the user.
Phase order
- Inventory —
inventory.pywalks the repo root. Output drives the report header and tells downstream phases where the built HTML lives. - Brand scan —
glossary_parser.pyreads the firstBegriff | Stattdessen | Grundtable fromCONTEXT.md→CLAUDE.md→README.md.brand_scan.pymatches every term case-insensitively againstdist/(or--dist) on a word-boundary, excluding<script>/<style>blocks. Per-file frontmatter flagcontrastiveVocabulary: trueand per-section<!-- seo-audit:contrastive -->markers suppress matches. - GEO/AEO scan —
geo_scan.pychecks entity/citability signals: About-page presence, citable prose blocks (≥ 60 chars), FAQ/Q&A structures, heading structure (H1 count, hierarchy, pseudo-headings), andllms.txt/llms-full.txtpresence. All checks are offline; findings carrydimension=geo. Heavy checks (prose, FAQ) are skipped under--quick. - Schema/JSON-LD scan —
schema_scan.pyextracts all<script type="application/ld+json">blocks, validates JSON, checks required-field completeness for 7 core types (via versionedREQUIRED_FIELDS_V1), flags deprecated types (DEPRECATED_TYPES_V1), and checks sameAs social-profile consistency. All checks are offline; findings carrydimension=schema. Files withcontrastiveVocabulary: truein an HTML comment are excluded. - Positioning brief (optional) —
positioning_brief.pyloads brand context from--brief <path>, or auto-discovers from<root>/.seo/positioning.mdor a<!-- seo:brief -->fenced section inCONTEXT.md. The brief is never used by finding-producing phases — it only flows into the report's recommendation section. - External probes — run when
--url <url>is passed. Seven adapters (Lighthouse, pa11y, W3C, Schema.org validator, Observatory, GSC, PageSpeed) fan out concurrently. Requires network. - Synthesis —
synthesis.pydedups (key:file_path, line_number, match, category, dimension), scores each finding byseverity × user_impact / fix_effort, computes a per-dimension breakdown and a/100headline score via versionedDIMENSION_WEIGHTS_V1, and sorts deterministically. - Report —
audit.pyrenderstemplates/report.mdwith the synthesis output and writes.scratch/<feature>/seo-audit-<YYYY-MM-DD>.md. Recommendations are split into Strategisch (track=strategic, content/entity decisions) and Technisch (track=technical, automatable fixes) sections, with copy-paste-ready fix snippets for JSON-LD and llms.txt findings.
Arguments
| Flag | Default | Behaviour |
|---|---|---|
--root <path> | required | Repository root to audit. |
--dist <path> | <root>/dist | Directory of built HTML to scan. |
--report-dir <path> | <root>/.scratch/seo-audit | Output directory. |
--brief <path> | none | Path to a Markdown positioning brief. Provides brand context for the recommendation section only — never affects findings or score. Auto-discovered from <root>/.seo/positioning.md or a <!-- seo:brief --> fenced section in CONTEXT.md when absent or unreadable. |
--quick | off | Skip heavy per-file GEO checks (prose analysis) and the site-wide FAQ scan; skip the heavy probes (Lighthouse, pa11y). |
--url <url> | none | Run external probes against this live URL. Repeatable. Requires network. |
--push | off | Enable push module (IndexNow, Bing Webmaster, llms.txt). Opt-in; the agent confirms each operation with the user before firing it. |
--dry-run | off | Only valid with --push: render the push plan to stdout without performing any submissions or writes. |
--compare-last | off | Diff against the most recent prior report in --report-dir. |
--doctor | off | Setup-Onboarding diagnostic — read-only env / file / probe inspection. Mutually compatible with --verify. See setup.md. |
--setup <tool> | none | Single-tool setup wizard. Valid tools: indexnow, pagespeed, bing, gsc. Not combinable with --doctor / --verify. See setup.md. |
--verify | off | One minimal probe call per configured tool, returning per-tool OK/4xx/5xx status. Mutually compatible with --doctor. See setup.md. |
--force | off | Force-regenerate setup artefacts (currently only honoured by --setup indexnow). |
Definition of Done (single source of truth)
A seo-audit run is DONE only when all of the following hold:
- The inventory section identified a framework (or
unknownwith a reason). - A glossary was loaded — or the report explicitly notes "no glossary found".
- The brand scan produced a deterministic finding list — running it twice over the same input yields byte-identical results.
- Suppression markers (
<!-- seo-audit:contrastive -->,contrastiveVocabulary: truefrontmatter flag) were honoured by both the brand scan and the schema scan. - The GEO/AEO scan ran over the same
--distdirectory and produced findings withdimension=geo. Two runs over identical input yield byte-identical results. - The Schema/JSON-LD scan ran over the same
--distdirectory and produced findings withdimension=schema. Two runs over identical input yield byte-identical results. - The synthesis output carries a
/100headline_scoreand a per-dimensiondimensions_breakdown(one score per dimension inDIMENSION_WEIGHTS_V1). The findings are sorted by score desc, with the(file_path, line_number, match, dimension)tiebreaker. - The report file exists under
<report-dir>/seo-audit-<YYYY-MM-DD>.mdwith these canonical sections: Executive Summary (headline score + dimensions breakdown), Findings nach Kategorie, Diff zum letzten Lauf, Empfehlungen (Strategisch / Technisch / Fix-Snippets). - The positioning brief (if loaded) appears only in the Positionierungs-Kontext subsection of the report — it never alters the finding list, scores, or synthesis output.
- Recommendations are split: Strategisch (du entscheidest) lists
findings with
track=strategic(content/entity decisions the human must make); Technisch (umsetzbar) liststrack=technicalfindings (automatable one-file or copy-paste fixes). - Fix snippets (copy-paste-ready JSON-LD blocks, llms.txt skeleton) are present in the report for every finding whose fix is deterministically derivable from the finding data.
- If
--urlis omitted, no external network call is made (the entire brand/GEO/schema pipeline stays fully offline). If--urlis supplied, the probe layer runs and its findings flow through the same synthesis pipeline. - If
--pushis passed, no submission or file write happens until the agent has asked the user, per operation, and received an explicit confirmation. The script never prompts; the agent does.--push --dry-runis side-effect-free.
Push confirmation flow (binding for the agent)
When the user passes --push, the dispatcher prints a structured plan
to stdout. Before executing any operation, you (the agent) must:
- Read the plan back to the user in plain prose — name each module,
show what would be submitted, and surface every warning and
first_setup_hint. - Ask once per module: "Should I run the IndexNow push to
<host>?" — wait for an explicit yes/no. - Only call
push.execute_all(plans, clients=..., confirmations=...)withconfirmations[module] = Truefor the modules the user confirmed. Modules the user declined stayFalseand are skipped silently. - If a module's plan is
ready: False, do not even ask — read thereason/first_setup_hintto the user and move on.
The script intentionally has no input() call. That keeps the
confirmation in your conversational control where it belongs.
Free-tier discipline
This skill is strictly local-first. Without --url, the pipeline
makes zero network calls. With --url, the seven probe adapters run
through npx or curl against public endpoints with the quotas
documented below.
Free-Tier — was kostet was?
| Tool | Cost | Quota / day | What happens past quota |
|---|---|---|---|
GEO/AEO scanner (geo_scan.py) | free, runs locally | n/a — local HTML walk | n/a |
Schema/JSON-LD scanner (schema_scan.py) | free, runs locally | n/a — local HTML walk | n/a |
Positioning-brief loader (positioning_brief.py) | free, local file read | n/a — local | n/a |
Lighthouse (npx lighthouse) | free, runs locally | n/a — bound only by local CPU | n/a |
pa11y (npx pa11y) | free, runs locally | n/a — local | n/a |
W3C Nu validator (validator.w3.org/nu/) | free, no API key | no published hard cap; "polite use" — the documented guidance is ≤ 1 request/sec (W3C Nu docs) | requests rejected with HTTP 429 until the rate drops |
Schema.org validator (validator.schema.org) | free, no published API contract | no published quota — polite-use convention | response throttled / shape may change without notice |
Mozilla HTTP Observatory (http-observatory.security.mozilla.org/api/v1/) | free, no key | no published hard cap; results cached server-side for 24h per host (Observatory docs) | rescans before the 24h cache window return cached results |
Google Search Console API (mcp__gsc__*) | free with a verified GSC property | 1 200 queries / minute, 30 000 queries / day per project (GSC API quotas) | HTTP 429 / quota-exceeded — wait until the next day |
| Google PageSpeed Insights API | free with API key | 25 000 requests / day, 240 requests / 100 s / user (PSI API quotas) | HTTP 429 — adapter logs the error and contributes [] |
IndexNow (api.indexnow.org) | free, no provider key — user generates and self-hosts the key file | no published per-day cap; one POST submits a batch of URLs (IndexNow docs) | malformed requests rejected; missing key file → HTTP 4xx |
| Bing Webmaster URL Submission API | free, BING_WEBMASTER_API_KEY from Webmaster Tools | 10 URLs / day default for unverified sites; 10 000 / day for verified (Bing docs) | HTTP 4xx; the skill clips the batch via a local date-rolled counter (BING_DAILY_LIMIT env overrides) |
llms.txt / llms-full.txt (local file) | free; generated locally | n/a — purely local file write | n/a |
Push (IndexNow, Bing Webmaster, llms.txt) is opt-in via --push and
confirmed per operation. See push.md.
Limitations (v1)
- HTML-only scanning. The scanner reads
.html/.htmfiles under--dist. SPA-rendered content that only shows up after JS execution is invisible. Slice 02's Lighthouse adapter will cover that. - One glossary table per doc. The parser picks the first table
whose header reads
Begriff | Stattdessen | Grund. If a project needs multiple tables, split them across the candidate files (CONTEXT.mdwins overCLAUDE.mdwins overREADME.md). - Markdown report only. No HTML or PDF output. The report is designed to be skimmed in an editor and diffed in git.