When to use
| Scenario | Action |
|---|---|
| "Run today's SEO daily report" | Run the full patrol (scripts/daily-report.py) |
| "A keyword's ranking dropped" | Single-word diagnosis (Claude directly executes the diagnosis flow below) |
| "Fix canonical" | Batch merge (scripts/canonical-fix.py) |
| "Rescue this article" | Social media rescue (scripts/rescue-post.py) — title front-loading + 3 high-authority internal links |
| "Check GA4 deployment" | Scan HTML for G-XXXXX tag |
| "Check llms.txt" | HTTP 200 + byte count |
Core flow
1. Daily report mode
Input: none (fully automatic) or a specified keyword list Output:
- Keyword ranking diff table (today vs yesterday's baseline)
- Google index count
- llms.txt status
- GA4 coverage status
- 🚨 Anomaly alerts (dropped out of top 100, overall refresh)
2. Single-word diagnosis
Input: 1 keyword Process:
- Run SERP for 4-5 long-tail variants
- Check whether the target URL returns HTTP 200
- Use
site:query to confirm whether it is indexed - Compare with data from 3 days ago (if history exists)
- 2026-06-24 newly added checks:
- Confirm the keyword is in the volume 300–1,000 / KD 5–35 range (if not, mark as low priority)
- Check whether the page title contains best/free/top/guide+year (CTR formula)
- Check whether the page's internal link density exceeds 2-3/paragraph
- Confirm the page has BreadcrumbList schema
- Confirm the last-updated date is displayed (freshness signal) Output: choose one of avalanche / individual / SERP refresh / dead link, with a 2026 compliance score
3. Canonical batch fix
Input: N articles on the same topic + 1 master URL
Process: replace the canonical_url: → master in each article's frontmatter, GitHub API PUT
Safety guardrails:
- Only modify
_posts/.md files under - Each article is committed separately (for easy rollback)
- Skip ja/ko (hreflang replacement, keep self-canonical)
4. Social media rescue
Input: 1 avalanche URL Actions:
- a. Rewrite title: front-load the primary keyword, length ≤ 60 characters,must contain best/free/top/guide+year(2026-06-24 CTR formula)
- b. Rewrite meta description:keyword rephrase(e.g. "figma alternative" → "the alternative to Figma")
- c. Inject internal links from top-3 high-authority articles (with anchor text = target keyword),max 2-3 internal links per paragraph
- d. Proactively add 1-2 competitor blog external links(raise trust score → raise ranking)
- e. Check and add/update the [Last Updated Date] (freshness signal)
- f. Confirm the BreadcrumbList schema is configured
- g. Log the changes before and after
API dependencies
| Service | Env var | Purpose |
|---|---|---|
| DataForSEO | DATAFORSEO_B64 |
SERP query |
| GitHub PAT | GITHUB_TOKEN |
read/write _posts/ |
For the full key template, see docs/api-keys-template.md。
SERP query template
import urllib.request, json
def serp(kw, loc=2840, lang="en", depth=100):
key = os.environ["DATAFORSEO_B64"]
payload = json.dumps([{"keyword": kw, "location_code": loc,
"language_code": lang, "device": "desktop",
"depth": depth}]).encode()
req = urllib.request.Request(
"https://api.dataforseo.com/v3/serp/google/organic/live/advanced",
data=payload,
headers={"Authorization": f"Basic {key}",
"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=40) as r:
return json.loads(r.read())
Common location_code:
- 2840 US / 2826 UK / 2392 JP / 2410 KR / 2156 CN (invalid, China does not return)
Execution scripts
The actually executable scripts are under scripts/ :
daily-report.py— full daily reportcanonical-fix.py— canonical bulk mergerescue-post.py— social media article rescuediag-keyword.py— single-keyword diagnosis (roadmap, temporarily replaced by the "single-word diagnosis" process below)
Each script can be run independently. When invoking, prefer the Bash tool; do not rewrite the scripts.
Output specifications
- Give the table first, then the conclusion
- Use arrows for differences:
#6 ↑/#19 ↓↓ - More than 3 anomalies → open a separate "🚨 Anomalies" section
- All rankings must include location_code, do not assume
Cascade recommendations
- Found cannibalization (multiple articles ranking close together for the same keyword) →
gr-blog-postdo canonical consolidation - Found N keywords dropped out of the top 100 on the same day → wait 24h first, do not force content changes
- Found new keyword opportunities (long-tail top 30) →
gr-blog-postexpand
Anti-patterns
- ❌ Do not use
grep/findto scan_posts/, use the GitHub Contents API instead - ❌ Do not directly
git pushin the production environment —— use the Contents API PUT - ❌ Do not serp 50 keywords at once —— too large a batch easily triggers rate limit, do batches of 6-10
- ❌ After changing canonical to master, do not immediately force Google to reindex —— wait 3-7 days for natural crawling
Monthly Full-Site Audit Workflow
Validated 2026-05-07 on 58 pages: caught 43 SERP-truncating titles + 36 schema warns + 27 stop-word slugs in a single 30-min pass. Single layout-level fix (commit
24a0410e) resolved 20 of 43 title issues.
Run once per month before phase2-monthly-checkpoint. Output: HTML report + machine-readable findings.json archived to ~/Downloads/seo-audit/audit-{YYYY-MM-DD}.json.
Stage 1 — Discovery (5 min)
import urllib.request, re
sm = urllib.request.urlopen("https://gingiris.tools/sitemap.xml").read().decode()
urls = [u for u in re.findall(r"<loc>([^<]+)</loc>", sm) if "/blog/" in u]
# typically 50-70 URLs
Stage 2 — Parallel Audit (20 min for 60 pages, 4 threads)
Use the adopted seo-audit-skill scripts in scripts/:
# For each URL — run 2 scripts in parallel
python3 scripts/check-page.py URL --timeout 20 # title, H1, meta, canonical, slug, alt, keyword placement
python3 scripts/check-schema.py URL --timeout 20 # JSON-LD validation
Or batch with Python's concurrent.futures.ThreadPoolExecutor(max_workers=4). Don't go higher than 4 — GitHub Pages CDN throttles aggressive parallel hits.
Each script outputs structured JSON envelope:
{"field": {"status": "pass|warn|fail|info", "detail": "...", "llm_review_required": false}}
Stage 3 — Aggregate Findings
Bucket by category:
- Title length > 70 chars (SERP truncation risk)
- H1 length > 70 chars (mobile readability)
- Meta description < 80 or > 170 chars
- Schema warns by @type (BlogPosting, Article, Organization)
- Canonical issues (mismatch with final URL)
- Slug issues (stop words, uppercase, missing keyword)
- Image alt text missing on content images
Save aggregated counts + per-issue URL lists to findings.json.
Stage 4 — Layered Fix Strategy (HIGH ROI ORDER)
1️⃣ Layout-level fixes first (1 commit, fixes 20+ pages):
- Schema bugs in
_layouts/default.html - Site-name suffix in
<title>tag - Missing
dateModifiedfromlast_modified_atfrontmatter - Organization / Publisher / contactPoint completeness
2️⃣ Config-level fixes (1 commit, fixes site-wide):
_config.yml— logo URL (must be absolute), twitter, social, author structure
3️⃣ Per-article batch fixes (1 commit per file, parallelizable):
- Trim long titles while preserving keyword (target ≤ 70 chars, ideal 50-60)
- Trim long H1s (target ≤ 70 chars)
- Expand short meta descriptions (target 120-160 chars)
- Add missing Citable Statistics blocks for top GSC-impression pages
4️⃣ Skip these (low ROI):
- Slug stop words (changing breaks 301)
- Old articles with <50 imp/month (low traffic = low fix priority)
Stage 5 — Verify (after Jekyll rebuild ~60-90s)
Re-run check-schema.py on a sample page. Confirm status: pass for at least:
Article · BlogPosting · Organization · FAQPage
Stage 6 — Archive
Commit findings.json to ~/Downloads/seo-audit/audit-{YYYY-MM-DD}.json for trend tracking.
Add 2-5 atoms documenting any new lessons learned.
HARD RULE (anti-hallucination guardrail)
Adopted from JeffLi1993/seo-audit-skill — strict whitelist pattern.
⛔ Output ONLY the checks defined in the audit script's JSON envelope.
- Do NOT add "bonus" checks not in the script output
- Do NOT contradict the script's
statusfield unless you have additional observable evidence - Do NOT invent metrics like "EEAT score 89" — third-party scoring tools are unofficial (per Google's 2026 guidance)
- Do NOT include checks marked
llm_review_required: falsein your judgment commentary — the script'sstatusis final - If
llm_review_required: true, make explicit judgment, document reasoning, then update status
The script envelope is the single source of truth. Treat it as a strict whitelist.
Companion skill
For single-page audits (not full-site), the same scripts power JeffLi1993/seo-audit-skill which produces a polished HTML audit report. Install as a complementary skill if you want client-presentable per-page audits.
Platform Description
EN: Daily SEO/GEO patrol skill (2026-06-24 standards). Tracks SERP keyword rankings (DataForSEO, filters volume 300–1,000 / KD 5–35), checks Google index count, llms.txt accessibility, GA4 tag deployment + AI traffic channel grouping. Rescues underperforming posts by applying title formula (best/free/top/guide+year), keyword rephrase in meta description, internal link density enforcement (≤2–3 per paragraph), competitor blog external link injection, BreadcrumbList schema check, and freshness date update. Fixes canonical issues.
ZH: Daily SEO/GEO patrol skill (2026-06-24 standard). Track SERP rankings (DataForSEO, volume 300–1,000 / KD 5–35 filter), check Google index count, llms.txt reachability, GA4 deployment + AI traffic channel grouping. Rescue avalanche articles: title contains best/free/top/guide+year, meta description keyword rephrase, internal link density ≤2–3/paragraph, proactively cite competitor external links, BreadcrumbList schema, freshness date update, canonical fix.
JA: Daily SEO/GEO patrol skill (2026-06-24 standard). SERP ranking tracking (DataForSEO, volume 300–1,000/KD 5–35 filter), Google index count, llms.txt confirmation, GA4 tag + AI traffic channel setup. Rescue of snow avalanche articles: best/free/top/guide + year in title, meta description keyword rephrase, internal link density ≤2–3/paragraph, competitor blog external link insertion, BreadcrumbList schema, freshness date update, canonical fix.
KO: Daily SEO/GEO patrol skill (as of 2026-06-24). SERP ranking tracking (DataForSEO, volume 300–1,000/KD 5–35 filter), Google index count, llms.txt accessibility, GA4 tag + AI traffic channel group check. Recovery of underperforming posts: apply best/free/top/guide+year to titles, rephrase meta description keywords, internal link density ≤2–3/paragraph, insert external links to competitor blogs, BreadcrumbList schema, update freshness date, fix canonical.