blog-drafter pinned
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 linked issues, and leave the publish issue carrying draft + verdict + preview + models behind one blocking reviewRequest. Never publishes.
Install
curl -fsSL https://agents.onecontext.me/skills/blog-drafter@v9.md -o .claude/skills/blog-drafter/SKILL.mdDepends on
Files
templates/draft-notes.md1.7 KB
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
- 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. - Parents — the epic's
Blogparent (list_tasks {kind: "issue", parentId: <epic>, label: "lane:blog"}→<blog parent>) and itsBacklog(label: "lane:backlog"→<backlog>); the routine prompt or the epic's## Structuremay carry the ids — confirm withget_task. Every publish issue is a child of<blog parent>, never of the epic (rules-blog§3 Hierarchy). - Session start — Context
usage_guide; Blogusage_guide+get_capabilities:article_upsert,content_lint,preview_render,check_record,check_listmust be intools[](elserules-blog§8: say which is missing, attach the payload you would have sent, stop). - Brand —
get_task {id: <epic>}→ its documents →get_documentof the brand persona (brand-guide: voice, sample paragraph, banned phrases, claims policy), audience & hubs (audience), design tokens (design-guide, the.json). - Platform — Blog MCP
tenant_get {slug}(locales, recurrence, hubs) andtopics_list {tenant_slug}. - Rotation — the last 5 published pieces (
list_tasks {kind: "issue", parentId: <blog parent>, state: "done", includeClosed: true, label: "channel:blog"}→ theirstylefrom the draft front matter) →recent_styles.
1. Decide whether to draft tonight
- Buffer = EN publish issues (
channel:blog,locale:en) that carry adeliverabledocument and are not live yet (in_review, ordonewith noPublished:update). Slots = publish issues withduein the next 7 days. - Priority 0 — changes requested. Issues in
startedwhose latest activity is an owner comment after areviewRequestcome first: revise per the comment (§5–§7 with the same articleid), re-localise the variants after the revision passes, and count that as tonight's work. - Draft when the buffer is below the floor, or when any slotted issue
due within 7 days has no deliverable. Otherwise stop and print
drafter: buffer ok (<buffer>/<floor>). - One EN piece per run, plus its locale variants. Every fifth piece
(~20 %) is a refresh when the Backlog or the Performance Report holds
a
kind:refreshproposal; otherwise new.
2. Pick the issue — hub-bound
In this order, first hit wins:
list_tasks {kind: "issue", parentId: <blog parent>, label: "channel:blog", state: "backlog", ready: true}→locale:en, no documents attached, earliestdue.- Backlog:
list_tasks {kind: "issue", parentId: <backlog>, label: "stage:topic", state: "backlog"}— researched topics, oldest first, preferring the hub with the fewest published pieces (stage:ideachildren only when no topic exists and the idea names a hub: research it, relabel itstage:topic, then continue). Graduate it: create the publish issue as a child of<blog parent>(lanes.md§4 shape,due= the next free cadence slot fromtenant_get.recurrence— free when no otherchannel:blogissue is due then —links: [{id: <backlog child>, type: "relates"}]), postGraduated → <new ticket>on the Backlog child and mark it done (complete_taskswithworkStats; it stays in the Backlog as history), andtopics_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. - 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
- Start from the issue's target query and fan-out queries (description, or
the topic row). Fetch at least three primary sources — official
documentation, bar / court / regulator texts, published studies, a
competitor's own pages — with the browser, fetch tool or
curl -sL. - For each source record URL, fetch date, and the exact figure or sentence you will use. You need ≥ 2 numeric statistics with a source and ≥ 2 outbound citations; competitors only on facts from their own documentation.
- Product facts come only from the persona's claims we make; Free / Pro claims are checked against it, never remembered. Nothing in claims we never make enters a draft.
- Write
templates/draft-notes.md: sources, statistics, the original artifact and why it is honest, style chosen vsrecent_styles, models. - The notes are an evidence inventory, not the article outline. A draft is not obliged to display every useful source or statistic. Choose only the proof that advances the article's governing idea; keep the rest in notes for the checker, FAQ, or a later piece.
- No source reachable → no draft. Say so on the issue and stop.
4. Draft — the Blog MCP shape
Shape the story before writing sections
Write these two lines in the draft notes before prose:
- Spine:
<reader's present problem> → <what this product changes> → <the outcome the reader can now reach>. - 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:
- What changed: name the product, its reader, and the useful outcome in the first 70 words.
- Why now: one short section names the broken workflow or constraint.
- See it work: walk the through-line through the product before listing capabilities.
- How it works: explain only the mechanisms the reader needs to believe the example.
- Why trust it: put proof and the relevant limitation beside the claim they qualify.
- 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
- Show one journey, not the org chart. The main example should encounter research, drafting, checking, approval, publishing and measurement in the order a reader would. A later compact list may label those capabilities; it cannot be the article's narrative.
- Use short visual units. Paragraphs are normally one to three sentences. Give each paragraph one job. Vary sentence length, but split a sentence carrying more than one claim and its caveat.
- Write actions and outputs. Prefer
Context Blog checks the cited URL before asking for approvaltocitation integrity is handled through guardrails. Define an abstract term on first use with a subject, action, and visible result. - Headings show progression. A reader scanning only H2s should see the argument advance. Use literal, reader-facing language; avoid a sequence of feature buckets or repeated rhetorical questions.
- Evidence is load-bearing. Default to one anchor finding and, only when
it changes the conclusion, one supporting finding. Two required numeric
facts may live in the same compact proof passage. Do not create a
What the research sayssection unless the research itself is the news. Put detailed methodology in draft notes, an appendix, or the FAQ. - Caveats calibrate once. State the limitation beside the affected claim in one direct sentence. Do not repeat the full methodology or apologize for using the evidence.
- Explain the product before the category. SEO, AEO, GEO, architecture, and schema vocabulary follow the reader-visible outcome; they never form a glossary in the opening.
- Close on motion. The final paragraph returns to the spine with a new
consequence, then gives one operational CTA. Generic
learn more,subscribe, orstart your journeyendings fail this 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:
- What does the product do from input to outcome?
- Which steps are automated, which are independently checked, and which need a person?
- What does the published result contain or emit?
- What guardrail stops a bad or unsupported result?
- 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:
- Product first —
seo.answerstill gives search systems a ≤ 60-word direct summary, while the opening names the product and its concrete outcome immediately—never a definition or industry preamble. Do not present the direct-answer field as the product workflow or governing idea. - Every H2 is followed by a 40–75-word paragraph a citing engine can lift whole. Comparison · listicle · how-to carry a table or list.
- FAQ 3–5 questions, each answer 80–150 words.
- ≥ 1 original artifact (
original: true): a sample output, table, tested result or named quote the brief or your notes back. A realistic-but-fictional sample says so in the text. - Inline HTML only
<em>,<strong>,<a href="https://…">; citations as links in the text orquote.source. - Voice — the persona's adjectives, none of the anti-adjectives, the sample paragraph as the calibration; no banned phrase; first-person singular only for what the byline actually said.
- Narrative — the spine is stated once, the through-line makes the product observable, evidence supports rather than interrupts it, and the ending creates one next action. If the article still reads as a tour of features or research findings, reshape it before calling the lint.
- Specificity — after the No AI Slop pass, a reader can still identify the product's input, sequence, automated and human decisions, published outputs, stopping guardrail, measurement states and next action. A polished draft that cannot answer these questions fails.
- The byline,
Article/Person/OrganizationJSON-LD and design tokens come from the tenant's brand on the platform — do not hand-write them into sections.
5. Upsert — honour the lint
content_lint {kind: "article", …, style, hub, locale, recent_styles, brand: {banned_phrases, own_hosts}}(for a variant addmaster). Fix everyseverity: "error"; judge the warnings.article_upsert {…, dry_run: true}, then the real call. Oninvalid_argumentreaddetails.warnings, fix the namedpaths, retry. Two bounces at most (three attempts). A third rejection →post_task_update {body: "ESCALATE: article_upsert rejected 3× — <codes>"}, leave the issuestarted, and stop this piece.- Keep
id,preview_url,urlfrom the result; every later revision passes the sameid. - 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
send_file {taskId: <issue>, filename: "<slug>.<locale>.md", title: "<title> (<locale>)", docKind: "deliverable", content: <markdown rendering of the draft with front matter: article id, slug, locale, hub, style, publish_at, translation_group>}.send_file {taskId, filename: "draft-notes-<locale>-r<n>.md", title: "Draft notes, round <n>", docKind: "notes", content: <templates/draft-notes.md filled>}.
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):
- pass — the checker attached the verdict and preview, recorded the
check, and raised the
reviewRequest(statein_review). If the state or request is missing, raise it once yourself with the checker's reason format. Thenarticle_set_status {id, status: "in_review"}. - bounce — fix each finding at its
path, re-run §5 with the sameid, re-attach the deliverable and notes, and hand over again as round + 1. The checker allows two bounces; on the third it escalates. - escalate — stop this piece; the issue carries
ESCALATE:; leave the state as the checker set it and go to §10.
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):
- Reuse the variant issue if
list_tasks {parentId: <blog parent>, label: "locale:<l>"}shows one thatrelatesto this master; elsesave_work {kind: "issue", issueType: "complex", parentId: <blog parent>, 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)."}. - Draft it as localisation, not translation: examples, currency, units,
idiom and the App Store storefront link for that market; own
slug,title,description; sametranslation_group. Full = every section; reduced = title, description,seo.answer, the H2s with their lead paragraphs, the FAQ. Same model as the master. - §5 with
masterin the lint context, §6, then §7 — the checker moves a cascade variant toin_reviewwithout areviewRequest;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
post_task_update {id: <issue>, body: "Drafted <round> — article <id> · preview <url> · check <check_id> · maker <model> · checker <model> · variants: <tickets or none>", workStats}(role: "maker").- Print one line for the routine log, which the daily brief reads:
drafter: <ISO> · drafted <ticket> (<verdict>) · variants <tickets> · buffer <n>/<floor> · <ok | stopped: reason>. - Stopped early for any reason → a handoff comment on the issue (rules §7).
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 reviewRequest · more than one EN
piece per run · store or echo a key value (keys by name: BLOG_ACCESS_KEY,
OPENROUTER_API_KEY).
Changelog
- v7 v13
- feat(skills): review package = commentable markdown + clickable final preview; Instagram carousel viewer; locale variants under the EN master
- v6 v10
- feat(skills): v3 tool-name migration + new context skill (CONT-507)
- v5 v9
- fix(blog-drafter): frame Context Blog as AEO GEO automation (#3)
- v4 v8
- feat(blog-drafter): preserve specificity in no-slop pass (#2)
- v3 v7
- feat(blog-drafter): strengthen launch narrative skill (CONT-500)
- v2 v4
- feat(skills): epic structure v2 — channel parents, one Backlog, existing-epic setup + routines step, epic Runs block; manifest v4 (CONT-454, E12)
- v1 v3
- feat(skills): blog-drafter + blog-publisher v1, checker cascade, manifest v3 (CONT-434, E6)