| name | blog-post-writer |
| description | Write a new blog post for petersouter.xyz. Gathers requirements, researches source material, creates the post with correct Hugo frontmatter and site conventions, and verifies the build. Use when the user wants to write, draft, or start a new blog post. |
| user-invocable | true |
Blog Post Writer for petersouter.xyz
Blog Conventions
- Site URL: https://petersouter.xyz/
- Hugo theme: tranquilpeak
- Post files:
content/post/YYYY/MM/slug.md (flat files, NOT page bundles)
- Image files:
static/images/YYYY/MM/ (separate from content)
- Frontmatter: TOML (
+++ delimiters, NOT YAML ---)
- Permalinks:
/:title/ (title-based, no dates in URLs)
- Language: British English (
en-uk)
Mandatory Execution Flow
Each step must be completed before proceeding to the next. No steps may be skipped.
Step 0: Gather Requirements
Before starting any work, confirm the following with the user:
- Topic: What is the post about?
- Source material (optional): Reference URLs, documents, notes, GitHub repos
- Target keywords (optional): SEO keywords for discoverability
If the user has already provided this information, proceed directly to Step 1.
Step 1: Determine Category, Slug, and File Path
Category Standards
Select a category based on article content. Only use established categories from the blog:
| Category | Applicable Content |
|---|
| Tech | Technical posts, tutorials, tools, programming, DevOps |
| Personal | Personal reflections, life updates, non-technical |
| Conference | Trip reports, event coverage, conference summaries |
| Meta | Posts about the blog itself |
| Blogging | Posts about the writing process |
| Career | Career advice, professional development |
| Cooking | Recipes, food-related |
| Travel | Travel-related content (can be combined with Tech) |
Multiple categories can be combined as an array: categories = ["Meta", "Blogging"]
File Path
- Use the
date command to get the current date
- Generate a slug: lowercase, hyphenated, descriptive (e.g.
getting-started-with-terraform-modules)
- Check for slug collisions:
grep -r '^slug = "your-new-slug"' content/post/ to ensure no existing post uses the same slug
- Post file:
content/post/YYYY/MM/slug.md
- Image directory (if needed):
static/images/YYYY/MM/
Step 1.5: Create the Branch
Blog posts always get their own branch, named feat/blog/<short-slug>. <short-slug> is a shortened, kebab-case version of the post slug chosen in Step 1 — keep it under ~40 characters so the branch name stays readable. Examples: post slug the-moneyball-problem-love-the-movie-loathe-its-legacy → branch feat/blog/moneyball-problem; post slug fosdem-2026-highlights → branch feat/blog/fosdem-2026.
Branch safety checks — before creating the branch:
- Check current branch:
git branch --show-current
- Check for uncommitted changes:
git status --short
- If there are uncommitted changes, stop and warn the user. Don't create a branch with dirty state — ask them to commit or stash first.
- If already on a
feat/blog/* branch, ask the user if they want to add to the current branch or create a new one.
- Check the branch name is unused locally:
git rev-parse --verify feat/blog/<short-slug> should fail.
- Check the branch name is unused on origin:
git ls-remote --exit-code --heads origin feat/blog/<short-slug> should fail. If either exists, pick a more specific slug and re-check.
Create the branch:
git checkout master
git pull origin master
git checkout -b feat/blog/<short-slug>
Step 2: Research Source Material
2.1 Read User-Provided Material
If the user provided reference links, read them thoroughly using WebFetch. Do not guess based on titles alone. For GitHub links, use the gh CLI or access raw content.
2.2 Proactive Research
Always perform the following:
- Use
WebSearch to find the latest developments and authoritative sources on the topic
- Check for official documentation, best practices, or canonical references
- Search for contrasting viewpoints or common misconceptions
- If the topic involves tools/frameworks, find official docs and GitHub repositories
2.3 Check Existing Posts (for Internal Linking)
Run the following to get the blog's existing article list:
find content/post/ -name "*.md" | sort
Record existing posts for use in adding internal links during Step 3. Look for topical overlap with the new post.
Step 3: Create the Post File
Do NOT use hugo new — while the project CLAUDE.md documents hugo new post/... as a general convention, the default archetype generates YAML frontmatter (--- delimiters). All existing blog posts use TOML frontmatter (+++ delimiters), so this skill creates the file directly to ensure consistency. If the archetype is updated to use TOML in the future, this guidance should be revisited.
Frontmatter Template (TOML)
+++
author = "Peter Souter"
categories = ["Tech"]
date = YYYY-MM-DDTHH:MM:SSZ
description = "A concise SEO description of the post (one sentence)."
draft = true
slug = "post-slug-here"
tags = ["Tag1", "Tag2", "Tag3"]
title = "Post Title Here"
keywords = ["keyword1", "keyword2"]
thumbnailImage = ""
coverImage = ""
+++
Field rules:
author is always "Peter Souter"
draft is always true (user publishes manually)
date uses ISO 8601 with UTC timezone suffix Z (not +00:00 or +08:00)
slug must be unique across all existing posts
description should be filled for SEO, but can be "" if unclear
thumbnailImage and coverImage are "" if no images provided (see Step 4)
Writing Style
Peter's voice is:
- Conversational and first-person: Uses "I", "you", personal anecdotes
- Informal: Contractions ("I've", "it'd"), colloquialisms, rhetorical questions
- Honest and self-deprecating: Admits mistakes, acknowledges limitations
- British English spelling: recognised, organised, colour, favourite, humour
- Link-heavy: Both internal cross-links to other posts and external references
- Uses
<!--more-->: Place after the first paragraph or opening hook to set the excerpt boundary
Content Structure
For technical posts, follow a logical structure:
Opening hook / why this matters
<!--more-->
## Background / Context
## The core content (concepts, tutorial, walkthrough)
## Practical examples or real-world experience
## Gotchas / things I learned the hard way
## Wrap-up
For personal/meta posts, a more narrative flow is fine.
Linking
- Internal links: Link to existing blog posts where relevant using Hugo's
relref shortcode, e.g., [link text]({{< relref "post/YYYY/MM/slug.md" >}})
- External links: Link to authoritative sources (official docs, GitHub repos, RFCs)
- Use inline markdown links:
[link text](url)
Available Shortcodes
- YouTube:
{{< youtube VIDEO_ID >}}
- Bluesky:
{{< bluesky link="URL" >}}
- Standard Hugo shortcodes:
figure, gist, highlight
Step 4: Handle Images
No image generation. If the user provides images:
- Create directory:
mkdir -p static/images/YYYY/MM/
- Place images there with descriptive kebab-case names
- Naming convention:
- Thumbnail:
descriptive-name.jpg
- Cover (wider version):
descriptive-name-cover.jpg
- Set frontmatter paths:
thumbnailImage = "/images/YYYY/MM/descriptive-name.jpg"
coverImage = "/images/YYYY/MM/descriptive-name-cover.jpg"
- In-body images:

If no images are available, leave thumbnailImage and coverImage as "".
GitHub Actions will automatically optimise images on commit — no manual compression needed.
Step 5: Pre-Publication Check
Frontmatter Validation
AI Writing Tells
Before finalising, review the draft against .claude/references/ai-writing-tells.md. Scan for and eliminate:
- Clusters of AI-overrepresented vocabulary ("delve", "crucial", "multifaceted", "landscape", "tapestry", "underscore", "foster")
- Inflated significance phrases ("stands as a testament", "plays a pivotal role")
- Trailing -ing phrases that add no information ("...ensuring a seamless experience")
- Formulaic transitions ("moreover", "furthermore", "it's important to note")
- Every list having exactly three items: vary the count
- Copula avoidance ("serves as" when "is" is more direct)
- Elegant variation (cycling through synonyms instead of repeating the concrete noun)
- Peter's overrides (do not flag): Title Case on every H1/H2/H3 heading and on bold prefixes of
**Term:** description bullets. These are intentional.
- Zero em-dashes, zero en-dashes in prose: Search the draft's prose for
— and –. Every instance must be replaced with a hyphen (-), parentheses, a comma, or a colon. Number ranges included (5-10, not 5–10). Code blocks and verbatim quotes from external sources are exempt.
The signal is density: one instance is fine; a cluster in a section means rewrite that section in Peter's natural voice.
Content Quality
Hugo Build Verification
Run build verification:
hugo --buildDrafts --quiet
The post is only considered complete after confirming there are no build errors.
Next Steps (suggest to user)
- Preview:
hugo server --buildDrafts then visit http://localhost:1313/slug-name/
- Commit:
git add content/post/YYYY/MM/slug.md static/images/YYYY/MM/ (if images added)
- Commit message:
feat(blog): add draft post about [topic]
- Push:
git push -u origin feat/blog/<short-slug>