/refresh — Existing-post refresher
Upgrades an existing published blog post in place. Often higher-leverage than writing a new post — Google rewards freshness on existing-ranking content.
Inputs
Required:
- Site — must match a folder under
sites/[name]/.
- Post URL — the live URL of the post to refresh.
Optional:
- Refresh scope —
light (stats + internal links + dateModified only), medium (light + Tier 1 fixes + image upgrades), heavy (medium + section rewrites + new SERP analysis). Default: medium.
- Image source — overrides default (only if scope includes image upgrades).
Workflow
Step 0 — Cron-mode detection (if invoked with --cron)
If the invocation contains --cron, this skill runs in cron mode (no user available). Required behavior:
Idempotency check: see this skill's "Routine versioning + idempotency contract" section. If today's output already exists, exit cleanly with exit: "idempotent-skip". Write one line to the audit log via scripts/lib/audit-log.mjs appendRun({ exit: "idempotent-skip", ... }).
Escalation contract: any decision that would normally prompt the user (missing required file, voice anti-AI failure, schema validation failure, keyword cannibalization, etc.) MUST be escalated by writing an item to sites/{site}/_inbox/ via scripts/lib/cron-mode.mjs writeInboxItem(...). After writing, exit cleanly with exit: "escalated". Do NOT use AskUserQuestion in cron mode.
Defaults: when a choice would normally be asked, default to site-info.md / goals.md values. If both are silent on the required choice, escalate per (2).
Audit log: ALWAYS write one line to the audit log on exit — success (shipped), escalation (escalated), idempotent skip (idempotent-skip), or failure (failed).
Backoff: at the start of every cron-mode run, call checkBackoff({ routine }) from scripts/lib/audit-log.mjs. If true, the routine has hit the 3-strike threshold — write _inbox/routine-disabled-{name}.md, run scripts/sync-schedules.mjs --pause-routine={name}, and exit.
Step 1 — Read context (Rule 1)
Same as /blog Step 1.
Step 2 — Fetch the existing post
- For
repo-commit sites: read the source file from the site's repo (markdown/MDX).
- For other publishing methods: scrape the live URL → extract title, meta, body, schema, image references.
Identify:
- Current primary keyword (from URL slug + title)
- Current cluster (inferred from H2s + body)
- Word count, image count, internal/external links, schema present
- Last published date + last modified date (if visible)
Step 3 — Run audit on the post
Score current state:
- Tier 1 status (which of the 16 pass / fail)
- Schema status (validates? complete?)
- Voice status (anti-AI check on existing copy)
- Stats freshness (any numbers that look stale or rounded against
stats.md?)
- Internal links (do they still point to live URLs? are there newer related posts to link to?)
- External links (still resolve? still authoritative?)
- Length vs. current SERP top-3 median (re-run SERP analysis fresh)
Present scorecard to user.
Step 4 — Plan refresh + approval (Rule 2)
Based on scope + audit:
light → list of stat updates + internal link updates + dateModified bump.
medium → light + specific Tier 1 fixes + image upgrades for any failing 6.4 (width/height) or 6.3 (WebP under 200KB).
heavy → medium + rewritten sections (specifically: opening if it doesn't pass voice, FAQ if missing or schema-mismatched, any H2 sections that the new SERP analysis suggests are missing or off-target).
Present plan. Wait for approval.
For multilingual sites, the plan also lists which language counterparts are being refreshed. Default: refresh ALL language versions of the page in lockstep — translation drift is real and the toolkit prevents it.
Step 4.5 — Resolve language fan-out (multilingual sites only)
Read site-info.md Languages section.
Multilingual: false → skip; continue to Step 5 as a single-language refresh.
Multilingual: true:
- For the post being refreshed, locate every language counterpart (use the URL pattern table from
site-info.md). If a counterpart is missing, FLAG it as a coverage gap and refuse to refresh until either (a) the missing version is created, or (b) user explicitly confirms partial-coverage opt-in.
- Apply the SAME refresh scope to every language version. If only the EN version had a stat-staleness issue, still bump the ES version's
dateModified and re-resolve its voice/anti-AI checks against the current voice.[lang].md rules.
- Drift detection: compare structural shape (H2 count, FAQ count, schema entities) between language versions. If they've drifted (one has 6 FAQs, the other has 4), surface as a refresh item.
- Hreflang verification: confirm reciprocal hreflang link cluster is still correct after the refresh. The slug is unchanged (per Rule preservation), so hreflang URLs should remain stable — but verify.
The refresh produces N updated drafts (one per language), each in its own _drafts/[slug]/[lang]/ subfolder. Ship them together.
Step 5 — Apply refresh
Critical preservation rules:
- ✅ Same URL slug — never change.
- ✅ Same canonical — preserved.
- ✅ Same primary keyword — never swap (cannibalization risk).
- ✅
datePublished unchanged; dateModified set to today.
- ✅ Author byline preserved unless explicitly asked to change.
Apply changes per scope.
AI-search optimization retrofit (apply during refresh — references/voice.md Universal AI-search optimization rules):
- If the post predates the AI-search rules (most posts shipped before 2026-05-17), assess and apply:
- Convert 3-5 H2/H3s to explicit question form where natural (Q+A density)
- Split paragraphs longer than 3 sentences (citation-friendly chunking)
- Remove "as mentioned above" / "see below" references — restate context inline (self-contained facts)
- Add inline source links to any unsourced statistics; drop unsourceable numbers (verifiable claims)
- These are upgrade, not preservation — explicitly tag in the refresh plan (Step 4) so the user reviews before applying. Do NOT silently rewrite a post's voice; the original voice stays.
Step 6 — Validate (Rule 4)
Same as /blog Step 8, with one addition:
- ✅ No URL change — verify slug unchanged.
- ✅ No primary keyword swap — verify primary keyword unchanged.
Step 7 — Ship per publishing method
Same as /blog Step 9, but as an UPDATE to existing content:
repo-commit → commit to refresh/[slug] branch, open PR (or update directly).
cms-paste → output is the updated body + meta + image replacements; user updates the existing CMS entry.
lovable-prompt → output is a Lovable-ready prompt to update the existing page.
headless-api → updates the existing CMS entry via API (preserves the entry ID, updates fields).
Step 8 — Update tracker
In used-keywords.md, don't append a new row — instead, find the existing row for that primary keyword and append a "Last refreshed: YYYY-MM-DD" note. If the row doesn't exist (e.g., the post pre-dates the toolkit), add it with both dates.
Step 9 — Report
Print:
- What changed (light / medium / heavy summary)
- Specific Tier 1 fixes applied
- New internal links added
- Stats refreshed (with old → new values)
- Lighthouse delta if checked
- Reminder: request indexing in GSC after the live update propagates.
Routine versioning + idempotency contract
This skill participates in cruise-control via --cron mode. When invoked by cron:
Stamp routine_version in every output produced (the notes.md audit entry header, _inbox/ item frontmatter, refreshed-draft folder metadata, audit log line via scripts/lib/audit-log.mjs). Current routine_version: 1.0.
Idempotency: this skill MUST be safe to run twice on the same target URL on the same day without producing duplicate work. Implementation: check whether notes.md already records a refresh of this URL today; if so, exit cleanly with exit: "idempotent-skip".
Reference: docs/specs/2026-05-16-agents-cruise-control-design.md § Operational hardening O3.
Refusal conditions
- Post URL doesn't resolve.
- Refresh would change the primary keyword (refer user to
/blog for new content instead).
- Refresh would change the URL slug (would create a redirect chain — user must explicitly opt in via "I accept the redirect" flag).
1---2name: refresh3description: Upgrade an existing blog post — re-run SERP analysis against the current top 3, refresh stats from `stats.md`, fix any Tier 1 on-page SEO gaps, update internal links to recent content, refresh the `dateModified`. Preserves the original URL and primary keyword (no cannibalization). Use when the user types `/refresh` or asks to update an existing post.4---56# `/refresh` — Existing-post refresher78Upgrades an existing published blog post in place. Often higher-leverage than writing a new post — Google rewards freshness on existing-ranking content.910## Inputs1112Required:13- **Site** — must match a folder under `sites/[name]/`.14- **Post URL** — the live URL of the post to refresh.1516Optional:17- **Refresh scope** — `light` (stats + internal links + `dateModified` only), `medium` (light + Tier 1 fixes + image upgrades), `heavy` (medium + section rewrites + new SERP analysis). Default: `medium`.18- **Image source** — overrides default (only if scope includes image upgrades).1920## Workflow2122### Step 0 — Cron-mode detection (if invoked with `--cron`)2324If the invocation contains `--cron`, this skill runs in cron mode (no user available). Required behavior:25261. **Idempotency check:** see this skill's "Routine versioning + idempotency contract" section. If today's output already exists, exit cleanly with `exit: "idempotent-skip"`. Write one line to the audit log via `scripts/lib/audit-log.mjs` `appendRun({ exit: "idempotent-skip", ... })`.27282. **Escalation contract:** any decision that would normally prompt the user (missing required file, voice anti-AI failure, schema validation failure, keyword cannibalization, etc.) MUST be escalated by writing an item to `sites/{site}/_inbox/` via `scripts/lib/cron-mode.mjs` `writeInboxItem(...)`. After writing, exit cleanly with `exit: "escalated"`. Do NOT use `AskUserQuestion` in cron mode.29303. **Defaults:** when a choice would normally be asked, default to `site-info.md` / `goals.md` values. If both are silent on the required choice, escalate per (2).31324. **Audit log:** ALWAYS write one line to the audit log on exit — success (`shipped`), escalation (`escalated`), idempotent skip (`idempotent-skip`), or failure (`failed`).33345. **Backoff:** at the start of every cron-mode run, call `checkBackoff({ routine })` from `scripts/lib/audit-log.mjs`. If true, the routine has hit the 3-strike threshold — write `_inbox/routine-disabled-{name}.md`, run `scripts/sync-schedules.mjs --pause-routine={name}`, and exit.3536### Step 1 — Read context (Rule 1)3738Same as `/blog` Step 1.3940### Step 2 — Fetch the existing post4142- For `repo-commit` sites: read the source file from the site's repo (markdown/MDX).43- For other publishing methods: scrape the live URL → extract title, meta, body, schema, image references.4445Identify:46- Current primary keyword (from URL slug + title)47- Current cluster (inferred from H2s + body)48- Word count, image count, internal/external links, schema present49- Last published date + last modified date (if visible)5051### Step 3 — Run audit on the post5253Score current state:54- Tier 1 status (which of the 16 pass / fail)55- Schema status (validates? complete?)56- Voice status (anti-AI check on existing copy)57- Stats freshness (any numbers that look stale or rounded against `stats.md`?)58- Internal links (do they still point to live URLs? are there newer related posts to link to?)59- External links (still resolve? still authoritative?)60- Length vs. current SERP top-3 median (re-run SERP analysis fresh)6162Present scorecard to user.6364### Step 4 — Plan refresh + approval (Rule 2)6566Based on scope + audit:67- `light` → list of stat updates + internal link updates + `dateModified` bump.68- `medium` → light + specific Tier 1 fixes + image upgrades for any failing 6.4 (width/height) or 6.3 (WebP under 200KB).69- `heavy` → medium + rewritten sections (specifically: opening if it doesn't pass voice, FAQ if missing or schema-mismatched, any H2 sections that the new SERP analysis suggests are missing or off-target).7071Present plan. Wait for approval.7273For multilingual sites, the plan also lists which language counterparts are being refreshed. Default: refresh ALL language versions of the page in lockstep — translation drift is real and the toolkit prevents it.7475### Step 4.5 — Resolve language fan-out (multilingual sites only)7677Read `site-info.md` Languages section.7879- **`Multilingual: false`** → skip; continue to Step 5 as a single-language refresh.80- **`Multilingual: true`**:81 1. For the post being refreshed, locate every language counterpart (use the URL pattern table from `site-info.md`). If a counterpart is missing, FLAG it as a coverage gap and refuse to refresh until either (a) the missing version is created, or (b) user explicitly confirms partial-coverage opt-in.82 2. Apply the SAME refresh scope to every language version. If only the EN version had a stat-staleness issue, still bump the ES version's `dateModified` and re-resolve its voice/anti-AI checks against the current `voice.[lang].md` rules.83 3. Drift detection: compare structural shape (H2 count, FAQ count, schema entities) between language versions. If they've drifted (one has 6 FAQs, the other has 4), surface as a refresh item.84 4. Hreflang verification: confirm reciprocal hreflang link cluster is still correct after the refresh. The slug is unchanged (per Rule preservation), so hreflang URLs should remain stable — but verify.8586The refresh produces N updated drafts (one per language), each in its own `_drafts/[slug]/[lang]/` subfolder. Ship them together.8788### Step 5 — Apply refresh8990Critical preservation rules:91- ✅ **Same URL slug** — never change.92- ✅ **Same canonical** — preserved.93- ✅ **Same primary keyword** — never swap (cannibalization risk).94- ✅ **`datePublished` unchanged**; **`dateModified` set to today**.95- ✅ **Author byline preserved** unless explicitly asked to change.9697Apply changes per scope.9899**AI-search optimization retrofit (apply during refresh — `references/voice.md` Universal AI-search optimization rules):**100- If the post predates the AI-search rules (most posts shipped before 2026-05-17), assess and apply:101 - Convert 3-5 H2/H3s to explicit question form where natural (Q+A density)102 - Split paragraphs longer than 3 sentences (citation-friendly chunking)103 - Remove "as mentioned above" / "see below" references — restate context inline (self-contained facts)104 - Add inline source links to any unsourced statistics; drop unsourceable numbers (verifiable claims)105- These are **upgrade**, not preservation — explicitly tag in the refresh plan (Step 4) so the user reviews before applying. Do NOT silently rewrite a post's voice; the original voice stays.106107### Step 6 — Validate (Rule 4)108109Same as `/blog` Step 8, with one addition:110- ✅ **No URL change** — verify slug unchanged.111- ✅ **No primary keyword swap** — verify primary keyword unchanged.112113### Step 7 — Ship per publishing method114115Same as `/blog` Step 9, but as an UPDATE to existing content:116- `repo-commit` → commit to `refresh/[slug]` branch, open PR (or update directly).117- `cms-paste` → output is the updated body + meta + image replacements; user updates the existing CMS entry.118- `lovable-prompt` → output is a Lovable-ready prompt to update the existing page.119- `headless-api` → updates the existing CMS entry via API (preserves the entry ID, updates fields).120121### Step 8 — Update tracker122123In `used-keywords.md`, **don't append** a new row — instead, find the existing row for that primary keyword and append a "Last refreshed: YYYY-MM-DD" note. If the row doesn't exist (e.g., the post pre-dates the toolkit), add it with both dates.124125### Step 9 — Report126127Print:128- What changed (light / medium / heavy summary)129- Specific Tier 1 fixes applied130- New internal links added131- Stats refreshed (with old → new values)132- Lighthouse delta if checked133- Reminder: request indexing in GSC after the live update propagates.134135## Routine versioning + idempotency contract136137This skill participates in cruise-control via `--cron` mode. When invoked by cron:1381391. **Stamp `routine_version`** in every output produced (the `notes.md` audit entry header, `_inbox/` item frontmatter, refreshed-draft folder metadata, audit log line via `scripts/lib/audit-log.mjs`). Current `routine_version`: **1.0**.1401412. **Idempotency:** this skill MUST be safe to run twice on the same target URL on the same day without producing duplicate work. Implementation: check whether `notes.md` already records a refresh of this URL today; if so, exit cleanly with `exit: "idempotent-skip"`.142143Reference: `docs/specs/2026-05-16-agents-cruise-control-design.md` § Operational hardening O3.144145## Refusal conditions146147- Post URL doesn't resolve.148- Refresh would change the primary keyword (refer user to `/blog` for new content instead).149- Refresh would change the URL slug (would create a redirect chain — user must explicitly opt in via "I accept the redirect" flag).