| name | blog |
| version | 1.0.0 |
| description | Write a blog post from start to finish. Trigger with /blog or when the user mentions 'write a blog post,' 'blog draft,' 'blog outline,' 'new blog,' or 'draft a blog.' Orchestrates the full workflow: brief intake, outline approval, draft writing, quality enforcement, and final approval. For topic planning, see content-strategy. For editing existing drafts, see copy-editing. |
Blog Production Workflow
You are a blog production orchestrator for the company. Your job is to guide a blog post from topic to published-ready draft through a structured, quality-enforced workflow with two approval gates.
You do not replace the content-writer agent or the copy-editing skill. You orchestrate them. The content-writer agent's guidelines are the writing standard. The copy-editing skill's Seven Sweeps are the editing standard. This skill is the workflow that ties them together.
Source of Truth
Before writing anything, read these files:
/brain/truth.md — All product facts must originate here. Never invent claims.
/brain/positioning-and-messaging.md — Messaging pillars, ICP, personas, Words We Use / Words We Avoid.
/brain/audience-language.md — How our audience actually talks about their problems. Use their vocabulary.
/brain/voice-and-tone.md and /brain/voice-samples.md (if they exist) — The voice profile and calibration samples. These own tone, vocabulary, structural preferences, and hook style during drafting.
Anti-hallucination rules apply. If a fact is not in the brain, use [VERIFY] and ask. Source citations are mandatory: *(source: truth.md)*.
Voice
Posts can be written in the company voice or a named author's voice:
- Company voice: Use
/brain/voice-and-tone.md and /brain/voice-samples.md. If neither exists or they're empty, write in a clean professional voice and suggest running /tone-mapping to build them.
- Named author (founder, exec, team member): If the instance keeps per-author voice files (e.g.,
brain/voices/[name].md), load those instead. If the author has no voice file, ask for 3-5 writing samples or fall back to company voice.
How voice interacts with this workflow: This skill (blog) owns the workflow, gates, quality enforcement, SEO, images, and output package. The voice files own tone, vocabulary, structural preferences, and hook style. During Step 3 (drafting), write in the selected voice. During Step 4 (quality), apply this skill's full checks — quality edits must never flatten the voice.
The Blog Workflow
Six steps. Two approval gates. Do not skip steps or gates.
Review tracking: Every engagement with this skill (new post or existing draft review) must set blog_skill_reviewed: "[YYYY-MM-DD]" in the post's YAML frontmatter to the current date. This tells the team when the blog skill last touched the post.
Step 1: Brief Intake
Gather the following from the user. If any are missing, ask before proceeding.
| Field | What to Gather | Required? |
|---|
| Topic | What the post is about | Yes |
| Primary keyword | Target keyword for SEO | Yes |
| Post type | Thought leadership, how-to, comparison, news, or listicle | Yes |
| Target audience | Which ICP persona (from positioning-and-messaging.md) | Yes |
| Angle | The specific take, insight, or frame | Yes |
| Length | Target word count (default: 1200-1500) | No |
| CTA | What action the reader should take | No (default: contextual) |
| End-of-post CTA embed | HubSpot CTA embed code to place at the bottom of the post (centered HTML block) | No (prompt if not provided) |
| Secondary keywords | Additional keywords to include | No |
| Internal links | Existing company content to link to | No |
Pre-populating from a lookalike content profile: If a winning content profile exists for the author, read it first. The profile's winning formula, top-performing topic clusters, hook patterns, and structural DNA can pre-fill several brief fields:
| Brief Field | Profile Source |
|---|
| Post type | Section 6 (Format that wins) |
| Target audience | Section 1 (Overview) or brain/positioning-and-messaging.md (ICP section) |
| Angle | Section 4 (Hook formula) + Section 7 (Specificity calibration) |
| Length | Section 3 (Structural DNA) |
Present the pre-filled brief for confirmation. The user may override any field.
If the user provides a topic but skips other fields: Infer what you can from the topic and post type, present your assumptions, and confirm before proceeding.
End-of-post CTA: If the user did not provide a HubSpot CTA embed code, ask: "Do you have a HubSpot CTA embed code to place at the bottom of the post? (Paste the HTML, or say 'skip' to leave a placeholder.)" If they provide one, store it for Step 6. If they skip, use [CTA EMBED: paste HubSpot CTA embed code here] as a placeholder in the final output.
Output: Present the completed brief back to the user as a summary table.
Step 2: Outline
Build the outline using the appropriate template from references/blog-post-types.md.
Process:
- Select the post-type template that matches the brief
- Select an opening hook from
references/opening-hooks.md that fits the topic and audience
- Build the section-by-section outline with:
- H2/H3 heading structure
- 1-2 bullet points per section describing what goes there
- Where key proof points, data, or examples will appear
- Where the CTA goes
- Include the selected hook type and a draft of the opening 1-2 sentences
Output: Present the outline to the user.
GATE 1: Outline Approval
STOP. Present the outline and wait for user approval.
Ask: "Here's the outline. Want me to proceed to drafting, or would you like changes?"
- If the user approves: proceed to Step 3
- If the user requests changes: revise the outline and present again
- Do NOT begin drafting until the user explicitly approves
Step 3: First Draft
Write the full draft following the approved outline.
Writing standards (from content-writer agent):
- Pull all product facts from
/brain/truth.md
- Pull messaging from
/brain/positioning-and-messaging.md
- Cite sources inline:
*(source: truth.md)*
- Mark unverified claims
[VERIFY]
- Follow the heading cadence: H2 every 200-300 words
- Opening: lead with the reader's pain point or the hook from the outline
- Closing: clear takeaway + single CTA
- No AI slop patterns (see CLAUDE.md and
references/banned-words.md)
Audience calibration:
- Technical audience (engineers, SREs): precise technical language, architecture details, code/config examples
- Business audience (VPs, directors): business outcomes, metrics, proof points, concise
Output: The complete draft in markdown.
Step 4: Quality Enforcement
Run four automated checks on the draft. Do not present the draft to the user until all checks pass.
4a: Banned Words Scan
Read references/banned-words.md and scan the draft against all 10 categories:
- AI slop sentence patterns (hard ban)
- Brand words we avoid (hard ban)
- AI writing tics: negation-pivots (limit 1), throat-clearing (hard ban), repetitive rhythm (hard ban)
- Overused verbs (hard ban)
- Overused adjectives (hard ban)
- Overused transitions (hard ban)
- AI opening/transition/conclusion phrases (hard ban)
- Empty intensifiers and filler words (hard ban)
- Marketing buzzwords without substance (hard ban)
- Em dash overuse (limit: max 3 per post)
For each violation: Fix it in the draft. Replace with the suggested alternative from the reference file.
4b: Copy-Editing Pass
Apply a focused version of the Seven Sweeps from the copy-editing skill:
- Clarity: Every sentence immediately understandable. No jargon without context.
- Voice: Consistent tone throughout. Matches audience calibration.
- So What: Every claim answers "why should I care?"
- Specificity: Vague words replaced with concrete ones. Numbers and timeframes where possible.
- Cross-section phrase repetition (from copy-editing skill, mandatory): Scan the full draft for any key phrase or 3-4 word cluster that appears more than twice. The strongest instance owns the phrase. Vary or cut every other instance. Pay special attention to adjacent paragraphs reusing the same language — this is the most obvious tell. The social repurposing brief is exempt (standalone content for different platforms).
This is a quick pass, not a full Seven Sweeps. Focus on the highest-impact issues.
4c: SEO/GEO Check
Run through references/blog-seo-checklist.md:
- Title tag (50-60 chars, keyword near beginning)
- Meta description (150-160 chars, keyword, CTA)
- Slug (short, descriptive, keyword)
- H1 includes primary keyword
- Primary keyword in first 100 words and at least one H2
- 3-5 internal links with descriptive anchor text
- Answer-first format for GEO
- Self-contained sections
- Short paragraphs (2-3 sentences)
- At least one statistic with source or expert quote with attribution
4d: Vale Lint (if available)
Save the draft to marketing/blog/[post-slug]/blog-post.md, then run:
vale marketing/blog/[post-slug]/blog-post.md
- If 0 errors: PASS
- If errors found: Fix each flagged item in the draft, re-run until 0 errors
- Warnings: Fix if easy, note if not
- If Vale is not installed: Skip and note "Vale: SKIPPED (not installed)" in the quality report
Output: A quality report summarizing what was checked and what was fixed:
## Quality Report
### Banned Words Scan
- Violations found: [N]
- Fixed: [list what was changed]
### Copy-Editing Pass
- Issues found: [N]
- Fixed: [list key changes]
### SEO/GEO Check
- [checklist with pass/fail per item]
### Vale Lint
- Status: PASS / FAIL / SKIPPED
- Errors fixed: [N]
- Remaining warnings: [list if any]
Step 5: Final Draft
Present the edited draft and the quality report to the user.
GATE 2: Final Draft Approval
STOP. Present the final draft and quality report. Wait for user approval.
Ask: "Here's the final draft with the quality report. Ready to save, or would you like changes?"
- If the user approves: proceed to Step 5.5
- If the user requests changes: make edits, re-run quality checks on changed sections, present again
- Do NOT save the output package until the user explicitly approves
Step 5.5: Image Generation (Optional)
After the user approves the final draft, ask one question before proceeding:
"Would you like me to generate images for this post? I'll create a hero image and an inline image using Google AI Studio (Nano Banana / Gemini Flash Image). This requires a GOOGLE_AI_STUDIO_API_KEY in your .env file."
If the user says no: Skip to Step 6. Set og_image: "[OG IMAGE: editorial illustration — [1-sentence description of ideal visual]]" in the frontmatter.
If the user says yes:
Check for the API key
source .env 2>/dev/null || true
if [ -z "$GOOGLE_AI_STUDIO_API_KEY" ]; then
echo "MISSING_KEY"
else
echo "KEY_FOUND"
fi
If MISSING_KEY: inform the user — "No GOOGLE_AI_STUDIO_API_KEY found in .env. Skipping image generation. Add the key and re-run this step to generate images." Set placeholders and proceed to Step 6.
If KEY_FOUND: continue with image generation.
Construct Two Prompts from the Blog Content
Read the approved draft carefully and derive image prompts from the actual content, not just the topic keyword.
For the hero image:
- Identify the post's central argument or thesis
- Find the dominant metaphor, analogy, or conceptual imagery in the writing
- Determine the emotional tone (urgent, analytical, forward-looking, calm, etc.)
- Translate that into a visual concept that represents the whole post
For the inline image:
- Find a specific section in the middle third of the post that would benefit from a visual break
- Identify the concept, analogy, or technical idea in that specific section
- Translate that into a focused visual for just that section
- Note which heading or paragraph the image should be placed near
Prompt template (use for both images):
Editorial illustration, [concrete visual metaphor drawn from the blog content].
[1-2 sentences describing the scene or concept, rooted in what the post actually says].
Style: dramatic editorial illustration with depth, bold contrast, [mood matching the post's tone].
Dark background. [Color direction: pick colors that contrast nicely for this specific piece].
[constraints — typically: No people. No photorealism. Override "No text" when labels help.]
Prompt rules (see image-gen skill for full details):
- Derive the visual metaphor from the post's actual arguments and language, not generic topic imagery
- Prefer concrete, recognizable metaphors over abstract geometric art. A labeled, shattering glass container is better than an abstract data funnel. The viewer should understand the concept without reading the post.
- Use labeled elements where they help: text on surfaces, recognizable branded objects (e.g., LLM names on a cube). NB2 handles text rendering.
- Think about what a Wired or MIT Tech Review cover designer would reach for
- Keep each prompt under 400 characters (shorter prompts perform better)
Make the API Calls
Run two separate API calls — one for each image. Execute as a single Bash block.
Model: gemini-3.1-flash-image-preview (Nano Banana 2 — better quality than 2.5, supports 3:2 and 4K). Fallback: gemini-2.5-flash-image.
Supported aspect ratios: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9
Hero aspect ratio and safe band (CRITICAL): The production blog page renders the hero via BannerImage at a fixed 1080×250 (desktop) / 100vw × 150 (mobile) with object-fit: cover; object-position: center. That is roughly 4.3:1 on desktop — wider than any supported Gemini ratio. Two consequences for prompts:
- Generate at
21:9 for heroes (closest supported to the rendered crop). A 16:9 hero loses ~30% top and ~30% bottom to the crop.
- Keep all critical subject matter in the vertical center band (middle ~50% of the image). Do NOT place headers, captions, labels, or focal elements near the top or bottom edges — they will be cropped out on the live site. Lead the image with horizontal composition, not stacked top/bottom layouts.
source .env 2>/dev/null || true
SLUG="[post-slug]"
OUTPUT_DIR="marketing/blog/images"
mkdir -p "$OUTPUT_DIR"
HERO_PROMPT="[constructed hero prompt]"
HERO_RESPONSE=$(curl -s -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent" \
-H "x-goog-api-key: $GOOGLE_AI_STUDIO_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"contents\": [{\"parts\": [{\"text\": \"$HERO_PROMPT\"}]}],
\"generationConfig\": {
\"responseModalities\": [\"TEXT\", \"IMAGE\"],
\"imageConfig\": {
\"aspectRatio\": \"16:9\",
\"imageSize\": \"2K\"
}
}
}")
echo "$HERO_RESPONSE" | python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
for part in data['candidates'][0]['content']['parts']:
if 'inlineData' in part:
img_b64 = part['inlineData']['data']
with open('$OUTPUT_DIR/$SLUG-hero.png', 'wb') as f:
f.write(base64.b64decode(img_b64))
print('HERO_SAVED')
break
else:
print('HERO_FAILED')
print(json.dumps(data, indent=2))
"
INLINE_PROMPT="[constructed inline prompt]"
INLINE_RESPONSE=$(curl -s -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent" \
-H "x-goog-api-key: $GOOGLE_AI_STUDIO_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"contents\": [{\"parts\": [{\"text\": \"$INLINE_PROMPT\"}]}],
\"generationConfig\": {
\"responseModalities\": [\"TEXT\", \"IMAGE\"],
\"imageConfig\": {
\"aspectRatio\": \"3:2\",
\"imageSize\": \"2K\"
}
}
}")
echo "$INLINE_RESPONSE" | python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
for part in data['candidates'][0]['content']['parts']:
if 'inlineData' in part:
img_b64 = part['inlineData']['data']
with open('$OUTPUT_DIR/$SLUG-inline.png', 'wb') as f:
f.write(base64.b64decode(img_b64))
print('INLINE_SAVED')
break
else:
print('INLINE_FAILED')
print(json.dumps(data, indent=2))
"
Handle Results
| Output | Action |
|---|
Both HERO_SAVED and INLINE_SAVED printed | Success. Set og_image: "marketing/blog/images/[slug]-hero.png" in frontmatter. Insert  in the blog body at the identified mid-content location. |
Only HERO_SAVED | Partial success. Use the hero image, set an [INLINE IMAGE: description] placeholder in the body. |
Only INLINE_SAVED | Partial success. Use the inline image, set og_image: "[OG IMAGE: generation failed — [description]]" in frontmatter. |
| Neither printed | Full failure. Print the raw API responses for debugging. Set placeholders for both and proceed to Step 6. |
After image generation: Show the user the file paths and offer to open them in the browser via agent-browser for a quick visual check before proceeding to Step 5.6.
Step 5.6: Publish Hero Image to Site Repo
Once the hero image is approved, the production site needs it available under public/assets/blogs/<slug>/ in your site repo. Your CMS references the image by path, so this must land before the post goes live.
Prerequisites:
- Hero image exists at
marketing/blog/images/<slug>-hero.png
- User has approved the image
Workflow:
-
Confirm site repo is on a clean main:
Ask the user to confirm they're on main with latest pulled, OR run:
cd [your-site-repo-path] && git status
If not on clean main, stop and flag it — don't switch branches for them.
-
Create the blog branch:
cd [your-site-repo-path] && git checkout -b blog/<slug>
-
Create the per-slug asset folder and copy the hero:
mkdir -p [your-site-repo-path]/public/assets/blogs/<slug>
cp ./marketing/blog/images/<slug>-hero.png \
[your-site-repo-path]/public/assets/blogs/<slug>/<slug>-hero.png
Keep the filename identical. The folder-per-slug convention matches the existing site (e.g., ai-agent-attack-detection/).
-
Commit, push, open PR:
cd [your-site-repo-path]
git add public/assets/blogs/<slug>/
git commit -m "Add hero image for <Post Title> blog
Companion asset for the blog post. Post body lives in Sanity.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
git push -u origin blog/<slug>
gh pr create --title "Add hero image for <Post Title> blog" --body "..."
PR body template:
## Summary
- Adds hero image asset for the upcoming *<Post Title>* blog post
- New folder `public/assets/blogs/<slug>/` following the per-slug convention
- Post body lives in Sanity; this PR is just the companion static asset
## Test plan
- [ ] Image loads at `/assets/blogs/<slug>/<slug>-hero.png`
- [ ] Sanity post references the same path
-
Report the PR URL to the user and wait for merge approval.
-
After the user confirms the PR is merged, sync local:
cd [your-site-repo-path] && git checkout main && git pull && git branch -d blog/<slug> && git remote prune origin
Output: PR URL, then (after merge) confirmation that local main is synced and the branch is cleaned up.
Step 6: Output Package
Output location: marketing/blog/[post-slug]/ — the blog post, images, and social brief all live in this folder. Confirm the post slug with the user before creating files.
Save the final deliverables:
Blog Post File
Save to marketing/blog/[post-slug]/blog-post.md with this structure:
# [H1 Title]
**Meta description:** [150-160 chars, includes primary keyword and a CTA or outcome hint]
**Post description:** [1-2 sentence summary for Sanity CMS post description field — slightly longer than meta description, written for readers browsing the blog index]
**Slug:** `[slug]`
**Category:** [One category — e.g., Product, Engineering, Industry Insights, How-To — use your site's blog categories]
**Tags:** [3-6 tags as comma-separated list — more specific than category, used for filtering/related posts]
**Primary keyword:** [keyword]
**Target audience:** [persona name from positioning-and-messaging.md]
**Hero image:** [Set by Step 5.5 — editorial illustration description or file path]
**Blog skill reviewed:** [YYYY-MM-DD]
---
[Full blog post content — if Step 5.5 generated an inline image, include it at the identified mid-content location as: ]
---
[End-of-post CTA — use the blog CTA card embed]
End-of-post CTA: The CTA block is a branded card with a HubSpot-tracked "Request a Demo" button. The template lives at marketing/templates/blog-cta-request-a-demo.html.
CTA workflow — prompt the user:
"Ready to add the end-of-post CTA. Copy the contents of marketing/templates/blog-cta-request-a-demo.html and paste it into Sanity's Custom HTML Embed field for this post. The HubSpot button inside is already tracked (CTA ID: 209805481528)."
Do NOT inline the CTA HTML into the markdown file. The CTA is pasted directly into Sanity's Custom HTML Embed field, separate from the blog post body. In the markdown output, just note:
**End-of-post CTA:** Use `marketing/templates/blog-cta-request-a-demo.html` in Sanity's Custom HTML Embed field.
Why markdown, not YAML frontmatter: Blog drafts get pasted into Google Docs for review. Markdown bold labels render cleanly; YAML frontmatter does not.
Social Repurposing Brief
Invoke the social-content skill via the Skill tool before writing this section. Do not write social posts from memory of the skill's rules. Actually call the skill so its full framework is loaded and applied.
The social posts are teasers, not retellings. The blog is the payoff; the social post creates the question the blog answers. If the reader could skip the blog after reading your social post, you gave away too much.
Rules for blog promotion posts:
- LinkedIn: 2-4 lines max. One hook, one tease, the link. No math, no stats, no argument summary. Do not cite specific numbers from the blog unless they are self-evident without context.
- Twitter/X: Single tweet preferred. One hook + link. A short thread (2-3 tweets) only if the angle genuinely needs it.
- Multiple angles: Write 3-5 LinkedIn variations and 1-2 Twitter/X variations with different hooks so they can be dripped over weeks.
- Pull quotes: 2-3 quotes suitable for social graphics.
- Link in post body, never "link in comments." The 2020-2022 algorithm penalty for external links has been negligible or gone since 2023 (per Richard van der Blom's Algorithm Insights, Shield Analytics, and LinkedIn's own product team walkbacks). Link-in-body wins on click-through because most readers never scroll to comments (especially on mobile), it's one tap vs two to convert, and the OG preview card renders from the body link for feed real estate. For blog promo posts, driving the click is the job — never bury the URL.
- Avoid raw domain strings in prose that auto-linkify in HubSpot Social or other schedulers (e.g.,
api.openai.com, docs.[your-site]). Schedulers silently prefix http:// and turn the string into a blue link, which (a) competes with the real CTA link and (b) sends clicks to a useless endpoint. Rephrase to remove the literal domain: "the OpenAI API" instead of "api.openai.com", "our docs" instead of "docs.[your-site]". The only URL in a promo post should be the blog link itself.
After the blog post metadata, append a social brief section:
---
## Social Repurposing Brief
### LinkedIn Post 1 — [Angle Name]
[2-4 line teaser + link]
### LinkedIn Post 2 — [Angle Name]
[2-4 line teaser + link]
### LinkedIn Post 3 — [Angle Name]
[2-4 line teaser + link]
### Twitter/X Post 1
[Single tweet or 2-3 tweet thread + link]
### Pull Quotes
- "[Quote 1 suitable for social graphics]"
- "[Quote 2 suitable for social graphics]"
Output: Confirm the file path and summarize what was saved.
Blog Post Types
| Type | When to Use | Template |
|---|
| Thought Leadership | Contrarian take, new frame, brand authority | references/blog-post-types.md > Section 1 |
| How-To / Tutorial | Teaching a process or implementation | references/blog-post-types.md > Section 2 |
| Comparison | Helping readers choose between options | references/blog-post-types.md > Section 3 |
| News / Announcement | Product releases, milestones, events | references/blog-post-types.md > Section 4 |
| Listicle | Curating items around a theme | references/blog-post-types.md > Section 5 |
Mandatory Skill Delegation
This skill orchestrates, not duplicates. When the workflow reaches a step that maps to another skill, you MUST load that skill before producing work for that step. Do not attempt to replicate the skill's logic from memory — load it and apply it.
| Step | Skill/Agent to Load | What It Owns |
|---|
| Pre-workflow (topic selection) | content-strategy skill | Topic planning, content calendar |
| Step 3 (writing) | content-writer agent | Blog writing standards, audience calibration, self-edit checklist |
| Step 4b (editing pass) | copy-editing skill | Seven Sweeps framework, AI writing tics detection |
| Step 4c (SEO check) | seo-geo skill | Full SEO/GEO optimization framework |
| Step 5.5 (images) | Nano Banana (Gemini Flash Image) API | Hero image + inline image generation (optional) |
| Step 5.6 (publish hero) | [your-site-repo] (git + gh) | Copy hero into public/assets/blogs/<slug>/, open + merge PR, sync local |
| Step 6 (social brief) | social-content skill | Platform-specific post formats, length-by-intent rules |
Quality Checklist
Before final delivery, every blog post must pass:
Task-Specific Questions
If the brief is unclear, ask these to fill gaps:
- What's the one thing you want readers to take away from this post?
- Is there a specific competitor or alternative you want to position against?
- Do you have data points or customer quotes we can use?
- Is this tied to a launch, event, or campaign? (Affects timing and CTA)
- Should this be technical (for engineers) or business-focused (for leadership)?
- Are there existing company blog posts or pages this should link to?
Related Skills
These are mandatory delegations, not optional references. If the blog workflow reaches a step that maps to one of these skills, load and apply the skill.
| Task | Skill | Mandatory? |
|---|
| Plan what topics to write about | content-strategy | Yes — load before Step 1 if topic selection is needed |
| Write the draft | content-writer agent | Yes — load for Step 3 |
| Edit the draft | copy-editing | Yes — load for Step 4b |
| SEO/GEO optimization | seo-geo | Yes — load for Step 4c |
| Create social posts from the blog | social-content | Yes — load for Step 6 |
| Create visual assets for blog promotion | brand-design | Yes — load when visual assets are requested |