Content Writer
Generate full publication-ready article drafts with brand-consistent voice, Eleventy frontmatter, JSON-LD structured data, and optional FAQ sections. Content is validated against the brand guide and presented for user approval before writing to disk.
Headless Mode Detection
If $ARGUMENTS contains --headless, set HEADLESS_MODE=true and strip --headless from $ARGUMENTS. The remainder is the topic/arguments.
Argument format: <topic> [--outline <outline>] [--keywords <keywords>] [--audience <audience>] [--headless]
Headless defaults for interactive gates:
- Phase 3 (User Approval): auto-selects Accept when all citations are PASS or SOURCED. When any citation is FAIL, auto-selects Fix — removes or replaces the failed claims, re-runs fact-checker, and accepts only when all claims pass (max 2 fix cycles, then accepts with UNSOURCED markers for any remaining failures).
- If citation verification was skipped (fact-checker unavailable), auto-selects Accept with a warning in the issue.
Phase 0: Prerequisites
<critical_sequence>
Before generating content, verify both prerequisites. If either fails, display the error message and stop.
1. Brand Guide
Check if knowledge-base/marketing/brand-guide.md exists.
If missing:
No brand guide found. Run the brand-architect agent first to establish brand identity:
Use the brand-architect agent to define our brand.
Stop execution.
2. Blog Infrastructure
Check if an Eleventy config file exists (eleventy.config.js or .eleventy.js).
If missing:
No Eleventy config found. Run the docs-site skill to scaffold blog infrastructure first.
Stop execution.
</critical_sequence>
Phase 1: Parse Input
Parse the arguments provided after the skill name:
<topic>(required): the article topic or title--outline "..."(optional): article structure as inline text (Markdown list format)--keywords "kw1, kw2, kw3"(optional): target keywords, comma-separated--path <output-path>(optional): where to write the file--audience "technical|general"(optional): audience register from brand guide.technicaluses engineering vocabulary and developer proof points.generaluses plain language and business-outcome proof points. Defaults to channel-appropriate (blog → technical, landing page → general).
Default output path (if --path not provided): auto-generate from topic slug as plugins/soleur/docs/blog/YYYY-MM-DD-<slug>.md.
Phase 2: Generate Draft
Read the brand guide sections that inform content generation:
- Read
## Voice-- apply brand voice, tone, do's and don'ts - Read
## Channel Notes > ### Blog-- apply blog-specific guidelines (if the section exists) - Read
## Identity-- use mission and positioning for content alignment - If
--audienceis set, read### Audience Voice Profilesfrom brand guide and apply the matching register's vocabulary, explanation depth, and proof point selection rules. If--audienceis not set, infer from--pathor topic context (blog posts default totechnical, landing pages and onboarding content default togeneral).
Generate a full article draft that:
- Follows the brand voice from
## Voice - Incorporates target keywords naturally (if
--keywordsprovided) - Follows the provided outline structure (if
--outlineprovided) - Links every external source at its FIRST mention in the body — when the article quotes a person, adopts a coined term/framework, or responds to an essay/study/announcement, hyperlink that source at the first reference or first quote, not only in a footer disclaimer or the citation list. Footer-only attribution buries the source readers (and AI engines) need at the point of the claim. A footer source line is additive, never a substitute for the inline link. Why: #5088 — the loop-engineering post linked Osmani's essay only in the footer; the inline link at first mention was added in review.
- Includes complete Eleventy frontmatter:
```yaml
title: "<Article Title>" date: "YYYY-MM-DD" description: "<Meta description, 120-160 characters, includes primary keyword>" tags:
- <relevant-tag>
```
Note: layout: "blog-post.njk" and ogType: "article" are inherited from blog/blog.json — do NOT add them to individual post frontmatter. The blog-post layout handles BlogPosting JSON-LD and OG meta tags automatically — do NOT generate inline JSON-LD in the post body.
- Generates a FAQ section with FAQPage schema if the topic naturally raises 2+ questions. Include the FAQ schema inline:
``html <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [ { "@type": "Question", "name": "<question>", "acceptedAnswer": { "@type": "Answer", "text": "<answer>" } } ] } </script> ``
If existing posts are present in the target directory, read 1-2 of them to match frontmatter schema, layout name, and tag conventions.
Phase 2.5: Citation Verification
<validation_gate>
After generating the draft, verify all factual claims before presenting to the user.
Invoke the fact-checker agent via the Task tool, passing the full draft content:
Task fact-checker: "Verify this draft:
<full draft text>"
Parse the returned Verification Report. For each claim:
- PASS: No annotation needed
- FAIL: Insert
[FAIL: <reason>]inline after the claim in the draft - UNSOURCED: Insert
[UNSOURCED]inline after the claim in the draft
If the fact-checker agent is unavailable (e.g., Task tool not accessible), warn: "Citation verification skipped -- fact-checker agent not available. Proceed with manual verification." Continue to Phase 3.
Re-verification runs after each Edit cycle in Phase 3 -- when the user selects "Edit" and the draft is regenerated in Phase 2, Phase 2.5 re-runs on the updated draft.
</validation_gate>
Phase 3: User Approval
If Phase 2.5 produced a Verification Report, display the summary first (total claims, verified, failed, unsourced), then present the draft with any inline FAIL/UNSOURCED markers visible. If all claims passed, note "All citations verified." If verification was skipped, note "Citation verification was skipped -- manual review recommended."
If HEADLESS_MODE=true:
- If all citations are PASS/SOURCED, or verification was skipped: auto-select Accept. Proceed to Phase 4.
- If any citation has a FAIL marker: auto-select Fix. For each FAIL claim:
- Remove the unsupported statistic, quote, or claim entirely, OR
- Replace it with a verifiable alternative (search for a real source via WebSearch/WebFetch)
- Remove the
[FAIL: ...]marker after fixing - After fixing all FAIL claims, re-run Phase 2.5 (fact-checker) on the updated draft.
- If re-verification passes (all PASS/SOURCED): auto-select Accept. Proceed to Phase 4.
- If FAIL claims persist after 2 fix cycles: convert remaining
[FAIL: ...]markers to[UNSOURCED], remove the specific claim text, and Accept the article. Do not abort — an article with conservative claims is better than no article. Note the removed claims in the GitHub audit issue.
If HEADLESS_MODE is not set (interactive mode):
Present the generated draft with word count displayed. Use the AskUserQuestion tool with three options:
- Accept -- Write article to disk
- Edit -- Provide feedback to revise the draft (return to Phase 2 with feedback incorporated)
- Reject -- Discard the draft and exit
If "Edit" is selected, ask for specific feedback, then regenerate incorporating the changes. The user can choose Edit as many times as needed.
Phase 4: Write to Disk
On acceptance, write the article to the output path.
Report: "Article written to <path>. Review and commit when ready."
Phase 4.5: OG Image Generation
Every blog post must have an `ogImag