live1,247 agents deployed
← All skillsSign up to install

RSS Feed Generator

General↓ 0 installsUpdated 105d ago
Curatedaaronjmars

Generate an Atom XML feed from articles, validate it, and notify only when it actually changes

SKILL.md preview

---
name: RSS Feed Generator
description: Generate an Atom XML feed from articles, validate it, and notify only when it actually changes
var: ""
tags: [content]
---
<!-- autoresearch: variation B — sharper output via change detection, XML validation, and what-changed notification -->

> **${var}** — Repo slug override (`owner/repo`) for feed URLs. If empty, the script auto-detects from the git remote. Pass through unchanged; do not invent a value.

Generate a valid Atom XML feed from all markdown articles in `articles/`, validate it, and notify only when the feed content actually changed. Unchanged runs are a silent no-op.

## Steps

### 1. Read context

Read `memory/MEMORY.md`.

### 2. Capture the baseline

Before regenerating, snapshot the current feed so we can tell whether anything changed:

```bash
if [[ -f articles/feed.xml ]]; then
  PREV_HASH="$(sha256sum articles/feed.xml | awk '{print $1}')"
  grep -oP '(?<=<title>)[^<]+' articles/feed.xml | tail -n +2 | sort -u > /tmp/rss-feed-prev-titles.txt
else
  PREV_HASH=""
  : > /tmp/rss-feed-prev-titles.txt
fi
```

The `tail -n +2` skips the feed-level `<title>` so only entry titles remain.

### 3. Regenerate the feed

Run the script. Pass `${var}` as the repo slug override only if it is non-empty:

```bash
if [[ -n "${var}" ]]; then
  bash scripts/generate-feed.sh "${var}"
else
  bash scripts/generate-feed.sh
fi
```

### 4. Validate the XML

Invalid XML breaks every subscriber. Fail loud if the feed is malformed:

```bash
if command -v xmllint >/dev/null 2>&1; then
  xmllint --noout articles/feed.xml || { STATUS="RSS_FEED_ERROR"; VALIDATION_ERR="xmllint failed"; }
else
  # Fallback: verify the root element and a closing tag
  head -2 articles/feed.xml | grep -q '<feed' || { STATUS="RSS_FEED_ERROR"; VALIDATION_ERR="missing <feed> root"; }
  tail -1 articles/feed.xml | grep -q '</feed>' || { STATUS="RSS_FEED_ERROR"; VALIDATION_ERR="missing </feed> close"; }
fi
```

If `STATUS=RSS_FEED_ERROR`, stop, notify with the error, log it, and exit without committing. Do **not** commit a broken feed.

### 5. Detect what changed

Compare new feed against the baseline:

```bash
NEW_HASH="$(sha256sum articles/feed.xml | awk '{print $1}')"
grep -oP '(?<=<title>)[^<]+' articles/feed.xml | tail -n +2 | sort -u > /tmp/rss-feed-new-titles.txt

ADDED="$(comm -13 /tmp/rss-feed-prev-titles.txt /tmp/rss-feed-new-titles.txt)"
REMOVED="$(comm -23 /tmp/rss-feed-prev-titles.txt /tmp/rss-feed-new-titles.txt)"
ENTRY_COUNT="$(wc -l < /tmp/rss-feed-new-titles.txt | tr -d ' ')"
```

Classify the run:
- **RSS_FEED_NO_CHANGE** — `PREV_HASH == NEW_HASH` (byte-identical). Exit silently after logging. No commit, no notify.
- **RSS_FEED_METADATA_ONLY** — hashes differ but `ADDED` and `REMOVED` are both empty (e.g. `<updated>` timestamp drifted). Commit so the feed stays fresh, but **do not notify** — it is not a subscriber-visible change.
- **RSS_FEED_OK** — `ADDED` or `REMOVED` is non-empty. Commit and notify.

### 6. Commit (only if changed)

Skip entirely for `RSS_FEED_NO_CHANGE`. Otherwise build a descriptive commit message:

```bash
if [[ "$STATUS" == "RSS_FEED_OK" ]]; then
  MSG="chore(feed): $(echo "$ADDED" | grep -c .) added, $(echo "$REMOVED" | grep -c .) removed ($ENTRY_COUNT total)"
else
  MSG="chore(feed): metadata refresh ($ENTRY_COUNT entries)"
fi

git add articles/feed.xml
git diff --cached --quiet || git commit -m "$MSG"
git push || true
```

If the push fails because the runner can't push directly, that's fine — the generated file remains in the workspace and a later skill/PR can carry it.

### 7. Notify (only on RSS_FEED_OK)

Silence is the feature. Do not notify on `RSS_FEED_NO_CHANGE` or `RSS_FEED_METADATA_ONLY`. On `RSS_FEED_OK`, build a concise message:

```
*RSS feed updated* — $ENTRY_COUNT entries

New:
- <first added title>
- <second added title>
[Removed: <removed titles> — only include this line if REMOVED is non-empty]

Subscribe: https://raw.githubusercontent.com/${repo}/main/articles/

…