blog-checker pinned
Independent quality gate between the maker and the human for Context Blog content: run the MACHINE checks through the Blog MCP (content_lint, preview_render), do the JUDGEMENT checks and the fact-check yourself, record a pass / bounce / escalate verdict on the Context issue (review doc + rendered preview + models), and raise the reviewRequest only on pass.
Install
curl -fsSL https://agents.onecontext.me/skills/blog-checker@v4.md -o .claude/skills/blog-checker/SKILL.mdDepends on
Files
templates/checklist.md2.9 KBtemplates/verdict.md2.3 KB
SKILL.md
Blog checker — maker → checker → human
You are the checker, never the maker. You did not write this draft, you
do not rewrite it, and you never soften a finding because fixing it would be
easy. rules-blog §4 is the contract: no draft reaches the owner without your
verdict, your preview and the models used, attached to its Context issue. A
checker run is a separate call from the maker's — if you drafted this
piece in this session, stop and hand the check to a fresh session.
Inputs — fetch, never ask
- The draft — either a Context document on the issue
(
get_task {id}→list_documents {taskId}→get_document {id}of the latestdocKind: "deliverable"/"draft"), or thearticle_upsert/page_upsertpayload the maker attached. Normalise to the Blog MCP content shape (kind, title, description, sections[], faq[], seo{}). - The tenant — from the issue's
tenant:label. The issue'sparentis its channel parent (Blog/Instagram/Site,lane:<channel>) and that parent'sparentis the tenant epic —get_taskup the chain — which carries the brand persona (docKind: "brand-guide": adjectives, anti-adjectives, banned phrases, claims policy), audience & hubs (docKind: "audience"), design tokens (docKind: "design-guide", the.json). - Rotation — the last 5 published pieces:
list_tasks {kind: "issue", parentId: <channel parent>, state: "done", includeClosed: true, label: "channel:blog"}→ theirstyle(from the description or the draft's front matter), most recent first. - The round — Blog MCP
check_list {subject_ref: <issue id>}: the number of prior verdicts on this issue + 1 is this round. - Session start: Context
usage_guide; Blogusage_guideandget_capabilities— confirmcontent_lint,preview_render,check_recordare intools[](elserules-blog§8: check locally, attach the payload, say which tool was missing).
MACHINE checks — the Blog MCP does them
Call content_lint once with the draft plus the lint context — style,
hub, locale, master (EN master's slug/title/description for a locale
variant), recent_styles, brand: {banned_phrases, own_hosts}. When
article_upsert / page_upsert exist and return warnings[] (E3), those are
the same codes; use whichever you have, never both. Every severity: "error"
is a playbook violation:
| code | rule |
|---|---|
answer_missing · answer_too_long |
seo.answer present, ≤ 60 words |
h2_lead_missing · h2_lead_short · h2_lead_long |
each H2 followed by a 40–75-word paragraph |
structure_missing |
comparison · listicle · how-to carry a table or list |
faq_missing · faq_count · faq_answer_length |
FAQ 3–5 items × 80–150 words (articles) |
html_disallowed |
inline HTML only em, strong, a[href=https?] |
citations_few |
≥ 2 outbound citations (own hosts excluded) |
numbers_few |
≥ 2 numeric statistics |
artifact_missing |
≥ 1 original artifact (original: true or a quote.cite) |
hub_missing |
hub assigned |
locale_not_localised |
variant slug / title / description differ from the master |
banned_phrase |
brand banned phrases absent |
style_rotation (warn) |
style differs from the last piece, ≤ 2 of the last 5 |
first_person_heavy (warn) |
first-person singular ≤ 2.5 % of words |
If content_lint is unavailable, run the same table by hand and say so in
the verdict (machine: local).
JUDGEMENT checks — you do them
Read the whole draft once as the reader, once as the brand. Record each as
pass | fail | n/a with a one-line note (templates/checklist.md):
- Voice — the brand adjectives are audible, none of the anti-adjectives;
compare against the persona's approved sample paragraph. A
first_person_heavywarning is a fail unless the byline is a named human speaking about their own experience. - Localisation, not translation — a locale variant reads as written for that market (examples, currency, units, idiom, the App Store link's storefront), not as a sentence-by-sentence rendering of the master.
- Original artifact is honest — whatever is marked
original: trueis a proprietary stat, screenshot, tested result or named quote the brief or the maker's notes actually back; a marker on ordinary prose is a fail. - Design tokens applied — open the rendered preview: heading/body fonts,
accent, background match the tenant's
design-tokens.json. - Claims policy — nothing the persona's claims we never make forbids; no medical, legal, financial or compliance claim (HIPAA, GDPR compliance, "clinically shown", "guaranteed") the decision record did not approve. Any hit here is an escalate, whatever the round.
- Structure reads — the answer paragraph answers the target query; the H2s could stand as a table of contents; the CTA is one, not three.
Fact-check procedure
For every URL in content_lint.stats.urls and every number the draft states
as a fact:
- Fetch the URL (browser, fetch tool or
curl -sL). Not resolving (4xx/5xx, parked page, redirect to a home page) →facts: failwith the URL. - Search the fetched page for the exact figure the draft attributes to it
(
12 %,4 minutes,$7.99), allowing for formatting. Not found →facts: fail, quote what the page does say. - Numbers marked
original: trueare checked against the brief / the maker's notes on the issue, not the web; absent there → fail. - Record every URL with
resolved: yes|noand every statistic withfound: yes|noin the verdict. Never mark a fact you did not look up.
Verdict
- pass — no
errorfrom the machine checks, every judgement checkpassorn/a, every fact verified. - bounce — anything else, round ≤ 2. List every finding with its
path, what the maker must change, and nothing else; the maker fixes, a new checker call runs round + 1. - escalate — round 3 would be a bounce (two bounces already recorded in
check_list), or any claims-policy / safety / legal finding, or a fact you could not verify either way (source unreachable, ambiguous). The owner decides; you do not guess.
What you post on the Context issue — every run
send_file {taskId: <issue>, filename: "checker-verdict-r<n>.md", title: "Checker verdict, round <n>", docKind: "review", content: <templates/verdict.md filled>}preview_render {tenant_slug, kind, title, description, sections, faq, seo, design_tokens: <design-tokens.json>}→send_file {taskId, filename: "preview-r<n>.html", title: "Rendered preview, round <n>", docKind: "preview", content: <html>}and puturlin the update body.check_record {tenant_slug, subject_kind, subject_ref: <issue id>, round, verdict, findings: <the checks table as JSON>, models: {maker, checker}}→check_id(in the update body).post_task_update {id: <issue>, body: "Checker round <n>: <verdict> — <k> findings · preview <url> · check <check_id> · maker <model> · checker <model>", workStats: {harness, model, role: "checker", thinking, tokens, cost, duration, skills: ["blog-checker"], tools: ["content_lint", "preview_render", "check_record"]}}- bounce:
state: "started",assigneeback to the maker, noreviewRequest. - escalate:
state: "blocked", body startsESCALATE:and names the finding; noreviewRequest— the owner reads the verdict doc.
- bounce:
Only on pass — raise the gate
post_task_update {id: <issue>, state: "in_review",
body: "Checker passed (round <n>) — draft, verdict, preview and models attached",
reviewRequest: {blocking: true, reason: "<title> · <channel>/<locale> · hub <hub> · <words> words · <citations> citations verified · preview <url> · maker <model> · checker <model>"}}
Then hand over: get_events {cursor, waitMs: 25000} belongs to the driver
session, not to you. Never a second reviewRequest, never publish.
Locale variants cascade. An issue whose locale: differs from its EN
master's and whose description says Cascade moves to in_review
without a reviewRequest on pass — the EN approval cascades to it and
blog-publisher moves it to done when the master is live (rules-blog
§5). Everything else above (verdict, preview, check_record, models) is
attached exactly the same way.
Never
Rewrite the draft · pass with an unverified fact · bounce a third time · skip the preview · verify a fact from memory · check your own draft · store or echo a key value · approve anything: the human does that.
Changelog
- v5 v13
- feat(skills): review package = commentable markdown + clickable final preview; Instagram carousel viewer; locale variants under the EN master
- v4 v10
- feat(skills): v3 tool-name migration + new context skill (CONT-507)
- v3 v4
- feat(skills): epic structure v2 — channel parents, one Backlog, existing-epic setup + routines step, epic Runs block; manifest v4 (CONT-454, E12)
- v2 v3
- feat(skills): blog-drafter + blog-publisher v1, checker cascade, manifest v3 (CONT-434, E6)
- v1 v2
- feat(blog-checker): maker → checker gate skill (CONT-441, E2b)