blog-post · git:20260724.8e9fbb7 · 2026-07-24 · sha256 1c240b66a977bfc9

blog-post git:20260724.8e9fbb7A

Immutable. This exact content is served forever at /api/v1/blob/1c240b66a977bfc9.

---
name: blog-post
description: Write and publish a new blog post for the skillfold site (site/blog/). Use whenever asked to write a blog post, add blog or SEO content, publish the next post from the queue, or when the weekly blog automation runs. Covers the full pipeline - picking the topic from docs/blog-todo.md, verifying every claim against the source, writing the markdown post, regenerating the site, and shipping the PR.
---

# Writing a blog post

The blog lives at `https://byronxlg.github.io/skillfold/blog/`, deployed by
`.github/workflows/docs.yml` on merge to main.

Unlike a hand-maintained static site, **you write exactly one file**. A post is
a markdown file in `site/blog/posts/` with YAML frontmatter.
`scripts/build-blog.ts` derives everything else - the blog index, the post
page, the RSS feed, the sitemap, and the newest-three list on the landing page.
Never hand-edit generated output; it is gitignored and rebuilt on every deploy.

A post touches these files and no others:

| File | Change |
|---|---|
| `site/blog/posts/<date>-<n>-<slug>.md` | the new post (create) |
| `site/index.html` | only the generated block, and only via `npm run build:blog` |
| `docs/blog-todo.md` | check the topic off Queue, add it under Shipped |

If you find yourself editing `site/blog/index.html`, `site/feed.xml`,
`site/sitemap.xml`, or `site/robots.txt`, stop: those are build products.

## 1. Pick the topic

Take the **topmost unchecked topic** in `docs/blog-todo.md`. The queue is
ordered by value; do not cherry-pick a more interesting one further down.

If the queue is empty, refill it with five new topics before writing. Good
topics come from the gap between what skillfold does and what people
currently do by hand: a real workflow problem, named the way someone would
search for it. Bad topics are release notes and feature tours.

Check the Shipped list first. Do not re-cover a topic already written up
unless the behavior itself changed, and then say what changed and link the
earlier post.

## 2. Get the facts right

This is the part that matters most. skillfold posts make concrete claims
about how a tool behaves, and a wrong claim in a post that ranks is worse
than no post at all.

**Never write skillfold behavior from memory.** Before describing anything:

- Read the implementation. `src/resolve.ts` for pin reuse and frozen mode,
  `src/install.ts` for managed-directory safety and pruning, `src/compose.ts`
  for composition, `src/targets.ts` and `src/agentsmd.ts` for targets and the
  AGENTS.md block, `src/lock.ts` for the lockfile shape.
- Read the docs that already pin the contract: `docs/manifest.md`,
  `docs/cli.md`, `docs/getting-started.md`, `docs/publishing.md`. If a post
  and the docs disagree, one of them is a bug - resolve it, do not paper over it.
- Run the command. Any console output you quote must come from an actual run
  (`npx tsx src/cli.ts <command>`), not from memory of what it prints. Use a
  scratch directory, not this repo, when the command would write files.
- Check the tests. `src/*.test.ts` encode the edge cases; they are the fastest
  way to learn what a function actually guarantees.

If a claim cannot be verified against source, docs, or a real run, cut it.

Claims about other tools (Claude Code, Codex, npm, the agent skills standard)
need a citation you actually fetched, not a recollection. WebSearch or
WebFetch it, and link it.

## 3. Write the post

Filename: `site/blog/posts/YYYY-MM-DD-N-<slug>.md`, where `YYYY-MM-DD` is
today's real date and `N` is a sequence number that only matters when several
posts share a date (lower sorts higher on the page; use `1`). Neither the
date nor the number appears in the URL.

Frontmatter:

```yaml
---
title: What the reader is trying to do
description: 140-160 characters stating what the post explains and what the reader leaves with.
date: 2026-07-25
tags: [concepts]
---
```

- `title`, `description`, and `date` are required; the build fails without them.
- `date` must be `YYYY-MM-DD` and must be today's real date.
- `tags`: one tag, from `concepts` (how to think about the problem),
  `feature` (how a specific capability works), or `workflow` (how to do a
  task end to end).
- `slug` is derived from the filename. Set it explicitly only to keep a URL
  stable when renaming.
- `draft: true` keeps a post out of the build entirely.

Content rules:

- 800-1400 words. Long enough to teach the topic, short enough that every
  section pulls weight.
- The title is what someone would search for or ask a colleague, phrased as
  their problem, not as a feature name. "Your skills directory is undeclared
  state", not "Introducing lockfiles".
- Open with the reader's situation, not with skillfold. The tool appears when
  it is genuinely the answer, usually a third of the way in and again at the
  end. A post that pitches in the first paragraph reads like an ad and gets
  closed like one.
- Say what the tool will not do, and when not to use the thing you are
  describing. Every post should contain at least one honest limitation. This
  is the single biggest difference between a post developers trust and one
  they bounce off.
- Voice: plain, precise, technical. No emojis. No em dashes - use hyphens or
  restructure the sentence. No exclamation marks. Do not use "simply",
  "just", "easy", or "powerful".
- Use `##` for sections and `###` sparingly. The generator gives every
  heading an id, so sections are linkable.
- Fenced code blocks with a language tag (```yaml, ```sh, ```console). Keep
  lines under about 76 characters: longer lines force a horizontal scrollbar
  at the article measure.
- Use `console` for anything showing a prompt and real output, `sh` for
  commands alone. Output in a `console` block must be a real run's output.
- Link to the docs (`https://github.com/byronxlg/skillfold/blob/main/docs/...`)
  rather than restating a reference table in full.
- Markdown tables are supported and are the right shape for
  option-by-option comparisons.

## 4. Build and verify

```sh
npm run build:blog
```

This regenerates the post page, blog index, feed, sitemap, and the landing
page block. Then:

- `npm run typecheck` and `npm test` still pass (they should be untouched,
  but a post is a commit like any other).
- Serve and click through: `npx http-server -p 8899 -s site`, then check
  `/blog/`, the new post, and the landing page `#blog` section. Confirm the
  post renders, the prev/next links point at real posts, and the landing
  block lists the new post first. Kill the server when done.
- Grep the new slug across `site/` - it must appear in the generated post
  path, the blog index, the feed, the sitemap, and the landing block. If it
  is missing anywhere, the build did not run or the frontmatter is wrong.
- Re-read the post for claims you did not actually verify in step 2. Cut them.

`site/index.html` is committed, so if `build:blog` rewrote its generated
block (it says so in its output), commit that change with the post.

## 5. Update the queue

In `docs/blog-todo.md`: check the topic off under Queue, and add a line under
Shipped with the date and the post path:

```md
- [x] <title> - 2026-07-25, site/blog/posts/2026-07-25-1-<slug>.md
```

Shipped is how the next run avoids re-covering a topic, so every published
post lands there.

## 6. Ship

Branch `blog/<slug>`, commit as `Blog: <title>`, push, and open a PR
describing the topic in a paragraph and naming the search intent it targets.

The PR is the audit trail and the revert point, not a review gate. If the
diff is only the post, the landing page's generated block, and
`docs/blog-todo.md`, squash-merge it yourself:

```sh
gh pr merge --squash --delete-branch
```

If the diff touches anything else - the workflow, this skill, `scripts/`,
`src/`, the manifest - leave the PR open for review and say why in a comment.
That boundary is the whole safety model for unattended runs; do not widen it.

When merging from inside a GitHub Actions run, dispatch the deploy afterwards:

```sh
gh workflow run docs.yml
```

Pushes made with `GITHUB_TOKEN` do not trigger other workflows on their own.
A merge done interactively with a human's `gh` auth triggers the deploy
automatically and needs no dispatch.