onsite-publish · git:20260718.7d82de8 · 2026-07-18 · sha256 1f97381604677b26

onsite-publish git:20260718.7d82de8A

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

---
name: onsite-publish
description: Use to publish an APPROVED, drafted content item to WordPress - "publish the draft", /organic-os:publish. Refuses unapproved items.
---

# Publish content (gated)

1. Input: a brief item with status "drafted" whose draft file sits next to it
   (same folder, <brief-name>.draft.md produced by ce-produce).
2. Gate: the brief's status must be "drafted" (step 1 already checks) AND
   `core.contracts.require_approval_lineage(brief_path)` must pass; abort on
   ContractError. A rejected item must never reach `create_post`. The profile
   user must also confirm the final draft in-session or via channel.
3. Create the post via the CMS adapter (`onsite.cms.adapter_for`;
   WordPress today): `create_post` (status draft by default; status
   "publish" only when site-profile sets publishing: direct), then
   `update_seo_meta` with title/description/focus keyword from the draft's
   frontmatter, and schema if the draft includes it. (`update_rankmath`
   remains as the WordPress adapter's alias for `update_seo_meta`.)
4. Verify with `get_rendered_head`; on success `PYTHONPATH="$CLAUDE_PLUGIN_ROOT/lib"
   python3 -m core status <brief-path> published --actor agent` and
   write the outcome record with measurement dates; on failure leave the post
   in draft and record the post id + failure reason in the outcome record.
   IndexNow: when site-profile.yaml has `indexnow: {enabled: true, key: ...}`
   (additive key) AND the post actually went live (status "publish" - a WP
   draft has no public URL to submit), call `hoo.indexnow.submit(host, key,
   [post_url])` after the successful verify and record the returned status
   in the outcome record; a non-200 is recorded, never retried in-run.
5. If a Canva image brief exists next to the draft (from ce-image), record its
   path in the outcome record for manual upload (media upload is not in v1).

Dry-run: with `onsite: {dry_run: true}` in site-profile.yaml, run the same
gated flow with the adapter constructed `dry_run=True` - nothing is
created, the brief stays `drafted`, and the outcome record is marked
dry-run listing every write from the adapter's `dry_run_log` (see
skills/onsite-apply).

Never edit brain frontmatter directly. The contract CLI is the only write
path for status and approvals.