Agent notes

  • Review GEMINI.md before working—contains project-specific guidance and expectations.

Pre-commit checklist

Before committing any blog post, always run these checks:

  1. Validate front matter: Ensure title, date, categories, and layout are present and correct.
  2. Check all links: Extract every URL from the post and verify each returns HTTP 200 (or 401 for API endpoints requiring auth). Fix any broken links before committing.
  3. Verify code blocks: Ensure all fenced code blocks have a language tag and that commands are syntactically valid.
  4. Check for secrets: Scan the post for API keys, tokens, or passwords. Never commit secrets to version control.

Commit and push workflow

  • Always commit and push your changes after completing the pre-commit checklist.
  • Branch strategy:
    • For new blog posts or significant changes: create a new branch from main, commit, push, and open a PR.
    • For minor fixes: commit directly to main and push.
  • Commit message format: Use a concise, descriptive message that explains what changed and why.
  • Never force-push to main unless explicitly instructed.

Creating a new blog post

Blog posts live in the posts/ directory as Markdown files.

File naming

posts/YYYY-MM-DD-slug.md

The slug should be lowercase, hyphenated, and descriptive (e.g., 2026-04-22-deploy-jupyterhub-openstack-magnum-tofu.md).

YAML front matter (header)

Every post starts with a YAML front matter block. Required and common fields:

---
title: "Your concise post title"
date: YYYY-MM-DD
categories: [category1, category2]
---
  • title: Always use quotes. Use sentence case, never title case. Capitalize only the first word and proper nouns or acronyms such as ChromeOS and AI. Prefer literal, concrete wording that says what the post helps the reader do. Avoid vague metaphors such as “giving an agent my browser.”
  • date: Must match the date in the filename.
  • categories: Use existing categories only — do not invent new ones. Common categories: python, kubernetes, jupyterhub, jetstream, linux, hpc, github, git, openscience, dask, singularity, nersc, sdsc, ai, llm, automation, tools, documentation, events, education, nbgrader, healpy, pysm, cosmology, cloudcomputing, openstack, jetstream2, italian.
  • description (optional): A one-sentence summary used for SEO and social previews.
  • author (optional): Defaults to “Andrea Zonca” via posts/_metadata.yml.
  • layout: Set to post for blog posts (some older posts omit this).
  • slug (optional): Override the URL slug if it differs from the filename.
  • aliases (optional): Old URLs that should redirect to this post.

Body content and style

  • Write in Markdown. The site is built with Quarto using the litera theme.
  • Use ## for section headings (the title is rendered separately from the banner block).
  • Keep an introductory paragraph right after the front matter that summarizes what the post is about.
  • Use bullet points and numbered lists for step-by-step instructions.
  • Link to relevant resources (GitHub repos, documentation, gists) inline.
  • For images, use paths relative to the post’s directory (e.g., img/screenshot.png). Avoid absolute paths like /img/....
  • Use --- horizontal rules to separate major sections.

Scripts and code

  • Multi-line scripts: Upload to a GitHub Gist and embed it in the post:

    <script src="https://gist.github.com/zonca/GIST_ID.js"></script>

    Also provide a plain-text link to the gist for accessibility.

  • One-liners: Can stay inline in the post body, for example:

    curl -o ~/.up.sh https://raw.githubusercontent.com/zonca/up/main/up.sh
  • Code blocks: Use fenced code blocks with language tags (```bash, ```python).

Categories reference

Only use categories that already exist in the blog. To check existing categories, search the categories: field across files in posts/. Never create new categories unless explicitly instructed.

Publishing workflow

  1. Create the new post file in posts/.
  2. Commit and push to the main branch (or open a PR for significant posts).
  3. GitHub Actions renders the Quarto project and publishes to GitHub Pages automatically.
  4. The Netlify build also triggers on pushes to main.

Buffer Social Media

Setup

  • Buffer skill located at: /home/zonca/.agents/skills/buffer/SKILL.md
  • Uses GraphQL API at https://api.buffer.com
  • Environment variable BUFFER_KEY must be set

Channel IDs (organization: 655270181efd918df3d33eff)

Platform Channel ID
Twitter/X 6552702d7a6cf4d23f916d5c
LinkedIn 67c6746b3b85969eafee1ee4
Bluesky 67c6738e3b85969eafe22a07

Quick post creation

buffer_query() {
  curl -s -X POST "https://api.buffer.com" \
    -H "Authorization: Bearer $BUFFER_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"query\": \"$1\"}"
}

buffer_query "mutation { createPost(input: { text: \"POST TEXT\", channelId: \"CHANNEL_ID\", schedulingType: automatic, mode: addToQueue }) { ... on PostActionSuccess { post { id text dueAt } } ... on MutationError { message } } }"

Workflow for new blog posts

  1. Read the post content
  2. Draft 1 announcement post adapted for each platform (Twitter: short + hashtags, LinkedIn: professional with bullet points, Mastodon/Bluesky: similar to Twitter)
  3. Show drafts to user for approval
  4. Once approved, queue to all 3 channels using Buffer API

Note: If running with Jules, you do not need to do the Buffer social media posting step.