Publishing Blog Posts

Blog source-of-truth model, frontmatter spec, and publish flow

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

ItemLocationNotes
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 + feedgenerated → `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:

"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:

---
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"]
---
FieldRequiredHow pagenary uses it in the manifest
`title`yesVerbatim (falls back to first `# Heading`, then slug). Also repeat as a leading `# Title` in the body.
`slug`yesConvention only — pagenary uses the filename stem as the slug. Keep them equal.
`date`yesVerbatim. Drives manifest sort order (`sortBy: date`, newest first).
`summary`yesVerbatim (falls back to `description`).
`hero`yesVerbatim, 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`recommendedVerbatim (array). pagenary reads `tags` — not `hashtags`.
`reading_time`informationalIgnored by the manifest — pagenary computes it from body word count (~200 wpm). Keep it accurate for humans, but the manifest value is computed.
`canonical`informationalIgnored 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 onlyNot a filter — see Drafts. Every post file in `docs/blog/` is published.
`pillar`, `audience`, `aiwg_refs`noProvenance 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: `<img src="/assets/blog/&lt;file&gt;.png" alt="alt">`
  • 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.

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):

## 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:

{
  "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 hero to `docs/.public/blog/<file>.png`; set frontmatter `hero` to the absolute `/assets/blog/…` URL and embed it in the body with `<img src="/assets/blog/&lt;file&gt;.png" alt="alt">`.

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:

   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:

IDWorking titleTarget slugSource draft
A10Build a custom agent in AIWG (with the smiths)`2026-5-build-a-custom-agent-in-aiwg``_work/C1-build-a-custom-agent.md`
A11Scope rules to where they matter: `paths:` frontmatter`2026-5-scope-rules-with-paths-frontmatter``_work/C2-scope-rules-with-paths.md`
A12Find anything in AIWG: discover then show`2026-5-find-anything-discover-show``_work/C3-find-anything-discover-show.md`
A13One 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 steps; the manifest regenerates automatically.

  • `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/`).