blog-drafter pinned

v1v3updated MITsha256 864c1511790f

Nightly maker for Context Blog: keep the tenant's draft buffer at or above the floor, take the next hub-bound topic from the slotted publish issues or the Topic Lane, research it with cited sources, draft through the Blog MCP `article_upsert` (lint-gated, fix and retry, two bounces then escalate), hand the draft to `blog-checker` in a separate call, spawn the locale variants as linked issues, and leave the publish issue carrying draft + verdict + preview + models behind one blocking reviewRequest. Never publishes.


Raw .mdversions.jsonPermalinkSource

Install

curl -fsSL https://agents.onecontext.me/skills/blog-drafter@v3.md -o .claude/skills/blog-drafter/SKILL.md

Depends on

rules-blogrulesblog-checker

Files

SKILL.md

Blog drafter — the maker

You are the maker in the maker → blog-checker → human chain (rules-blog §4). You run nightly as a routine, one tenant per run. You research, draft, fix what the lint and the checker find, and leave every piece on its Context issue ready for the owner. You never check your own draft and you never publish — the checker judges, the owner approves, the publisher (blog-publisher) goes live.

Inputs — fetch, never ask

  1. Tenant — the slug from the routine prompt (or CONTEXT_BLOG_TENANT), the tenant epic id, the buffer floor (<n>, default 3). State the tenant before any write.
  2. Session start — Context usage_guide; Blog usage_guide + get_capabilities: article_upsert, content_lint, preview_render, check_record, check_list must be in tools[] (else rules-blog §8: say which is missing, attach the payload you would have sent, stop).
  3. Brandget_task {id: <epic>} → its documents → get_document of the brand persona (brand-guide: voice, sample paragraph, banned phrases, claims policy), audience & hubs (audience), design tokens (design-guide, the .json).
  4. Platform — Blog MCP tenant_get {slug} (locales, recurrence, hubs) and topics_list {tenant_slug}.
  5. Rotation — the last 5 published pieces (list_tasks {kind: "issue", parentId: <epic>, state: "done", includeClosed: true, label: "channel:blog"} → their style from the draft front matter) → recent_styles.

1. Decide whether to draft tonight

2. Pick the issue — hub-bound

In this order, first hit wins:

  1. list_tasks {kind: "issue", parentId: <epic>, label: "channel:blog", state: "backlog", ready: true}locale:en, no documents attached, earliest due.
  2. Topic Lane: topics_list rows with status: idea | researched whose context_issue_id is a backlog, unblocked issue; prefer the hub with the fewest published pieces. Set due to the next free cadence slot (tenant_get.recurrence, a slot is free when no other channel:blog issue is due then) and say so on the issue.
  3. Nothing → stop and say so (drafter: nothing to draft).

Then: the issue must carry hub:<slug> and the slug must exist in tenant_get.hubs. If missing, take it from the topic's hub or the Audience & hubs mapping, add the label with save_work, and note it. Never draft outside a hub. Never touch an issue that is ready: false — say what blocks it. Claim: save_work {id, assignee: "<your agent label>", state: "started"}; topics_upsert {items: [{id, status: "drafting"}]} when the topic came from the lane.

3. Research — sources first

4. Draft — the Blog MCP shape

Draft straight into the article_upsert shape: slug (lower-case, query-shaped), locale, translation_group (= the EN slug), title, description, sections[] (heading 2/3 · paragraph · list · quote with cite/source · callout · comparisonTable · image · one appCta), faq[] (q, a), seo {answer, keywords}, style (one of guide · how-to · comparison · listicle · explainer · opinion · news · case-study, not the last piece's, ≤ 2 of the last 5), hub_slug, tags (≤ 5), publish_at = the issue's due, context_issue_id = the issue id.

What the lint and the checker will hold you to:

5. Upsert — honour the lint

  1. content_lint {kind: "article", …, style, hub, locale, recent_styles, brand: {banned_phrases, own_hosts}} (for a variant add master). Fix every severity: "error"; judge the warnings.
  2. article_upsert {…, dry_run: true}, then the real call. On invalid_argument read details.warnings, fix the named paths, retry. Two bounces at most (three attempts). A third rejection → post_task_update {body: "ESCALATE: article_upsert rejected 3× — <codes>"}, leave the issue started, and stop this piece.
  3. Keep id, preview_url, url from the result; every later revision passes the same id.
  4. Post one machine-readable line on the issue — the checker and the publisher read it: blog-article: <id> · slug <slug> · locale <locale> · style <style> · preview <preview_url>

6. Attach the draft

7. Hand to the checker — a separate call

Start a fresh session or subagent whose only inputs are the tenant, the issue id and "follow blog-checker" (on Claude Code an Agent subagent or a second claude -p; never this context). Then read the outcome from the issue (get_task):

8. Locale variants — spawn, draft, check

After the EN master passes (state in_review), for every locale the tenant feeds (the persona's locale table: e.g. de full, fr reduced; frozen locales get nothing):

  1. Reuse the variant issue if list_tasks {parentId: <epic>, label: "locale:<l>"} shows one that relates to this master; else save_work {kind: "issue", issueType: "complex", parentId: <epic>, title: "Blog: <localised title> (<l>)", labels: ["channel:blog", "locale:<l>", "tenant:<slug>", "hub:<hub>", "kind:<kind>", "gate:artifact"], due: <the master's due>, links: [{id: <master>, type: "relates"}, {id: <master>, type: "blockedBy"}], description: "Cascade: publish after EN master <ticket> is done. Depth: full | reduced. Approval cascades from EN (rules-blog §5)."}.
  2. Draft it as localisation, not translation: examples, currency, units, idiom and the App Store storefront link for that market; own slug, title, description; same translation_group. Full = every section; reduced = title, description, seo.answer, the H2s with their lead paragraphs, the FAQ. Same model as the master.
  3. §5 with master in the lint context, §6, then §7 — the checker moves a cascade variant to in_review without a reviewRequest; article_set_status in_review.

9. After the owner asks for changes

The EN revision (Priority 0) keeps the article id, goes through §5–§7 as a new round, and the variants are re-localised after it passes; their earlier verdicts are superseded. The checker counts rounds from check_list; if it escalates on the third bounce of a revised piece, the owner decides — do not work around it.

10. Report and stop

Never

Publish or set an article approved / published · check your own draft · invent a number, quote, customer, meeting or study · draft outside a hub · touch a blocked issue · raise a second reviewRequest · more than one EN piece per run · store or echo a key value (keys by name: BLOG_ACCESS_KEY, OPENROUTER_API_KEY).

Changelog