blog-checker latest

v5v13updated MITsha256 2fbff0131f2f

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 request_review only on pass.


Raw .mdversions.jsonPermalinkSource

Install

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

Depends on

rules-blogrules

Files

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

  1. The draft — either a Context Artifact on the issue (get_issue {id}list_artifacts {parent_id}get_artifact {id} of the latest docKind: "deliverable" / "draft"), or the article_upsert / page_upsert payload the maker attached. Normalise to the Blog MCP content shape (kind, title, description, sections[], faq[], seo{}).
  2. The tenant — from the issue's tenant: label. The issue's parent is its channel parent (Blog / Instagram / Site, lane:<channel>) and that parent's parent is the tenant epic — get_issue / get_epic up 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).
  3. Rotation — the last 5 published pieces: list_issues {parent_id: <channel parent>, state: "done", includeClosed: true, label: "channel:blog"} → their style (from the description or the draft's front matter), most recent first.
  4. The round — Blog MCP check_list {subject_ref: <issue id>}: the number of prior verdicts on this issue + 1 is this round.
  5. Session start: Context start_context; Blog MCP usage_guide and get_capabilities — confirm content_lint, preview_render, check_record are in tools[] (else rules-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):

Fact-check procedure

For every URL in content_lint.stats.urls and every number the draft states as a fact:

  1. Fetch the URL (browser, fetch tool or curl -sL). Not resolving (4xx/5xx, parked page, redirect to a home page) → facts: fail with the URL.
  2. 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.
  3. Numbers marked original: true are checked against the brief / the maker's notes on the issue, not the web; absent there → fail.
  4. Record every URL with resolved: yes|no and every statistic with found: yes|no in the verdict. Never mark a fact you did not look up.

Verdict

What you post on the Context issue — every run

  1. attach_artifact {parent_id: <issue>, filename: "checker-verdict-r<n>.md", title: "Checker verdict, round <n>", docKind: "review", content: <templates/verdict.md filled>}
  2. The final rendered preview the owner clicks (rules-blog §4.3). Blog: preview_render {tenant_slug, kind, title, description, sections, faq, seo, design_tokens: <design-tokens.json>}attach_artifact {parent_id, filename: "<slug>.<locale>.preview.html", title: "Preview — <title>", docKind: "preview", content: <html>}. Instagram: confirm the maker's <TICKET>-carousel.html (Instagram-style viewer over the asset URLs) is attached, opens, and every slide src matches an asset id on the post; re-attach it if a bounce changed slides. Put the artifact url in the update body and in the review reason. Bounce a blog with no preview or an Instagram post with per-slide image artifacts instead of the viewer.
  3. 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).
  4. post_comment {parent_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: update_issues {ids: [<issue>], state: "in_progress", assignee: <maker>}, no request_review.
    • escalate: update_issues {ids: [<issue>], state: "blocked"}, body starts ESCALATE: and names the finding; no request_review — the owner reads the verdict doc.

Only on pass — raise the gate

update_issues {ids: [<issue>], state: "in_review"}
post_comment {parent_id: <issue>, body: "Checker passed (round <n>) — draft, verdict, preview and models attached"}
request_review {parent_id: <issue>, reason: "<title> · <channel>/<locale> · hub <hub> · <words> words · <citations> citations verified · preview <url> · maker <model> · checker <model>"}

Then hand over: get_changes {cursor, waitMs: 25000} belongs to the driver session, not to you. Never a second request_review, 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 request_review 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