content-writer · git:20260612.451d7ac · 2026-06-12 · sha256 36b9f93734838bb5

content-writer git:20260612.451d7acB

Immutable. This exact content is served forever at /api/v1/blob/36b9f93734838bb5.

---
name: content-writer
description: "This skill should be used when generating full article drafts with brand-consistent voice, Eleventy frontmatter, and structured data. It requires a brand guide and existing blog infrastructure."
---

# 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. `technical` uses engineering vocabulary and developer proof points. `general` uses 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:

1. Read `## Voice` -- apply brand voice, tone, do's and don'ts
2. Read `## Channel Notes > ### Blog` -- apply blog-specific guidelines (if the section exists)
3. Read `## Identity` -- use mission and positioning for content alignment
4. If `--audience` is set, read `### Audience Voice Profiles` from brand guide and apply the matching register's vocabulary, explanation depth, and proof point selection rules. If `--audience` is not set, infer from `--path` or topic context (blog posts default to `technical`, landing pages and onboarding content default to `general`).

Generate a full article draft that:

- Follows the brand voice from `## Voice`
- Incorporates target keywords naturally (if `--keywords` provided)
- Follows the provided outline structure (if `--outline` provided)
- **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:

```text
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:
  1. Remove the unsupported statistic, quote, or claim entirely, OR
  2. Replace it with a verifiable alternative (search for a real source via WebSearch/WebFetch)
  3. 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 `ogImage` for social sharing differentiation. This is **mandatory, not optional** — `plugins/soleur/test/seo-aeo-drift-guard.test.ts` (#4753) FAILS CI for any post without an `ogImage` frontmatter field. A post that reaches CI without it red-lights the build. After writing the article:

1. **Check for existing `ogImage`** in the frontmatter. If already set, skip.
2. **Generate a unique OG image** (1200x630px) using the `gemini-imagegen` skill or Pillow fallback:
   - Brand colors: dark background `#1a1a1a`, gold accent `#c4a35a`
   - Abstract/thematic visual matching the article topic -- no text in the image (og:title provides text)
   - Save to `plugins/soleur/docs/images/blog/og-<slug>.png`
   - **Fallback if generation is unavailable (never omit the field):** reuse the closest on-theme existing card — `ls plugins/soleur/docs/images/blog/og-*.png` and pick the nearest topical match (precedent: `2026-06-01-claude-code-plugin-vs-skill-vs-mcp.md` reuses `og-best-claude-code-plugins-2026.png`). A reused on-theme card beats a missing field (CI red) or the site default (the #4753 guard rejects the default for a bespoke-image post).
3. **Add `ogImage` to frontmatter**: `ogImage: "blog/og-<slug>.png"`
4. The base template resolves this as `/images/{{ ogImage }}` for og:image meta tags

**Headless mode:** Auto-generate without prompting. **Interactive mode:** Show the generated image and ask for approval.

## Important Guidelines

- All content requires explicit user approval before writing -- no auto-write (unless `--headless` is passed, which auto-accepts on PASS citations and auto-fixes FAIL claims before accepting)
- Brand guide is a hard prerequisite. Without it, the skill cannot generate brand-consistent content.
- Read the brand guide Voice section during draft generation, not as a separate post-hoc validation pass
- If outline is provided, follow it. If not, generate a reasonable article structure from the topic.
- Do not scaffold blog infrastructure. If missing, direct the user to the docs-site skill.
- The blog-post.njk layout generates BlogPosting JSON-LD automatically. Do not duplicate it in the post body.
- Frontmatter fields should match existing posts in the target directory when possible. The `date:` field must be unquoted (e.g., `date: 2026-03-26`, not `date: "2026-03-26"`) -- Eleventy's `dateToRfc3339` filter requires a Date object, and quoted dates are parsed as strings.
- If the brand guide's `## Channel Notes > ### Blog` section is missing, generate content using only the `## Voice` section (no error).
- Every factual claim, statistic, and attributed quote must have a verifiable source URL. Phase 2.5 enforces this via the fact-checker agent -- claims without citations are flagged as UNSOURCED and claims with unsupporting sources are flagged as FAIL [enforced: fact-checker agent via Phase 2.5].