Audit · 2026-07-27

Csupi Autómentő — SEO health & SEO-skill readiness

Two independent questions, measured separately: is csupiautomentes.hu actually in good shape, and are the six seo-* skills safe to point at the next client. Every claim below traces to a command; raw outputs in .tmp/audit-2026-07-27/.

Site verdict
Strong

29 of 33 checklist lines PASS. Zero critical defects found live. The four gaps are one measurement blind spot (Search Console), one blocked item (Google cégprofil), and two slow pages.

Skill verdict
Ready — with 2 fixes

The extraction is genuinely sound: the skill scripts rebuilt 44 committed artefacts byte-for-byte. But two of them crash on their first run against a fresh project.

Hardest evidence
44 / 44

Files reproduced byte-identically by the parameterised skill scripts from seo.config.json + committed data alone — 15 service pages and 29 images.

Part A · Is Csupi's SEO good?

Scored against the master checklist in ~/.claude/skills/seo/SKILL.md. "It exists" never counts — each line names the measurement that decided it.

Checklist lineVerdictEvidence
Foundations
seo.config.json in the project rootFAIL Absent. Every skill script anchors on it; verify_redirects.py died with FileNotFoundError: seo-data\gsc-url-status.json until one was supplied.
One canonical host, 301 from the other; .html → extensionlessPASS www → apex 301 · /index.html → 308 / · both single-hop.
Junk URL returns a real 404PASS curl -sI /totally-random-nonexistent-xyz → 404. The soft-404 cascade is gone.
robots.txt present, blocks nothing realPASS Allow: / + sitemap declared. Fetched live.
Per page — 37 indexable pages
Unique <title> ≤ 62 chars1 FAIL 35/37 pass. /furgonmentes = 66 chars. (m31.html at 68 is a dead file, unreachable behind a 301 — see finding 5.)
Unique meta description ≤ 158 chars1 FAIL 35/37 pass. /furgonmentes = 197 chars — Google truncates it.
Self-referencing canonical, exactly onePASS 37/37 present, self-referencing and unique. The historic bug (9 pages canonicalised to /m31) has not returned.
One H1; OG + Twitter in sync with title/descPASS Sweep of all 37 pages for og:title/description/url/image + twitter:* mismatches and H1 count: 0 problems.
JSON-LD parses; fields localised per pagePASS LocalBusiness 37, Service 37, BreadcrumbList 37, FAQPage 36, WebSite 1 — all parse. 37 distinct Service nodes and 37 distinct breadcrumb trails; the "generator forgot to localise areaServed" failure is absent.
No internal links ending in .htmlPASS audit.py → 0. (Was 80 pointless 308 hops.)
Favicon set + OG share imagePASS 4 favicon files live 200; og-share.jpg measured at exactly 1200×630.
Site-wide files
sitemap.xml lists every real page, nothing retiredPASS All 37/37 URLs return 200 live, cache-busted, no hop.
llms.txt present, every link resolvesPASS 39/39 unique links return 200.
Content uniqueness measured vs own baselinePASS Templated locations: max 0.297 / mean 0.176 (site baseline: 0.295 acceptable, 0.35 = delete). Rebuilt topic pages: max 0.164 / mean 0.133. Neither family is doorway-risk.
Redirects & history
GSC export crossed with a live crawlPASS seo-tools/gsc-url-status.json, 265 URLs, dated 2026-07-22. 60% of 16-month clicks sat on URLs that were 404 at crawl time.
Every traffic-earning dead URL mapped, all 301PASS 190 of 193 return 301, routing 4 837 clicks/16m to a real page. The 3 exceptions are instrument artefacts, checked by hand: two are pages since rebuilt (308 → 200 live), one is a Windows console encoding crash on a mangled Unicode path.
Map in a Pages Function if >150 rules; gclid preservedPASS 191 rules in functions/legacy-map.js (past the ~200 _redirects ceiling). ?gclid=TEST123 survives the hop intact.
Search Console
Correct property confirmed, access workingNOT CHECKED No live access this session (agreed scope). Only committed export available.
Sitemap submitted, stale entries removedNOT CHECKEDSame.
Index-coverage buckets read and reconciledNOT CHECKED The committed chart ends 2026-06-12 — before every fix. It cannot speak to today. See finding 1.
No manual actions / security issuesNOT CHECKEDSame.
Performance
Images shipped at 2× render box, SSIM-gated; re-run is a no-opPASS Rebuilt from originals in a clean clone: 29/29 files byte-identical to what is deployed. Every SSIM ≥ 0.99 at painted size.
LCP element identified; no CSS-background LCPPASS LCP is a real HEADER img on all 36 measured pages, and it is preloaded: <link rel="preload" as="image" href="assets/hero-bg.webp" fetchpriority="high">. No preload-scanner trap.
Throttled-mobile sweep of all sitemap URLs, median < 2.5 sPASS slow-4G + 4× CPU at 390 px: median LCP 2 168 ms. The 9 392 ms "worst" was a cold start — re-measured 3× at a median of 2 488 ms. Two genuine laggards remain (findings 3–4).
_headers caching + _routes.json exclusionPASS Second fetch of styles.css → cf-cache-status: HIT; images HIT. _routes.json excludes all 12 static paths from the Function.
CrUX field dataNOT CHECKED PageSpeed API returned 429 — Quota exceeded … Queries per day on both attempts. Needs an API key. All speed numbers here are lab-only, on a deliberately harsh profile.
Local
Live review count feeding schemaPASS /api/reviews returns userRatingCount: 337; schema asserts 337 on all 37 pages. Exact match, live-sourced.
NAP identical across footer, schema, copyPASS One address string on 38/38 pages; one identical LocalBusiness name/telephone/address block on 38/38. tel:+36706262777 on all 391 phone links.
GBP assessed, risk register put to the ownerBLOCKED No manager access since 2026-07-19. Plan written, cannot execute. See finding 2.
Ship & close
Deployed to the production branchPASS Live HTML matches the repo; dist/ and root are in sync (0 drift across 39 files).
Invariant greps livePASS Live homepage carries 8 gfn-track-phone (the documented expectation); all 39 pages carry phone_conversion_number: '06 (70) 62 62 777', matching the displayed string exactly; GTM-TH67S8M and AW-871296311 present live.
Browser QA at 1280 / 768 / 390 incl. mobile menuPASS-WITH-NOTES No horizontal overflow anywhere; hamburger opens all 6 items and closes; hero photo visible and legible; review carousel renders live data; gallery loads. One real cosmetic overlap (finding 6).
Expectations written for the clientPASS SEO-OSSZEFOGLALO.md §5 sets them, and explicitly forbids promising a ranking by a date.
One QA finding did not survive verification. The browser-QA pass reported a "major" defect — blank white boxes where the homepage service-card images should be. Measured directly in the browser, all three images are complete, at natural sizes 1110×833 / 1110×833 / 1024×768, filling their 200 px box with object-fit: cover and a 0 px gap, and all three return 200 live. The agent screenshotted before the loading="lazy" images painted. Reported here rather than silently dropped, because it is the same lesson the audit skill already carries: an instrument is a hypothesis until you validate it.

Findings, ranked by impact

1 · Index status is currently unverifiable HIGH

Evidence
The only Search Console data in the repo (search-console-issues/Diagram.csv) ends 2026-06-12 and shows 2 indexed / 69 not-indexed — a snapshot from before the canonical fix, the soft-404 fix, the 13 rebuilt pages and the 191-URL redirect map.
Why it matters
Everything else in this audit is verified live and healthy — but "the site is technically correct" and "Google has actually re-indexed it" are different claims. The entire value of the 13 rebuilt pages rests on the second one, and right now nobody can state it.
Effort
Minutes. Confirm the apex property (not www — that mix-up already cost a diagnosis once), export the 16-month Pages report, re-run verify_redirects.py against it.
Verify when fixed
Index-coverage count for the apex property > 37, and the 13 rebuilt URLs appear as indexed.

2 · Google cégprofil still unoptimised and inaccessible MEDIUM

Evidence
0 businesses visible under [e-mail cím eltávolítva] (checked 2026-07-19/20); unchanged. Plan exists at plans/seo-expansion-round2/gbp-optimization-plan.md.
Why it matters
For a 0-24 roadside service the map pack routinely outperforms the organic list — a stranded driver taps the map, not result #4. This is the single largest untouched channel, and it is blocked on one client action, not on work.
Effort
Client grants manager access at business.google.com; then a day of work.
Verify when fixed
Profile visible in the account; categories and services mirror the site; photos < 90 days old.

3 · /furgonmentes is the weakest page on the site MEDIUM

Evidence
Title 66 chars (>62), description 197 chars (>158) — both truncated in results. It is also the slowest page measured: it timed out at 120 s in the sweep, and re-measured 3× gave 1 440 / 5 096 / 4 420 ms — median 4 420 ms against a site median of 2 168 ms.
Why it matters
Furgonmentés is a distinct, commercially valuable service with its own demand, and this is its only page. It sits outside apply_copy_fixes.py's page list, which is exactly why it escaped the sitewide copy pass that fixed everything else.
Effort
Copy: minutes. Speed: needs a look at what the page loads that /bikazas-hideginditas (1 472 ms, 35 KB, 2 images) does not — it carries 5 images and 52 KB of HTML.
Verify when fixed
audit.py → 0 problems; 3× re-measure median < 2 500 ms.

4 · /automentes-buda LCP median 3 408 ms LOW-MED

Evidence
Re-measured 3×: 3 408 / 1 892 / 3 624 ms. Not a cold-start artefact — it is consistently above the 2.5 s bar, unlike the other 12 pages that crossed it in the first pass.
Why it matters
Buda is one of the highest-intent location pages and a redirect target for 25 legacy URLs.
Effort
Small — same investigation as finding 3 (both carry more images than their siblings).
Verify when fixed
3× re-measure median < 2 500 ms.

5 · m31.html still ships as a stale duplicate LOW

Evidence
Deployed, index, follow, self-canonical to /m31, identical H1 to /automentes-m31, with an out-of-date 68-char title and 176-char description. It is not in the sitemap, and /m31 301s to /automentes-m31 live — so the edge redirect is the only thing preventing a duplicate.
Why it matters
No active harm today. But it is the file CLAUDE.md still calls "the master template for all location pages", it inflates every audit with two phantom failures, and it is one redirect-rule deletion away from becoming a real duplicate.
Effort
Minutes — delete it, or add noindex, and correct the CLAUDE.md line.
Verify when fixed
audit.py reports 38 → 37 pages and 2 fewer problems.

6 · Floating phone widget overlaps the footer LOW

Evidence
Measured by bounding-box intersection at all three viewports scrolled to the bottom: at 1280 and 768 it covers part of "Autómentés • Furgonmentés • Gépszállítás"; at 390 it also covers "© 2026 Két-Max-Szer Kft. Minden jog fenntartva".
Why it matters
Cosmetic only — no ranking or conversion impact — but it is on every page.
Effort
One CSS rule: extra bottom padding on the footer's last row.
Verify when fixed
Re-run the intersection check → empty overlap list at all three widths.

7 · Two hops from www + a legacy URL LOW

Evidence
www.csupiautomentes.hu/automentes-melygarazsbol → 301 to apex → 301 to /melygarazsbol-mentes. The middleware rewrites the host and returns before consulting the legacy map.
Why it matters
Well within Google's tolerance, and www traffic is small. Noted for completeness, not for action.
Effort
A few lines in _middleware.js — resolve the legacy path before returning the host redirect.
Verify when fixed
curl -sIL shows one 301, not two.

What is working — do not break

What I could not check

Part B · Are the skills ready for the next project?

Not assessed by reading them. Tested: a clean copy of the repo was made in a scratch directory, given the seo.config.json the family assumes exists (and which this project never had), and every skill script was run against it — output diffed byte-for-byte against what is deployed. Nothing in the client repo was modified.

The core extraction is sound, and this is the proof. The parameterised skill copies — which had never been run against a real project — reproduced 15 of 15 service pages and 29 of 29 deployed images byte-identically, driven only by seo.config.json plus committed data. wire_pages.py reported 0 sitemap changes, 0 redirect rules dropped, 0 retargeted; apply_copy_fixes.py reported "0 file(s) would change"; seo_enrich.py and add_favicons.py were clean no-ops. Idempotency is real, not claimed.

Blocking — fix before pointing at a new client

B1 · apply_copy_fixes.py crashes on its own documented config value BLOCKER

Evidence
seo/SKILL.md documents "reviews_api": "/api/reviews". The script does REVIEWS_API = cfg("reviews_api", "") and passes it straight to urllib.request.Request → ValueError: unknown url type: '/api/reviews'. Setting it to an absolute URL made the same run succeed and report "0 file(s) would change".
Why it matters
Following the documentation exactly produces a crash on first use. The Csupi fork hardcoded an absolute URL, so this was introduced by the extraction and has never been exercised.
Fix
Join against site when the value starts with / — one line — or change the documented example to an absolute URL.

B2 · optimize_images.py --dry-run cannot run on a fresh project BLOCKER

Evidence
It writes .tmp probe files into dist/images/ while measuring, but only creates that directory when not in dry-run → FileNotFoundError: …\dist\images\ blue_mercedes_g_wagon.webp.tmp. Creating the directory by hand made the identical command succeed.
Why it matters
Dry-run is the first thing anyone does on a new project, and it is precisely the case where dist/images/ does not yet exist. The failure looks like a broken script, not a missing directory.
Fix
os.makedirs(OUT, exist_ok=True) unconditionally.

Safety — should fix

B3 · optimize_images.py fails open into the exact bug it exists to prevent MEDIUM

Evidence
BOXES is a hardcoded table of Csupi's filenames and measured render boxes. An image not in it is silently shutil.copy2'd at full original size (the comment justifies this for og-share.jpg). On a new project no filename matches, so every original is copied whole.
Why it matters
The script's entire reason to exist is that 55 MB of 4000×3000 originals were being shipped. On a new client it would reproduce that silently and report success.
Fix
Load BOXES from measure_boxes.js output in the project (it already ships in seo-ship/scripts/), and make an unmeasured, non-excluded image a loud error.

B4 · An unrecognised flag means "run for real" MEDIUM

Evidence
No script parses arguments. Probing with --help: wire_pages.py executed and rewrote the sitemap; seo_enrich.py and add_favicons.py executed; gen_*_pages.py silently treated it as a page slug and did nothing. Only the exact string --dry-run is honoured, by substring match on sys.argv.
Why it matters
A typo — --dryrun, -n — writes to the project instead of previewing. On a client site that is a live edit.
Fix
argparse, or at minimum reject unknown flags.

B5 · The family's own hard invariant is not met MEDIUM

Evidence
seo/SKILL.md invariant #3 requires every transform to be idempotent, support --dry-run, and "run its selftest on every invocation". Measured: gen_service_pages.py and gen_location_pages.py have neither a dry-run nor a selftest; only apply_copy_fixes.py has a selftest.
Why it matters
The generators are the scripts that write whole pages — the ones where a preview matters most. A rule the skills state but do not follow will not survive contact with a second project.
Fix
Add --dry-run to both generators. Either add selftests or soften the invariant to what is actually enforced.

Documentation & data

ItemSeverityDetail
B6 · LEARNINGS.mdMEDIUM seo/SKILL.md line 11 says the full record with numbers lives in LEARNINGS.md at the skills repo root. Indexed every file under all eight seo* skills — it does not exist. The router's stated provenance is a dead pointer.
B7 · seo-setupMEDIUM Superseded by seo-onpage (which says so), but still present, still listed, and its description covers the same ground almost word for word — so it can win the trigger. It also still references scripts at seo-tools/… paths that now live in seo-onpage/scripts/. It ships a SKILL.md and nothing else. Retire it.
B8 · misfiled script pathLOW seo-audit/SKILL.md §5 cites scripts/speed_sweep.js, implying its own directory; it lives in seo-ship/scripts/. A fresh session follows that path and finds nothing.
B9 · location-page data missingMEDIUM Project-side, not skill-side, but it breaks the same promise. seo-tools/locations/ holds exactly one file (automento-paty.json) while 23 location pages are live. gen_location_pages.py can only update pages already on disk — run with no arguments it printed skip automento-paty — no page on disk and touched nothing. The 23-page location family is not reproducible from the repo. The invariant "generators are source, they get committed" holds for the code and fails for the data.
B10 · hardcoded dist/LOW wire_pages.py hardcodes dist/_redirects and dist/sitemap.xml; not in the config spec. Fine for Cloudflare Pages, wrong for any other layout.
B11 · cosmeticNIT optimize_images.py prints 0 KB -> 2802 KB (-280100% smaller) when the baseline is empty — i.e. on every first run.

Verdict on the skills

READY-WITH-FIXES. The method and the scripts are real: they rebuilt this site's artefacts byte-for-byte from config plus data, which is a far stronger result than a code review could have given. What is not yet proven is the first-run path, because it has never been walked — and both blockers (B1, B2) live exactly there. Fix B1 and B2, then B3 and B4 for safety, and the family is safe to point at a new client. B6–B8 are half an hour of tidying. B9 is a Csupi data debt worth paying before anyone tries to regenerate a location page.

The strongest recommendation from this exercise: the next project should start by writing seo.config.json and running every script in dry-run before any real work — that single step surfaced both blockers in under an hour.