blog-drafter latest

v7v13updated MITsha256 1c5ec9bc828e

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 under the tenant's `Blog` parent or graduate one from the `Backlog`, 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 child issues of the EN master, and leave the publish issue carrying the markdown draft (for comments) + verdict + the final rendered preview (to click and see) + models behind one blocking request_review. Never publishes.


Raw .mdversions.jsonPermalinkSource

Install

curl -fsSL https://agents.onecontext.me/skills/blog-drafter.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. Parents — the epic's Blog parent (list_issues {parent_id: <epic>, label: "lane:blog"}<blog parent>) and its Backlog (label: "lane:backlog"<backlog>); the routine prompt or the epic's ## Structure may carry the ids — confirm with get_issue. Every publish issue is a child of <blog parent>, never of the epic (rules-blog §3 Hierarchy).
  3. Session start — Context start_context; Blog MCP 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).
  4. Brandget_epic {id: <epic>} → its artifacts → get_artifact of the brand persona (brand-guide: voice, sample paragraph, banned phrases, claims policy), audience & hubs (audience), design tokens (design-guide, the .json).
  5. Platform — Blog MCP tenant_get {slug} (locales, recurrence, hubs) and topics_list {tenant_slug}.
  6. Rotation — the last 5 published pieces (list_issues {parent_id: <blog parent>, 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_issues {parent_id: <blog parent>, label: "channel:blog", state: "open", ready: true}locale:en, no artifacts attached, earliest due.
  2. Backlog: list_issues {parent_id: <backlog>, label: "stage:topic", state: "open"} — researched topics, oldest first, preferring the hub with the fewest published pieces (stage:idea children only when no topic exists and the idea names a hub: research it, relabel it stage:topic, then continue). Graduate it: create the publish issue as a child of <blog parent> (create_issues {parent_id: <blog parent>}, lanes.md §4 shape, due = the next free cadence slot from tenant_get.recurrence — free when no other channel:blog issue is due then — labeled relates:<backlog child>), post Graduated → <new ticket> on the Backlog child and mark it done (complete_issues with workStats; it stays in the Backlog as history), and topics_upsert {items: [{id, context_issue_id: <new issue>, status: "drafting"}]} when the topic exists on the Blog MCP. Say on the new issue where it came from.
  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 update_issues, and note it. Never draft outside a hub. Never touch an issue that is ready: false — say what blocks it. Claim: update_issues {ids: [id], assignee: "<your agent label>", state: "in_progress"}; topics_upsert {items: [{id, status: "drafting"}]} when the topic came from the lane.

3. Research — sources first

4. Draft — the Blog MCP shape

Shape the story before writing sections

Write these two lines in the draft notes before prose:

  1. Spine: <reader's present problem> → <what this product changes> → <the outcome the reader can now reach>.
  2. Through-line: one real, verified case—or one clearly labeled, realistic example—that makes the spine observable from beginning to end.

Every section must move that same spine forward. If a section introduces a second thesis, remove it or make it another article.

For a product launch, use this six-beat arc. Compress beats when the release is small; do not add a beat merely to satisfy the outline:

  1. What changed: name the product, its reader, and the useful outcome in the first 70 words.
  2. Why now: one short section names the broken workflow or constraint.
  3. See it work: walk the through-line through the product before listing capabilities.
  4. How it works: explain only the mechanisms the reader needs to believe the example.
  5. Why trust it: put proof and the relevant limitation beside the claim they qualify.
  6. What next: state current availability and one concrete action. Do not recap the article.

For the Context Blog launch post, keep the product frame exact: blog automation with AEO and GEO built in. Show how an agent writes, a separate checker reviews, a person approves, the platform publishes the discovery signals, and visibility evidence improves the next article. A topic or search query may be an input to that automation; do not turn “start with an audience question” or “answer first” into the product thesis. Those are editorial techniques, not what Context Blog is.

This arc is derived from a first-party review of OpenAI and Anthropic product writing: strong launches reveal the product immediately, explain it through observable work, keep proof local to claims, and end once access and the next step are clear. It is an editorial model, not a request to imitate either company's phrases.

Prose and evidence contract

Before lint, run a cut pass. Remove market throat-clearing, repeated versions of the thesis, feature lists already demonstrated by the through-line, methodology that does not change interpretation, and sentences that merely announce importance. The target is not a word count; it is the shortest article that makes the product, its proof, and its limits clear.

Then run a No AI Slop pass as a restrained copy edit. Use the portability test: if a sentence could move unchanged into a launch for an unrelated product, rewrite it with the actual subject, action, output or constraint, or delete it. Cut throat-clearing, false binary setups, faux quotations, summary recaps, dramatic fragments, decorative em dashes, inflated transitions and sentences that interpret the previous sentence instead of adding information. Prefer plain verbs and concrete nouns. Do not flatten a distinctive sentence merely because it is longer or stylistic.

This pass has a specificity floor. It may not remove the details a reader needs to answer all five questions below:

  1. What does the product do from input to outcome?
  2. Which steps are automated, which are independently checked, and which need a person?
  3. What does the published result contain or emit?
  4. What guardrail stops a bad or unsupported result?
  5. What evidence is measured, and how does it change the next action?

For every important capability, keep at least one subject → action → visible result sentence. A feature may be compressed after the through-line has shown it, but never replaced by category language such as visibility system, content infrastructure or intelligent workflow. If the edited draft leaves the philosophy clearer than the product, restore the concrete mechanism or a worked example before linting. No AI Slop is an editing pass, not permission to abstract away the product.

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_comment {parent_id: <issue>, body: "ESCALATE: article_upsert rejected 3× — <codes>"}, leave the issue in_progress, 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_issue):

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_issues {parent_id: <master issue>, label: "locale:<l>"} shows one; else create_issues {parent_id: <master issue>, issues: [{title: "Blog: <localised title> (<l>)", labels: ["channel:blog", "locale:<l>", "tenant:<slug>", "hub:<hub>", "kind:<kind>", "gate:artifact", "relates: <master>"], due: <the master's due>, blocked_by: [<master>], description: "Cascade: publish after EN master <ticket> is done. Depth: full | reduced. Approval cascades from EN (rules-blog §5)."}]}. Variants live under the master, never beside it under the channel parent — the owner opens one blog Issue and sees every language as a child. (Publisher and daily brief walk list_issues {parent_id: <master>} for the cascade.)
  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 request_review; 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 · create a piece directly under the epic · touch a blocked issue · raise a second request_review · more than one EN piece per run · store or echo a key value (keys by name: BLOG_ACCESS_KEY, OPENROUTER_API_KEY).

Changelog