skills-ws
Version:
87 agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.
82 lines (65 loc) • 3.73 kB
Markdown
## Contents
- 8. Content Audit & Maintenance
- Build the inventory (real tools, real steps)
- Runnable: join a crawl export with GSC clicks to triage
- Maintenance cadence
## 8. Content Audit & Maintenance
Audit to decide, per URL: **keep / optimize / consolidate / rewrite / prune**. Pull inventory + metrics, score with §4, act by band.
### Build the inventory (real tools, real steps)
1. **URL list** — `sitemap.xml`, or crawl with Screaming Frog (free up to 500 URLs) / Sitebulb to also capture titles, word count, status codes, indexability.
2. **Performance** — Search Console "Pages" (clicks, impressions, position) and GA4 (engaged sessions, conversions). Pull a 16-month GSC window to see trend, not a snapshot.
3. **Indexation** — flag URLs not indexed (GSC URL Inspection / Pages report); decide index vs no-index vs redirect.
4. **Score** — apply the §4 rubric; sort by band.
### Runnable: join a crawl export with GSC clicks to triage
```python
#!/usr/bin/env python3
"""Triage a content inventory: crawl export (Screaming Frog) + GSC pages export.
Outputs a keep/optimize/consolidate/prune recommendation per URL.
- crawl.csv : Screaming Frog 'Internal > HTML' export (cols: Address, Word Count, Indexability)
- gsc.csv : Search Console 'Pages' export (cols: Top pages/Page, Clicks, Impressions, Position)
"""
import csv, sys
def load(p):
with open(p, newline="", encoding="utf-8-sig") as f:
return list(csv.DictReader(f))
def num(x, d=0):
try: return float(str(x).replace(",", ""))
except (TypeError, ValueError): return d
crawl = {r["Address"].rstrip("/"): r for r in load(sys.argv[1])}
gsc = {}
for r in load(sys.argv[2]):
url = (r.get("Page") or r.get("Top pages") or "").rstrip("/")
if url:
gsc[url] = r
for url, c in crawl.items():
g = gsc.get(url, {})
clicks = num(g.get("Clicks"))
impr = num(g.get("Impressions"))
pos = num(g.get("Position"), 999)
words = num(c.get("Word Count"))
# Screaming Frog emits exactly "Indexable" / "Non-Indexable" — match exactly,
# NOT substring (`"indexable" in "non-indexable"` is True and would mis-pass).
indexable = c.get("Indexability", "").strip().lower() == "indexable"
if not indexable:
rec = "review-indexation"
elif clicks == 0 and impr < 50:
rec = "PRUNE/redirect (no demand, no clicks)"
elif impr >= 100 and pos > 10:
rec = "OPTIMIZE (demand exists, ranking page 2+)" # quick win
elif clicks > 0 and words < 600:
rec = "EXPAND/REWRITE (thin but converting)"
elif clicks > 0:
rec = "KEEP (refresh on cadence)"
else:
rec = "CONSOLIDATE candidate (low signal)"
print(f"{rec:42} clk={clicks:<5.0f} impr={impr:<6.0f} pos={pos:<5.0f} w={words:<5.0f} {url}")
```
> "Quick wins" = URLs with impressions but position 11-20: small edits (better title/intro answer, internal links, refreshed facts) often move them onto page 1. The `search-console` skill's quick-win detection automates surfacing these.
### Maintenance cadence
| Cadence | Tasks |
|---|---|
| **Weekly** | Watch new-post performance; ship scheduled distribution/repurposing; fix broken links on recent posts; reply to comments |
| **Monthly** | Review GSC/GA4 trends; promote quick-win URLs (pos 11-20); refresh CTAs/offers; re-run §3 query-class tests for money topics |
| **Quarterly** | Full audit (score every URL with §4); refresh every pillar (dates, stats, screenshots, last-reviewed); competitor + AI-citation gap pass (§6); consolidate/prune the `<40` band |
| **Annually** | Full inventory recategorization; cluster restructure; channel-relevance review (kill dead channels); content ROI/attribution review |
---