name: wr-blog:create-social-posts description: Generate platform-specific social posts for a published windyroad blog article. Covers LinkedIn, Twitter, Bluesky, Hacker News, Lobsters, Reddit, and dev.to. Applies the windyroad voice gate, content-risk gate, wr-blog-social-critic loop, and cognitive-accessibility gate per platform.
wr-blog:create-social-posts
Generate platform-specific social posts for an article that has already been drafted and committed to src/articles/<slug>/index.md. Each post lands in src/social/<slug>/<platform>.md and is reviewed by fresh-context subagents before save.
Inputs
article_path: required. The path to the published article, e.g.src/articles/an-ai-agent-deleted-production-the-model-wasnt-the-problem/index.md.cover_image_path: optional. The cover image to reference from social posts (defaults tocover.pngin the social folder, or the first SVG render underpublic/img/social/matching the article slug).platforms: optional. Defaults to all seven (linkedin, twitter, bluesky, hackernews, lobsters, reddit, devto). Can be overridden to skip platforms.
Outputs
For an article at src/articles/<slug>/index.md, this skill writes:
src/social/<slug>/linkedin.mdsrc/social/<slug>/twitter.mdsrc/social/<slug>/bluesky.mdsrc/social/<slug>/hackernews.mdsrc/social/<slug>/lobsters.mdsrc/social/<slug>/reddit-<subreddit>.md(one per target subreddit)src/social/<slug>/devto.mdsrc/social/<slug>/cover.png(if not already present, copied from the article's primary social SVG render)
Step sequence
0. Load context
Read these files into context before drafting any post:
- The article itself at
article_path. docs/VOICE-AND-TONE.md(voice rules, banned patterns, per-platform conventions if documented).src/social/<slug>/if it already exists, to avoid clobbering work in progress.- The folder
src/social/enforcing-voice-and-tone-with-claude-code-hooks/as a reference set of seven platform posts done correctly. assets/social-platform-conventions.md(this skill's reference, see below).assets/social-critic-rubric.md(this skill's review rubric).
If the article has not yet been published (i.e. the build does not include it), warn the user and stop. Social posts that link to an unpublished article will 404.
1. Confirm canonical URL
The canonical URL is https://windyroad.com.au/blog/<slug>. Verify the slug by running:
node -e "const s = require('slugify'); console.log(s('<title>', { lower: true, strict: true }));"
against the article's H1, and confirm it matches the directory name under src/articles/. If the slug includes an apostrophe or trailing punctuation, check that src/lib/markdown.ts slugFromTitle() produced the expected output. Net stale URLs are the most common avoidable error in this skill.
2. Cover image
If src/social/<slug>/cover.png is missing:
Identify the article's primary social diagram. Convention: the first SVG referenced in the article body, or
public/img/social/<slug-prefix>.svgif a per-article render exists.Render to PNG via
node scripts/render-svg.mjs <input.svg> src/social/<slug>/cover.png.Read the PNG via the Read tool and confirm visually that text fits, arrows land on targets, and the palette matches existing diagrams. The diagram-inspection-checklist in the sibling
wr-blog:render-diagramsskill applies.Contrast audit (mandatory). Delegate to the
contrast-mastersubagent against the SVG source. Audit every text-on-background pair (4.5:1 AA for normal text, 3:1 for large text >=18px or >=14px bold) and every UI border pair (3:1). Common windyroad-palette failure patterns to watch for:- Alpha-channel suffixes on subtext fills (
#XXXXXX80,#XXXXXX90) drop into the 3:1 to 4.4:1 band on saturated dark fills. Use full-alpha hex; rely on font size and weight for hierarchy. - Slate-500 (
#64748B) body text on slate-900 (#0F172A) page bg or slate-800 (#1E293B) card bg fails 4.5:1. Use slate-400 (#94A3B8) or lighter. - Saturated dark borders (
#7F1D1Dred-800,#1E40AFblue-700,#334155slate-700) on the page or card bg fail 3:1. Use red-600 (#DC2626), blue-500 (#3B82F6), slate-500 (#64748B) instead.
Apply every measured FAIL fix to the SVG before re-rendering the PNG. Re-run the audit after fixes to confirm AA compliance. Do not ship a cover image with known contrast failures.
- Alpha-channel suffixes on subtext fills (
2.5. Article diagrams
If the article includes additional SVG diagrams beyond the cover, run the contrast audit (step 2.4) against each one. The same patterns apply across the windyroad palette. Article body diagrams are visitor-facing accessibility surfaces, not just social-share assets.
3. Per-platform draft
Draft each platform post using the conventions in assets/social-platform-conventions.md. Summary of the conventions (full text in the asset):
- LinkedIn: long-form. 3 to 4 paragraphs. Lead with a concrete fact, walk the structural argument, end with a CTA pointing at the article. Image: cover.png. Frontmatter:
platform,article,image,image_alt. Tone: practitioner sharing. - Twitter / X: terse. One-tweet hook (under 280 chars) plus a reply tweet that is the article URL. Image: cover.png. Frontmatter:
platform,article,image,image_alt. Tone: punchy. - Bluesky: similar shape to Twitter but slightly looser (300 char limit). One post plus URL on its own line. Frontmatter:
platform,article,image,image_alt. Tone: practitioner. - Hacker News: link post only. Frontmatter:
platform,article,url,title,type: link. Title is the article title verbatim, with sentence case. No body. - Lobsters: link post with tags. Frontmatter:
platform,article,url,title,tags,type: link. Tags are picked from the lobsters tag list (commonlyai,security,devops,practices). - Reddit: per-subreddit text post. Frontmatter:
platform: reddit,subreddit,flair,crosspost(optional),article,title. Body is 3 to 5 paragraphs ending with a question that invites discussion. Lead with the strongest concrete claim. Title style varies per subreddit (check the subreddit's recent posts for tone). - dev.to: cross-post the full article body. Frontmatter:
platform: devto,article,canonical_url,title,tags(max 4),cover_image,cover_image_alt,published: false. Body is the article verbatim, with<span data-pull>blocks expanded into blockquotes (dev.to does not render the data-pull markup).
4. Apply voice rules to every post
Before save, every post must pass these mechanical checks:
- No em-dashes (U+2014). Use commas, periods, colons, semicolons, or parentheses.
- No avoided words. Run the project's voice-tone check or grep for the banned-pattern list in
docs/VOICE-AND-TONE.md. - No ambiguous link text. Replace any "click here", "read more", "learn more", or bare "(here)" with descriptive anchor text.
- No visible "(opens in new tab)" text on any anchor. If the platform supports HTML, use sr-only span; otherwise rely on the platform's default.
- Reading level: opener and closing sentences at Grade 9 to 10. Mechanism sections may be denser.
If any check fails, rewrite that sentence and re-run the check.
4.5. Platform character-limit check (mandatory)
For platforms with a hard character limit, count the post and confirm it fits. URL counting differs per platform; refer to assets/social-platform-conventions.md for the per-platform rule. Hard limits and counting:
- Twitter / X: 280 chars. URLs auto-shorten to
t.co~23 chars. Hook + URL + space must be <= 280. - Bluesky: 300 chars. URLs are counted as their full length (Bluesky does NOT shorten). Body + 2 (blank line) + URL.length must be <= 300. For an 88-char canonical URL, body budget is ~210 chars.
- LinkedIn: 3000 chars. Effectively unbounded for our long-form posts; no check needed.
- Reddit: 40000 chars body. Effectively unbounded.
- Hacker News / Lobsters: link-only, no body.
- dev.to: no length limit on the body.
For Twitter and Bluesky, count the actual saved post (body + URL + newlines). If over budget, tighten the body and re-count. Do not save a post that exceeds the platform limit; the platform composer will reject it on paste-in or display a negative count (e.g. Bluesky's -30).
5. Voice gate (subagent)
For every drafted post, invoke a fresh-context wr-voice-tone:agent subagent with the post path. Apply findings. If findings include items the gate considers blocking, the post does not save until those are addressed.
6. Content-risk gate (subagent)
For LinkedIn and Reddit (the two long-form posts that name vendors and incidents), invoke a fresh-context wr-content-risk-scorer:agent subagent. Twitter, Bluesky, dev.to, Hacker News, and Lobsters reuse the article's already-cleared content-risk verdict because they either link out or duplicate the article body verbatim.
7. Per-platform critic (subagent, optional but recommended)
For each long-form post (LinkedIn, Reddit), invoke the wr-blog-social-critic subagent (it reads its own rubric at assets/social-critic-rubric.md). Up to 2 rounds. Apply findings. dev.to reuses the article's already-cleared critic verdict because the body is the article verbatim. Short-form posts (Twitter, Bluesky, Hacker News, Lobsters) skip the critic loop because the surface area is too small to score meaningfully.
8. Cognitive accessibility gate (subagent)
Invoke a fresh-context cognitive-accessibility subagent against each post that has its own body text:
- LinkedIn: full pass (opener and CTA at Grade 9 to 10, jargon load, plain English).
- Reddit: full pass (same checks, plus the closing question).
- Twitter / Bluesky: opener-only pass. Confirm the hook reads at Grade 9 to 10. The body is too short for jargon-load scoring.
- dev.to: skipped. Reuses the article's already-cleared cog-a11y verdict because the body is the article verbatim.
- Hacker News / Lobsters: skipped. Link posts have no body to score.
Apply findings. If a post's opener fails the Grade 9 to 10 check, rewrite and re-run.
9. Save
Write each post to src/social/<slug>/<platform>.md. Confirm by listing the directory.
10. Commit
Create a changeset in .changeset/ describing the social-post addition. The risk-scorer hooks fire on commit. No bypass. Commit message follows Conventional Commits: docs(social): add platform posts for <article-slug>.
11. Push (optional)
If the user asks for preview, push via npm run push:watch. Do not run bare git push.
Subagent contracts
Each subagent invocation follows the Generator-Reviewer pattern in PRINCIPLES.md: the orchestrator never inline-authors a PASS block. Findings are the subagent's output verbatim. Apply findings, then re-invoke if a second pass is warranted.
Files this skill writes
src/social/<slug>/<platform>.md(one per platform).src/social/<slug>/cover.png(if missing)..changeset/<random-name>.md(a docs changeset).
Files this skill never writes
src/articles/<slug>/index.md(usewr-blog:review-articlefor revisions to the article body).public/img/social/*.svg(usewr-blog:render-diagramsfor diagram changes).docs/VOICE-AND-TONE.md(usewr-voice-tone:update-guide).
Failure modes and recovery
- Slug mismatch: caught at step 1. Stop, ask the user, do not silently produce posts that link to a 404.
- Cover image overflow: caught at step 2. Re-run
wr-blog:render-diagramswith the offending SVG and iterate. - Voice gate failure: caught at step 5. Rewrite the offending sentence, re-run the gate.
- Hook block on em-dash: pre-commit hook may still fire if a stray em-dash slipped through. Search the file for U+2014 and replace.
Reference: social platform conventions
Detail in assets/social-platform-conventions.md. That asset is loaded on every invocation.
Reference: social critic rubric
Detail in assets/social-critic-rubric.md. The wr-blog-social-critic subagent reads it for long-form posts at step 7.