# Content Decay Refresh

> Weekly reusable-session content-refresh loop for {{target_repo}}. Reads Google Search Console for {{site_url}} to find pages under {{content_path}} losing impressions and clicks, rotates coverage using a durable ledger, refreshes copy/stats/internal links on an isolated branch, and opens a PR only — never publishing or merging.

- Skill: `kortix-ai/content-decay-refresh` (Agent Skill)
- Install (CLI): `npx skillmds@latest add kortix-ai/content-decay-refresh`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kortix-ai/content-decay-refresh/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: kortix-ai (https://skillmd.com/u/kortix-ai)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/kortix-ai/content-decay-refresh

---


<skill name="content-decay-refresh">

<overview>
Keep marketing and blog content in `{{target_repo}}` from decaying silently. A
weekly cron re-prompts a persistent session, pulls the pages losing the most
impressions and clicks from Google Search Console, cross-references them
against `{{content_path}}`, and rotates through the decaying set so the same
few high-traffic pages don't get all the attention while the long tail rots.
Each run refreshes a small batch — stale copy, aged-out stats, internal links
that point at renamed or retired pages — and opens one PR. The agent never
publishes and never merges.

Proactive and schedule-driven; the ledger is what makes the rotation fair
across runs.
</overview>

<when-to-load>
- The weekly cron fires the content-refresh sweep.
- A human asks the agent to check for decaying content or run a refresh pass.
- A prior run's PR needs a follow-up before the next batch starts.
</when-to-load>

<workflow>

## Step 0 — Orient and resume

```sh
# Read the durable ledger first — which pages were refreshed, when, and why.
cat .kortix/memory/content-refresh-log.md 2>/dev/null || echo "(no ledger yet)"

# Check any open refresh PR from a prior run.
gh pr list --repo {{target_repo}} --state open \
  --search 'in:title "content refresh" OR label:content-refresh' \
  --json number,title,headRefName,statusCheckRollup,url
```

If a prior PR is still open and unreviewed, note it and don't duplicate its
pages in this run's batch.

## Step 1 — Freshen the repo

```sh
if [ -d /workspace/repo/.git ]; then
  cd /workspace/repo && git fetch origin && git checkout main && git reset --hard origin/main
else
  git clone --filter=blob:none https://github.com/{{target_repo}}.git /workspace/repo
  cd /workspace/repo
fi
```

Never start a refresh on a stale base.

## Step 2 — Pull decay signals from Search Console

Through the scoped Search Console connector, pull impressions and clicks per
URL for `{{site_url}}` over the trailing 28 days versus the 28 days before
that, restricted to pages under `{{content_path}}`. Compute, per page:

- **Decay** = percentage drop in impressions and clicks between the two
  windows.
- **Weight** = decay severity × the page's absolute traffic in the trailing
  window (a big page with a small decline still outranks a tiny page in free
  fall).

Rank pages by weight. This is the raw candidate list before rotation.

## Step 3 — Rotate using the ledger

Read each candidate's last-refreshed date from the ledger (never-refreshed
pages count as oldest). Re-rank candidates by combining decay weight with
staleness of coverage: a page refreshed in the last two weeks drops several
places even if it's still the sharpest decliner; a page that's never been
touched moves up. Take the top 3–5 pages as this week's batch — enough to make
progress, small enough to keep the PR reviewable.

## Step 4 — Isolated refresh branch

```sh
cd /workspace/repo
BRANCH="content-refresh/$(date +%Y-W%V)"
git checkout -b "$BRANCH" origin/main
```

One branch per weekly run.

## Step 5 — Refresh each page in the batch

For each page:

1. Open the source file under `{{content_path}}`.
2. **Copy** — rewrite sentences that read as dated (old product names, retired
   features, superseded positioning) to match current reality.
3. **Stats** — find numbers, dates, and claims that have aged out (customer
   counts, benchmark years, "as of" figures) and update them from the current
   source of truth; if no current figure is available, soften the claim rather
   than leave a stale one.
4. **Internal links** — grep the repo for the page's outbound internal links;
   repoint any that reference a renamed, merged, or retired page to its
   current location.
5. Leave everything else on the page untouched — this is a refresh, not a
   rewrite.

```sh
cd /workspace/repo
grep -rn "old-slug-or-renamed-page" {{content_path}} 2>/dev/null
```

## Step 6 — Commit

```sh
cd /workspace/repo
git add {{content_path}}
git commit -m "content: refresh $(date +%Y-W%V) batch

$(for each page: echo "- <page>: <decay signal> -> <what changed>")"
```

## Step 7 — Open the PR (only, never publish)

```sh
cd /workspace/repo
git push origin "$BRANCH"
gh pr create --repo {{target_repo}} --base main --head "$BRANCH" \
  --title "content: refresh $(date +%Y-W%V) batch" \
  --label content-refresh \
  --body "Generated by the content-refresh agent. Pages selected from Search
Console decay signals for {{site_url}}, rotated against pages already covered
recently. Per page: the decay signal that triggered inclusion and what was
refreshed (copy / stats / internal links). This PR is never merged by the
agent — a human reviews and decides what ships."
```

## Step 8 — Update the ledger

Append a dated entry to `.kortix/memory/content-refresh-log.md` (see
`<ledger-format>`).

</workflow>

<ledger-format>
Lives at `.kortix/memory/content-refresh-log.md`. Every run appends a dated
entry with: run timestamp, branch, PR link (or "not opened — nothing stale
enough"), a **Refreshed pages** table (page path / decay signal / what
changed), the **full candidate list** considered that week with their decay
weight and rotation rank (so future runs can see what was passed over and
why), and **blockers for next run**.
</ledger-format>

<guardrails>
- **PR only, never publish.** The agent opens a PR against an isolated branch
  and stops. It never pushes to the live content branch and never merges its
  own work.
- **Content is the only write surface.** Only files under `{{content_path}}`
  are edited. Application code, config, and anything outside that path is
  read-only to this agent.
- **Read-only Search Console.** The connector only reads impressions and
  clicks; the agent cannot change anything in Search Console itself.
- **Sandbox isolation.** Refreshes are made and reviewed on an isolated branch
  in the session sandbox. Nothing reaches the repo until `git push` runs.
- **Secrets scoped.** The `GH_TOKEN` used by the `gh` CLI is injected at
  runtime, never written to disk or logged.
- **One PR per run, one run per week.** If a prior PR is still open, don't
  duplicate its pages — extend it or wait for review.
- **Rotation over repetition.** Never let the same small set of pages absorb
  every run; the ledger's staleness weighting is what enforces this.
</guardrails>

</skill>

