# Publishing Blog Posts

`docs/blog/` in the `aiwg` repo is the **single source of truth** for AIWG blog content. You write a post once as markdown here; pagenary builds it to docs.aiwg.io and auto-emits a machine-readable manifest, and aiwg.io sources the list from there. One place to write, one merge to publish — no hand-authored HTML on the website.

This page is the contract the downstream consumer (aiwg.io, `aiwg.io#60`) builds against.

## The model

```
docs/blog/<slug>.md (frontmatter + body)     ← authoritative content (this repo)
docs/.public/blog/<image>.png                ← post images (served as static assets)
docs/config.json  "collections"              ← marks docs/blog as a collection
   │ pagenary build (.gitea/workflows/docsite-build.yml → docsite-deploy.yml)
   ▼
docs.aiwg.io/pages/blog--<slug>.html         ← rendered post (crawlable static page)
docs.aiwg.io/blog/index.json                 ← AUTO-GENERATED manifest (from frontmatter)
docs.aiwg.io/blog/feed.xml                    ← AUTO-GENERATED RSS feed
   │ fetched at build by aiwg.io's vite-plugin-blog (aiwg.io#60)
   ▼
aiwg.io/blog                                 ← public list, links to / mirrors docs.aiwg.io
```

The rule: **content is single-sourced in this repo**. The manifest is generated by pagenary from each post's frontmatter (no hand-maintained index). The website reads the manifest and renders or links out.

Requires `@pagenary/publisher` ≥ 2026.5.3 (collection support, pagenary#18).

## Where things live

| Item | Location | Notes |
|------|----------|-------|
| Post markdown | `docs/blog/<slug>.md` | `<slug>` = filename stem = URL slug |
| Post images | `docs/.public/blog/<file>.png` | served at `https://docs.aiwg.io/assets/blog/<file>.png` |
| Collection config | `docs/config.json` → `collections[]` | marks `docs/blog` as a collection (one-time, already set) |
| Section nav | `docs/blog/_manifest.json` + the top-level `docs/_manifest.json` `order` | makes the post reachable in the docs nav |
| Manifest + feed | **generated** → `docs.aiwg.io/blog/index.json`, `/blog/feed.xml` | do **not** hand-author these |

Use the date-prefixed kebab slug, e.g. `2026-5-how-aiwg-builds-your-system-prompt`.

## Collection config (already wired)

`docs/config.json` carries the one-time collection registration:

```json
"collections": [
  {
    "path": "blog",
    "route": "/blog",
    "title": "AIWG Blog",
    "manifest": true,
    "feed": true,
    "sortBy": "date",
    "order": "desc"
  }
]
```

`path` is relative to the docs content root (`docs/`). On every build, pagenary reads every `.md` in `docs/blog/` (excluding `_`-prefixed files and `index.md`) and emits `dist/.../blog/index.json` + `feed.xml`. You don't touch this per-post.

## Frontmatter spec

Every post opens with YAML frontmatter. pagenary's collection generator derives the manifest from it. Note which fields it reads **verbatim** vs **derives/overrides** — this is exact behavior verified against `@pagenary/publisher` 2026.5.3:

```yaml
---
title: "How AIWG builds your customized system prompt (and command set)"
slug: "2026-5-how-aiwg-builds-your-system-prompt"
date: "2026-05-26"
summary: "One or two sentences. Lead with the answer. Shown in the blog list, social cards, and RSS."
hero: "https://docs.aiwg.io/assets/blog/system-prompt.png"
reading_time: 9
status: "published"
canonical: "https://aiwg.io/blog/2026-5-how-aiwg-builds-your-system-prompt"
tags: ["ai-coding", "developer-tools", "open-source"]
# --- optional (provenance / categorization, ignored by the manifest) ---
pillar: "2 how/why"
audience: "developers customizing AI coding assistants across more than one tool"
aiwg_refs: ["aiwg use", "aiwg regenerate", "path-scoped rules"]
---
```

| Field | Required | How pagenary uses it in the manifest |
|-------|----------|--------------------------------------|
| `title` | yes | Verbatim (falls back to first `# Heading`, then slug). Also repeat as a leading `# Title` in the body. |
| `slug` | yes | Convention only — pagenary uses the **filename stem** as the slug. Keep them equal. |
| `date` | yes | Verbatim. Drives manifest sort order (`sortBy: date`, newest first). |
| `summary` | yes | Verbatim (falls back to `description`). |
| `hero` | yes | **Verbatim, not absolutized** — so use a full `https://docs.aiwg.io/assets/blog/<file>.png` URL (the consumer fetches the manifest cross-origin and needs an absolute image URL). |
| `tags` | recommended | Verbatim (array). pagenary reads `tags` — not `hashtags`. |
| `reading_time` | informational | **Ignored by the manifest** — pagenary computes it from body word count (~200 wpm). Keep it accurate for humans, but the manifest value is computed. |
| `canonical` | informational | **Ignored by the manifest** — pagenary derives the manifest `canonical` to the crawlable docs.aiwg.io static page (`/pages/blog--<slug>.html`). Keep it to record author intent. |
| `status` | convention only | **Not a filter** — see [Drafts](#drafts-and-status). Every post file in `docs/blog/` is published. |
| `pillar`, `audience`, `aiwg_refs` | no | Provenance only; not in the manifest. |

**Do not add an `authoring:` field or any AI-tool credit.** The repo-wide `no-attribution` rule applies to blog posts.

Always repeat the title as a leading `# Title` in the body — pagenary strips the YAML frontmatter and renders only the body, so without a leading heading the page has no `<h1>`. (`@pagenary/publisher` ≥ 2026.5.4 wires the frontmatter parser into the page renderer too, closing pagenary#19.)

## Images

Place post images under `docs/.public/blog/` and reference them by their **served absolute path**:

- Body embed: `![alt](/assets/blog/<file>.png)`
- Frontmatter `hero`: `https://docs.aiwg.io/assets/blog/<file>.png`

`docs/.public/` is pagenary's static-asset directory; its contents are copied under `/assets/` in the built site (`docs/.public/blog/x.png` → `docs.aiwg.io/assets/blog/x.png`). **Co-located `docs/blog/images/` files are NOT copied** by the build — use `.public/blog/`. Keep heroes 16:9 with descriptive alt text.

Reports, retrospectives, release announcements, and other public-facing posts must ship with a generated 16:9 hero image. Generate the image through the approved ChatGPT image workflow, save the final PNG under `docs/.public/blog/`, and verify the rendered page or preview with Playwright before publishing.

## Tools & transparency disclosure (required)

Every post **must end** with a `## Tools & transparency` section that honestly discloses how the content was produced. AIWG publishes how its own content is made; readers should never have to guess. Be truthful, not flattering — most AIWG posts are **mostly AI-generated and then human-fact-checked**, and the disclosure should say exactly that rather than overstate human authorship.

Use this consistent shape (adapt the imagery + facts lines per post):

```markdown
## Tools & transparency

AIWG is open about how its content is made.

- **Words:** mostly AI-generated, then fact-checked and edited by a human.
- **Facts and claims:** checked against the actual work/release — nothing model-invented ships unverified.
- **Imagery:** which images, if any, were AI-generated and with what tool (e.g. ChatGPT / DALL·E); confirm no text/logos are AI-rendered. ("none" if there are no images.)
- **Final pass:** human.

Net: AI-drafted, human-fact-checked.
```

Guidance:
- **Don't over-claim originality.** If the prose was drafted by a model (the common case), say "mostly AI-generated." Reserve "original human writing" for posts a person actually wrote start to finish.
- **The human contribution is fact-checking and editing** — that's the honest, valuable part, and it's what the disclosure should foreground.
- This is a disclosure of *content provenance*, distinct from the `no-attribution` rule (which forbids stamping AI-tool credit on commits/code). Marketing prose and imagery get an honest provenance note; commits do not get tool attribution.

## Manifest (generated — do not hand-edit)

pagenary writes `docs.aiwg.io/blog/index.json` on every build as an **envelope object**:

```json
{
  "title": "AIWG Blog",
  "route": "/blog",
  "count": 1,
  "generated": "2026-05-27T17:03:29.776Z",
  "posts": [
    {
      "slug": "2026-5-how-aiwg-builds-your-system-prompt",
      "title": "How AIWG builds your customized system prompt (and command set)",
      "date": "2026-05-26",
      "summary": "AIWG doesn't ship one static system prompt. …",
      "hero": "https://docs.aiwg.io/assets/blog/system-prompt.png",
      "tags": ["ai-coding", "developer-tools", "open-source"],
      "reading_time": 9,
      "canonical": "https://docs.aiwg.io/pages/blog--2026-5-how-aiwg-builds-your-system-prompt.html",
      "path": "/blog/2026-5-how-aiwg-builds-your-system-prompt"
    }
  ]
}
```

Consumer contract notes for **aiwg.io#60**:
- It is an **object** (`{title, route, count, generated, posts[]}`), **not a bare array**. Iterate `posts`.
- `canonical` points at the docs.aiwg.io rendered page (pagenary makes docs.aiwg.io canonical); `path` is the clean `/blog/<slug>` route.
- `hero` is whatever the frontmatter declared — keep it an absolute URL so it renders without rewriting.
- `feed.xml` (RSS 2.0) is emitted alongside for feed readers / sitemap.
- Sorted by `date` descending.

## Drafts and status

There is **no status filter**: pagenary publishes *every* `.md` in `docs/blog/` (except `_`-prefixed files and `index.md`). So a draft committed to `docs/blog/` goes live. Keep drafts **out** of `docs/blog/` until they're ready — author them in `roctinam/social-orchestration` (`aiwg/articles/_work/`) and migrate only when publishing. The `status` frontmatter field is a human marker, not a build gate. (`idea` → `outlined` → `drafted` → `reviewed` → `published`; only migrate at `reviewed`+.)

## Adding a post

1. **Author/migrate** a reviewed draft to `docs/blog/<slug>.md` with the frontmatter above and a leading `# Title`.
2. **Add the generated hero** to `docs/.public/blog/<file>.png`; set frontmatter `hero` to the absolute `/assets/blog/…` URL, embed it in the body with `![alt](/assets/blog/<file>.png)`, and verify the rendered image with Playwright.
3. **Register nav**: add `<slug>` to `docs/blog/_manifest.json` `order`, and `blog/<slug>` to the top-level `docs/_manifest.json` `order` (+ a page-definition entry).
4. **Build-check locally:**
   ```bash
   npm ci
   npx pagenary build:tenants aiwg-docs            # mirrors docsite-build.yml
   cat dist/aiwg-docs/blog/index.json              # confirm your post is in posts[]
   ```
5. **Commit and merge.** Delivery mode is `direct` (see `.aiwg/aiwg.config`): commit to `main` with a `Closes #<issue>` line; no PR. `Docsite Build` validates any `docs/**` change; `docsite-deploy` publishes to docs.aiwg.io on push to `main`, regenerating the manifest. aiwg.io picks up the new entry on its next build.

The manifest updates itself — you never edit `index.json` by hand.

## Publish flow at a glance

```
write docs/blog/<slug>.md  →  commit to main  →  Docsite Build (validate + emit manifest)  →  docsite-deploy (docs.aiwg.io)
                                                                                                      │
                                            aiwg.io vite-plugin-blog fetches /blog/index.json → aiwg.io/blog
```

## Companion roadmap

The first post (A9) is live. Four companion how-tos are reviewed and queued in `roctinam/social-orchestration` (`aiwg/articles/_work/C1–C4`), to migrate as their own posts:

| ID | Working title | Target slug | Source draft |
|----|---------------|-------------|--------------|
| A10 | Build a custom agent in AIWG (with the smiths) | `2026-5-build-a-custom-agent-in-aiwg` | `_work/C1-build-a-custom-agent.md` |
| A11 | Scope rules to where they matter: `paths:` frontmatter | `2026-5-scope-rules-with-paths-frontmatter` | `_work/C2-scope-rules-with-paths.md` |
| A12 | Find anything in AIWG: discover then show | `2026-5-find-anything-discover-show` | `_work/C3-find-anything-discover-show.md` |
| A13 | One source, ten targets: deploy AIWG across your tools | `2026-5-one-source-ten-targets` | `_work/C4-one-source-ten-targets.md` |

Each follows the [Adding a post](#adding-a-post) steps; the manifest regenerates automatically.

## Related

- `roctinam/aiwg.io#60` — website sources the blog from docs.aiwg.io (consumes `index.json`).
- `roctinam/pagenary#18` — collection manifest + feed support (**shipped** in 2026.5.3; this is what generates the manifest).
- `roctinam/pagenary#19` — page renderer strips frontmatter (closed; adopted in `@pagenary/publisher` 2026.5.4).
- `roctinam/social-orchestration` — upstream article drafting and validation (`aiwg/articles/`).
