Generate an Atom XML feed from articles, validate it, and notify only when it actually changes
Scanned 9/5/2026
Install to Claude Code
npx -y skills add anajuliabit/aeon --skill rss-feed --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Rss Feed?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anajuliabit-rss-feed)More formats (shields.io, HTML) on the badges page.
---
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/feed.xml
```
Keep it to one paragraph plus the bullet list. Cap at 5 added titles; if there are more, show the first 5 and append `(+N more)`. Send via `./notify "<message>"`.
On `RSS_FEED_ERROR`, notify with:
```
*RSS feed ERROR* — $VALIDATION_ERR
Feed not committed. Investigate scripts/generate-feed.sh or articles/ inputs.
```
### 8. Log
Append to `memory/logs/${today}.md`:
```
### rss-feed
- Status: RSS_FEED_OK | RSS_FEED_METADATA_ONLY | RSS_FEED_NO_CHANGE | RSS_FEED_ERROR
- Entries: $ENTRY_COUNT
- Added: <titles or "none">
- Removed: <titles or "none">
- Prev hash: <first 8 chars>, new hash: <first 8 chars>
```
Always log, even on `RSS_FEED_NO_CHANGE` — the log line is how the operator distinguishes "ran and was idle" from "never ran."
## Sandbox note
This skill is entirely local: it reads/writes files and shells out to `scripts/generate-feed.sh`. No network calls required. If `git push` is blocked by the sandbox, the commit still lands locally and the workflow's post-step commit/push path will handle it.
## Constraints
- Never commit a feed that fails validation.
- Never notify on an unchanged feed — noise destroys the signal.
- Preserve the script interface: `scripts/generate-feed.sh [repo_slug]`. Do not rewrite the generator inline.
- Keep `${var}` semantics aligned with what the script actually accepts (repo slug, not base URL).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!