# ----------------------------------------------------------------------------- # VENDORED COPY — DO NOT EDIT THIS FILE BY HAND. # # Canonical source of truth: # git.chemavx.xyz/chemavx/chemavx-seo-tools -> seo_rules.py # (local working copy: ~/seo-tools/seo_rules.py) # # The bot reuses the shared SEO rule engine inside the container, where # seo-tools is not installed. Everything below the BEGIN marker is a # byte-for-byte copy of the canonical file. # # To update: edit the canonical, then run `make sync-seo` (re-copies here). # Drift guard: CI step "Verify vendored SEO engine" clones the canonical and # diffs it against the content below the marker; the build FAILS on # any divergence. Locally, `make check-seo-sync` does the same. # ----------------------------------------------------------------------------- # ===== BEGIN VENDORED seo_rules.py (exact copy of canonical; do not edit below) ===== """ Reusable SEO rule engine for The Exclusion Zone (EN) — theexclusionzone.com (Ghost). Pure functions, NO I/O. Feed it a Ghost Admin API post dict (with `html` format included) and it returns a list of Violation(rule, severity, message, fix). Shared by: - seo_audit.py — Tool A, site-wide auditor (this engine, run over all posts) - (future) seo_validate.py — Tool C, pre-publish validator (same engine, one draft) Design note: every check is an independent function registered in RULES. To add a rule, write a function (post) -> list[Violation] and append it to RULES. The auditor and the validator both just call check_post(); they never re-implement a check. """ import re from collections import namedtuple SITE_HOST = "theexclusionzone.com" # ---- thresholds (single source of truth, reused by validator) ------------- META_TITLE_MAX = 60 META_DESC_MAX = 145 CUSTOM_EXCERPT_MAX = 300 MIN_INTERNAL_LINKS = 2 # ---- severity weights (used to rank "worst first") ------------------------ HIGH, MED, LOW, INFO = 3, 2, 1, 0 SEV_NAME = {HIGH: "HIGH", MED: "MED", LOW: "LOW", INFO: "INFO"} # The Edition-main theme injects BlogPosting JSON-LD globally in default.hbs # ({{#is "post"}} ...