The problem robots.txt never tells you about
A site's robots.txt declares two sitemaps. The first one parses, most URLs return 200, everyone moves on. The second one is a sitemap index pointing at three child sitemaps that nobody has ever validated. That gap - valid-looking but unverified - is where sitemap bugs live, and they are cheap to find and usually cheap to fix.
This article gives you a Codex skill that does the whole check in one pass: read the robots.txt declarations, probe the common paths when nothing is declared, and for every sitemap it finds, report XML validity, URL count against the 50,000 limit, file size against the 50MB limit, lastmod consistency, deprecated tags, and a status sample of the first 20 target URLs. It also warns you what a sitemap index implies: each child sitemap needs the same checks again. Ten minutes to install, one command per site.
Why sitemaps still matter in 2026
Sitemaps are not a ranking factor and never were. They are a crawl-efficiency and crawl-priority signal: they tell Google which URLs exist and when each one last changed meaningfully, so the crawler can skip discovery through links where the site makes that slow or impossible (huge catalogs, paginated archives, empty JS-returned link trees).
Two things about the signal are worth knowing, because both are where sites tend to get sloppy:
lastmod is only honored when it is honest. Google has said repeatedly that it uses <lastmod> when it is consistently and verifiably accurate - and ignores it when sitemaps carry boilerplate dates. A sitemap where every URL has the same lastmod is a red flag, not a feature. It also means your sitemap is doing zero work: if the tamper dates are ignored, the crawler goes back to link discovery anyway.
The per-file limits force architecture decisions before you hit them. Fifty thousand URLs or 50MB uncompressed per file, whichever comes first. Past that, you need a sitemap index and split files. The same rule applies to the extension sitemaps with different ceilings: 1,000 image entries per URL and 1,000 video entries per file, and for news sitemaps only 1,000 items per file and articles from the last two days only. If you generate a sitemap programmatically, this is the requirement your generator must handle, not just your checker.
The pipeline in one glance
robots.txt declarations -> probe common paths -> parse each sitemap
-> validate (XML, limits, lastmod, deprecations)
-> sample target URL statuses
-> prioritized issue list
(if sitemap index -> repeat the middle on each child)The checker automates the middle. The judgment table below is what you apply to its output.

The reference: limits, deprecations, and extension caps
Check | Rule | Severity when violated |
|---|---|---|
URLs per file | 50,000 max | Critical: split with index |
Size per file | 50MB uncompressed max | Critical: split with index |
URL status | all should return 200 | High: remove or fix broken |
noindexed URLs | none, ever | High: remove from sitemap |
Redirected URLs | none; use final URLs | Medium: update |
lastmod | valid W3C Datetime, real change date, not uniformly copied | Low when all identical: effectively ignored by Google |
| deprecated, ignored by Google | Info: removable |
HTTPS | sitemap and URLs on HTTPS | High when HTTP |
Extension sitemaps each carry their own smaller caps:
Type | Cap | Required | Deprecated (info-level) |
|---|---|---|---|
Image | 1,000 |
| caption, geo_location, title, license (2022) |
Video | full video:title checks | thumbnailloc, title, description, contentloc or player_loc | category, galleryloc, price, tvshow, autoplay, allowembed |
News | 1,000 | publication, name, language, publication_date, title | remove old items (last 2 days only) |
The full SKILL.md
This is the file you give to Codex. It keeps the methodology and thresholds of the project it is adapted from, rewritten for the Codex runtime (the original was written for a different assistant harness). Every command inside is a plain python3 call.
---
name: codex-seo-sitemap
description: Use when the user asks about XML sitemaps, sitemap issues, missing sitemaps, sitemap validation, sitemap generation, or sitemap index files. Triggers on "sitemap", "check my sitemap", "generate sitemap", "sitemap not found". Works with any site; python3 only.
---
# Sitemap Analysis & Generation
Analyze existing XML sitemaps or generate new ones. Analysis mode validates
format, URL counts, sizes, lastmod quality, deprecations, and samples target
URLs. Generation mode applies industry quality gates before emitting XML.
## Run
```bash
python3 ~/.codex/skills/codex-seo-sitemap/scripts/sitemap_check.py <url> [--json]
```
The checker reads robots.txt `Sitemap:` lines, probes common paths when nothing
is declared, and reports per-sitemap: status, XML validity, URL count, size,
lastmod validity and distinctness, deprecated tags, and a status sample of
the first 20 target URLs. `--json` gives the full structure.
## Mode 1: Analyze Existing Sitemap
### Discovery rules
- Read robots.txt first; a declared sitemap is a candidate, not proof it works.
- If nothing is declared or a declared sitemap is stale/invalid, probe common
paths: /sitemap.xml, /sitemap_index.xml, /sitemap-index.xml, /sitemap1.xml,
/wp-sitemap.xml, /seo-sitemap.xml.
- Report "no sitemap found" only after declared and common candidates are
checked and all fail.
### Validation checks
- Valid XML parse
- Per-file limit: 50,000 URLs AND 50MB uncompressed, whichever is hit first
- All URLs return 200 (the checker samples the first 20; crawl the rest for a
full audit)
- `<lastmod>` valid W3C Datetime and plausibly the real last content change;
Google only honors it when consistently accurate, so warn on uniformly
identical values or values newer than the page's real content
- No deprecated tags: `<priority>` and `<changefreq>` are ignored by Google
- Sitemap referenced in robots.txt
- Compare crawled pages vs sitemap and flag missing pages (in a full crawl)
### Quality signals
- Sitemap index file if >50k URLs
- Split by content type (pages, posts, images, videos)
- No non-canonical URLs
- No noindexed URLs
- No redirected URLs
- HTTPS URLs only
### Common issues
| Issue | Severity | Fix |
|-------|----------|-----|
| >50k URLs in single file | Critical | Split with sitemap index |
| >50MB uncompressed single file | Critical | Split with sitemap index |
| Non-200 URLs | High | Remove or fix broken URLs |
| Noindexed URLs included | High | Remove from sitemap |
| Redirected URLs included | Medium | Update to final URLs |
| All identical lastmod | Low | Use actual modification dates |
| Priority/changefreq used | Info | Can remove (ignored by Google) |
### Extension sitemaps (image / video / news)
Image: namespace google.com/schemas/sitemap-image/1.1. Only two valid tags
remain, `<image:image>` and `<image:loc>` (max 1,000 `<image:image>` per
`<url>`). `<image:caption>`, `<image:geo_location>`, `<image:title>`,
`<image:license>` were deprecated (2022) - info-level removable.
Video: required `<video:video>` with `<video:thumbnail_loc>`,
`<video:title>`, `<video:description>`, plus `<video:content_loc>` or
`<video:player_loc>`; mRSS also supported. Deprecated/removed tags
(`<video:category>`, `<video:gallery_loc>`, `<video:price>`, `<video:tvshow>`,
player autoplay/allow_embed) are info-level removable; recheck Google docs
before citing a removal date.
News: max 1,000 `<news:news>` per file (not 50,000); only articles from the
last 2 days; required `<news:publication>`/`<news:name>`/`<news:language>`/
`<news:publication_date>`/`<news:title>`; submitted through Search Console or
a robots/sitemap-index reference. When the `news:` namespace is present,
override the 50k check with the 1,000 cap.
## Mode 2: Generate New Sitemap
### Process
1. Ask for business type (or auto-detect from the existing site)
2. Load the industry architecture template
3. Plan structure interactively with the user
4. Apply quality gates:
- WARNING at 30+ location pages (require 60%+ unique content)
- HARD STOP at 50+ location pages (require justification)
5. Generate valid XML
6. Split at whichever comes first: 50,000 URLs or 50MB uncompressed, using a
sitemap index
7. Generate STRUCTURE.md site-architecture documentation
### Safe programmatic pages (OK at scale)
- Integration pages with real setup docs
- Template/tool pages with downloadable content
- Glossary pages with 200+ word definitions
- Product pages with unique specs and reviews
- User profile pages with user-generated content
### Penalty risk (avoid at scale)
- Location pages with only the city name swapped
- "Best [tool] for [industry]" without industry-specific value
- "[Competitor] alternative" without real comparison data
- AI-generated pages without human review and unique value
## Sitemap format
Standard:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/page</loc>
<lastmod>2026-02-07</lastmod>
</url>
</urlset>
```
Index (for >50k URLs or split-by-type):
```xml
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://example.com/sitemap-pages.xml</loc>
<lastmod>2026-02-07</lastmod>
</sitemap>
<sitemap>
<loc>https://example.com/sitemap-posts.xml</loc>
<lastmod>2026-02-07</lastmod>
</sitemap>
</sitemapindex>
```
## Reading the checker output
Each sitemap prints status, XML validity, root tag (urlset or sitemapindex),
URL count, size, lastmod statistics, deprecated-tag counts, and for a plain
urlset a sample of target URL statuses (ok / redirected / missing / other).
The flags out:
| Output | Meaning |
|--------|---------|
| `urls > 50,000 limit` | split into an index |
| `size > 50MB` | split into an index |
| `lastmod distinct: 1` | all identical; likely boilerplate, Google ignores it |
| `deprecated tags` | priority/changefreq present, removable |
| `sample_redirected/missing` | stale sitemap entries |
| `index file with N children` | re-check each child sitemap |
## Errors
| Scenario | Action |
|----------|--------|
| URL unreachable | Report status code; check if the site is live |
| No sitemap found | Report that, after robots.txt and common paths, nothing was found |
| Invalid XML | Report the parse error and line context; fetch fresh |
| Sitemap served with HTML status 200 | Likely a 404 page; treat as invalid, re-check the sitemap URL |
| HEAD blocked by WAF | The checker falls back to ranged GET; sizes may be approximate |
| Rate limiting | Back off; report partial results with retry timing |
## Output
### For analysis
- A report listing each sitemap, its health, and the prioritized issues
- Issues with severity from the table above
- Recommendations per issue
### For generation
- sitemap.xml (or split files plus index)
- STRUCTURE.md: site architecture documentation
- URL count and organization summaryThe checker
The script is one file, standard library only. It does five things:
- Parse robots.txt for
Sitemap:lines (same host only, no cross-host fetches). - Probe common sitemap paths when nothing is declared.
- Parse each sitemap as XML, count URLs, check size from the download itself.
- Validate every lastmod against the W3C Datetime format and count distinct values.
- Sample the first 20 target URLs with HEAD, falling back to a ranged GET when
a server or WAF refuses HEAD (many do).
It reports a sitemap index without recursing by design: the skill's job is to point at the children and tell you to check each one, not to crawl an entire site in a single command.
#!/usr/bin/env python3
"""Check a site's XML sitemaps: discovery, XML validity, size, URL count,
deprecated tags, lastmod format, and a sample of target URL statuses.
Standard library only. Usage:
python3 sitemap_check.py <url> [--json]
"""
import json
import re
import sys
import urllib.error
import urllib.request
import urllib.parse
import xml.etree.ElementTree as ET
TIMEOUT = 12
MAX_URL_COUNT = 50000
MAX_SIZE_BYTES = 50 * 1024 * 1024
SAMPLE = 20
UA = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36 "
"codex-seo-sitemap/1.0")
COMMON_PATHS = [
"/sitemap.xml", "/sitemap_index.xml", "/sitemap-index.xml",
"/sitemap1.xml", "/wp-sitemap.xml", "/seo-sitemap.xml",
]
DATETIME_RE = re.compile(
r"^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?"
r"(Z|[+-]\d{2}:\d{2})?)?\d*$")
DEPRECATED_TAGS = ("priority", "changefreq")
def fetch(url, method="GET"):
req = urllib.request.Request(url, method=method, headers={"User-Agent": UA})
try:
resp = urllib.request.urlopen(req, timeout=TIMEOUT)
data = resp.read()
return resp.status, resp.geturl(), data, resp.headers
except urllib.error.HTTPError as e:
return e.code, url, b"", e.headers
except Exception as e:
return "ERR:" + str(e)[:100], url, b"", {}
def local(tag):
return tag.split("}")[-1]
def status_of(url):
"""Work out a target URL's status; fall back to ranged GET when the
server refuses HEAD (405/403/501) since sitemap targets often use WAFs."""
status, _, _, _ = fetch(url, method="HEAD")
if isinstance(status, int) and status in (405, 403, 501, 400, 404):
if status == 404:
return 404
req = urllib.request.Request(url, headers={
"User-Agent": UA, "Range": "bytes=0-99"})
try:
resp = urllib.request.urlopen(req, timeout=TIMEOUT)
return resp.status
except urllib.error.HTTPError as e:
return e.code
except Exception:
return status
return status
def parse_robots(base):
"""Return declared sitemap URLs from robots.txt (bounded: one host only)."""
url = urllib.parse.urljoin(base, "/robots.txt")
status, _, data, _ = fetch(url)
if status != 200 or not data:
return []
out = []
for line in data.decode("utf-8", "replace").splitlines():
m = re.match(r"^\s*Sitemap:\s*(\S+)", line, re.I)
if m:
out.append(m.group(1))
return out
def probe_common(base):
out = []
for p in COMMON_PATHS:
url = urllib.parse.urljoin(base, p)
status, _, data, _ = fetch(url)
if status == 200:
# sitemaps return application/xml or text/xml; also accept .xml
first = data[:256].decode("utf-8", "replace")
if "<urlset" in first or "<sitemapindex" in first or \
"<urlset" in first.lower():
out.append(url)
return out
def analyze_sitemap(url, depth=0):
info = {"url": url, "depth": depth, "children": []}
status, final, data, headers = fetch(url)
info["status"] = status
info["final_url"] = final
if status not in (200, "200"):
info["error"] = "fetch failed"
return info
info["size_bytes"] = len(data)
info["size_mb"] = round(len(data) / 1024 / 1024, 2)
try:
root = ET.fromstring(data.decode("utf-8", "replace"))
except ET.ParseError as e:
info["ok_xml"] = False
info["xml_error"] = str(e)[:160]
return info
info["ok_xml"] = True
info["root_tag"] = local(root.tag)
locs, lastmods, dcounts = [], [], {t: 0 for t in DEPRECATED_TAGS}
for el in root.iter():
t = local(el.tag)
if t == "loc":
locs.append((el.text or "").strip())
elif t == "lastmod":
lastmods.append((el.text or "").strip())
elif t in DEPRECATED_TAGS:
dcounts[t] += 1
info["loc_count"] = len(locs)
info["lastmod_count"] = len(lastmods)
info["deprecated"] = dcounts
info["url_count"] = len(locs)
valid_lm = sum(1 for x in lastmods if DATETIME_RE.match(x))
info["lastmod_valid"] = valid_lm
info["lastmod_invalid"] = len(lastmods) - valid_lm
uniq = [x for x in lastmods if x]
info["lastmod_sample"] = uniq[:5]
info["lastmod_distinct"] = len(set(uniq))
if info["root_tag"] == "sitemapindex":
info["is_index"] = True
info["child_count"] = sum(1 for el in root.iter() if local(el.tag) == "sitemap")
for child in locs[:50]:
info["children"].append({"url": child, "checked": False})
else:
info["is_index"] = False
# sample target URL statuses
sample = []
non200 = redirected = missing = 0
for loc in locs[:SAMPLE]:
s = status_of(loc)
sample.append({"loc": loc, "status": s})
try:
code = int(s)
except (TypeError, ValueError):
code = 0
if code and 300 <= code < 400:
redirected += 1
elif code == 404:
missing += 1
elif code == 200:
pass
else:
non200 += 1
info["sample_http"] = sample
info["sample_ok"] = sum(1 for x in sample
if isinstance(x["status"], int) and x["status"] == 200)
info["sample_redirected"] = redirected
info["sample_missing"] = missing
info["sample_other"] = non200
return info
def main():
url = sys.argv[1] if len(sys.argv) > 1 else ""
if not url:
print("usage: python3 sitemap_check.py <url> [--json]")
sys.exit(1)
want_json = "--json" in sys.argv
if not url.startswith("http"):
url = "https://" + url
base = url
m = re.match(r"^(https?://[^/]+)/", url)
if m:
base = m.group(1) + "/"
declared = parse_robots(base)
found = declared or probe_common(base)
result = {"base": base, "declared": declared, "found": found,
"sitemaps": []}
if declared:
for s in declared:
result["sitemaps"].append(analyze_sitemap(s))
elif found:
for s in found[:5]:
result["sitemaps"].append(analyze_sitemap(s))
else:
result["sitemaps"] = []
if want_json:
print(json.dumps(result, indent=2, default=str))
return
print("sitemap_check %s" % result["base"])
print(" declared in robots.txt: %d discovered: %d" % (
len(declared), len(found)))
if not result["sitemaps"]:
print(" no sitemap found (checked robots.txt + common paths)")
return
for sm in result["sitemaps"]:
print(" sitemap %s status %s" % (sm["url"], sm["status"]))
if sm.get("error"):
print(" error: %s" % sm["error"])
continue
print(" xml ok: %s tag: %s urls: %d size: %sMB" % (
sm.get("ok_xml"), sm.get("root_tag"), sm.get("url_count"),
sm.get("size_mb")))
if sm.get("lastmod_count"):
print(" lastmod: %d present, %d valid, %d invalid, distinct: %d" % (
sm["lastmod_count"], sm["lastmod_valid"], sm["lastmod_invalid"],
sm["lastmod_distinct"]))
if any(sm.get("deprecated", {}).values()):
print(" deprecated tags: %s" % sm["deprecated"])
if sm.get("is_index"):
print(" index file with %d child sitemaps (sub-check the top ones)" % sm["child_count"])
else:
s = sm["sample_http"]
print(" sample %d URLs: %d ok, %d redirected, %d missing, %d other" % (
len(s), sm.get("sample_ok", 0), sm.get("sample_redirected", 0),
sm.get("sample_missing", 0), sm.get("sample_other", 0)))
if sm.get("url_count", 0) > MAX_URL_COUNT:
print(" CRITICAL: %d URLs > 50,000 limit" % sm["url_count"])
if sm.get("size_mb", 0) > 50:
print(" CRITICAL: %sMB > 50MB uncompressed limit" % sm["size_mb"])
if sm["status"] != 200:
print(" not 200: report as broken sitemap reference")
if __name__ == "__main__":
main()Install it in three commands
mkdir -p ~/.codex/skills/codex-seo-sitemap/scripts
# save the two files above at:
# ~/.codex/skills/codex-seo-sitemap/SKILL.md
# ~/.codex/skills/codex-seo-sitemap/scripts/sitemap_check.py
python3 ~/.codex/skills/codex-seo-sitemap/scripts/sitemap_check.py https://example.comThe last command is your sanity check. You want to see declared in robots.txt: 0 or a sitemap with a status line. If the site has no sitemap at all, you will see the no sitemap found line; that - not an error - is the honest result.
Run it
python3 ~/.codex/skills/codex-seo-sitemap/scripts/sitemap_check.py https://yoursite.comA real check
Here is the checker against https://auspia.ai, the site this article is published on. Two sitemaps are declared in robots.txt, and both check out in different, interesting ways:
sitemap_check https://auspia.ai
declared in robots.txt: 2 discovered: 2
sitemap https://auspia.ai/sitemap.xml status 200
xml ok: True tag: urlset urls: 966 size: 0.16MB
lastmod: 966 present, 966 valid, 0 invalid, distinct: 1
deprecated tags: {'priority': 966, 'changefreq': 966}
sample 20 URLs: 20 ok, 0 redirected, 0 missing, 0 other
sitemap https://auspia.ai/blog/blog-index.xml status 200
xml ok: True tag: sitemapindex urls: 3 size: 0.0MB
index file with 3 child sitemaps (sub-check the top ones)Three findings, all worth acting on:
- lastmod distinct: 1. All 966 URLs carry the same lastmod. That tells
you this sitemap is generated with a fixed date (the generation date, almost certainly), which makes the whole lastmod signal void for the crawler. Every entry is also valid W3C Datetime, so nothing throws, but this is the exact "uniform and therefore unreliable" pattern from the reference table. Per-URL lastmod is a template change: stamp the article's publish date, not the date the sitemap was regenerated.
- Deprecated tags: 966 priority + 966 changefreq. The generator is
emitting two tags Google has ignored for years. Harmless, but it is noise, and if you are going to touch the generator anyway, removing them costs nothing and keeps the file honest.
- The index needs recursion.
/sitemap.xmlpasses, so does the
blog-index structure, but the three child sitemaps it points at are each unvalidated. The next command is the same checker pointed at each child.
For contrast, here is a site with no sitemap at all (felo.ai, in this case the checker finds one legitimately absent, not an error):
sitemap_check https://felo.ai
declared in robots.txt: 0 discovered: 0
no sitemap found (checked robots.txt + common paths)If you see this line on a site you expected to have a sitemap, the next step is the generation path, not the checker.
Reading the flags
Flag | Meaning | Next step |
|---|---|---|
| parse error: unescaped | regenerate, check template escaping |
| over the per-file cap | split with sitemap index |
| over the per-file size cap | split with sitemap index |
| boilerplate date | stamp per-URL real lastmod |
| priority / changefreq present | remove (ignored by Google) |
| URLs return 404 | remove stale entries |
| stale URLs in sitemap | update to final URLs |
| children unchecked | run same check per child |
The generation path
The skill's second mode answers the same question from the other side: you have no sitemap and want to emit one. The rules to keep in view:
- Ask the business type first, or auto-detect from the site structure.
- Quality gates for location pages: warning at 30+ location pages (require
60%+ unique content each), hard stop at 50+ until justified.
- Generate plain XML first; split at 50,000 URLs or 50MB, and wrap the parts
in a sitemap index.
- Safe at scale: integration pages with real docs, template/tool pages,
glossary pages with 200+ word definitions, product pages with unique specs, user-profile pages backed by UGC.
- Risky at scale: city-name-swapped location pages, "best X for Y" pages
without industry-specific value, "competitor alternative" pages without comparison data, AI-generated pages without human review.
- Ship STRUCTURE.md with the sitemap so the architecture is documented, not
just the XML.
Failure table
Scenario | What happens | What to do |
|---|---|---|
| nothing declared or discoverable | run the generation path |
Sitemap fetch fails ( | TLS/network issue | try http, check VPN/proxy |
XML parse error | generator output malformed | fix template escaping, re-export |
HEAD gives 403/405 on every target | WAF blocks HEAD; sizes came through | result uses ranged GET fallback; trust statuses, check sizes manually |
Index found | children not checked by design | run the checker per child sitemap |
| this is not a page check; sitemaps only | give the checker the site root, not a blog URL |
Install this skill by pasting to Codex
Copy everything between the two four-tilde lines in the SKILL.md section of this article into a message to Codex (or just select the whole section). Paste
Read this message. Create the skill below at
~/.codex/skills/codex-seo-sitemap/ by following the two code blocks in it:
1. The markdown block is SKILL.md.
2. The python block is scripts/sitemap_check.py.
Then run python3 ~/.codex/skills/codex-seo-sitemap/scripts/sitemap_check.py
https://example.com as a self-test and report the output (or the error) back
to me, plus what you would check first on my own site.
Do not run any other commands. Do not modify any other files.Codex will create the folder, write the two files, run the self-test, and report back. If the output says no sitemap found for example.com, that is the checker working, not a failure.
FAQ
Is the sitemap still monitored? Yes. Google still documents and uses sitemaps; the limits and the ignored tags (priority, changefreq) are what have shifted. This skill follows the published rules.
Why check lastmod at all? Because a sitemap with honest per-URL lastmod values is a real crawl-priority signal, and a sitemap with one uniform date is not - Google ignores lastmod it cannot verify as accurate. The skill flags the uniform case as the "all identical" pattern rather than silently accepting it.
My sitemap is generated by my CMS. Why should I care? The same reasons, but the fix changes: the generator template is the thing to change, not the XML by hand. The checker tells you which template knob is off.
Can this check my own site property and competitors'? Both, with the caveat that a competitor site's sitemap is public. Filename paths, robots.txt and sitemap URLs are all public; use the same checker on anything you can fetch. Just don't confuse a competitor's 404 sitemap with your problem - their robots.txt is their truth.
What about sitemaps in subdirectories? The checker normalizes to the registered domain root and checks the usual paths there. If your sitemap is at a non-standard path like /api/sitemap/latest.xml, point the checker at that URL directly, or update robots.txt first.
Author: Daniel Cross, Programmatic SEO Architect for 50k+ Page Systems at Auspia. Daniel writes about scaled page systems, index management, and templates that hold up at volume.
Based on the open-source claude-seo project by AgriciDaniel (MIT license, GitHub). This series adapts it for the Codex runtime: rewritten methodology, a Codex-native SKILL.md, and new evidence collectors written from scratch for this series.



