write-content

Create content of any configured type from a topic or from the content backlog. Orchestrates research, writing, style review, glossary creation, linking, and reindexing as focused sub-agents. Hooks handle tracker updates and build verification automatically.

Unified content creation skill. Orchestrates focused sub-agents for each phase. Reindex runs in Phase 9 before commit. Hooks handle tracker updates and build verification after commits.

Arguments: $ARGUMENTS


Phase 1: Parse Arguments

Determine content type, source, and mode from $ARGUMENTS.

First, read the content_types keys from .content-ops/config.md to get the list of valid content types. Then match the first word of $ARGUMENTS against those keys:

ArgumentContent TypeSourceMode
"Docker Containers"ask user (list all types)user promptinteractive
article "Docker Containers"articleuser promptinteractive
glossary "container, image"glossaryuser promptinteractive
case-study "Project X"case-studyuser promptinteractive
backlog 3from backlogbacklogautonomous
(empty)ask user (list all types)ask userinteractive

Rules:

  • If the first word matches a key in content_types → that is the content type; the rest is the topic or terms.
  • If the first word is backlog → type comes from backlog entries, rest is the selection (count, #ids, all, first N, last N). If rest is empty, treat as all.
  • If the first word does not match any configured type and is not backlog → treat the entire argument as a topic and ask the user which content type to use, listing all configured types.
  • If arguments are empty → ask the user what they want to create, listing all configured content types.

Source determines mode automatically:

  • source: user promptinteractive mode — ask 3-5 interview questions before writing
  • source: backlogautonomous mode — context comes from the backlog entry, no interview

Phase 2: Load Context

Read .content-ops/config.md in this phase. Extract and hold these values — they are used throughout all phases and passed explicitly to each subagent that needs them:

  • author, content_types, content_strategy, content_pillars_path, backlog_file, translation_tracker_file, reference_content, languages, default_language, source_hierarchy, research_cache_path, research_cache_ttl_days, content_index_path, linking_max_candidates, linking_max_links, image_generation

Also extract and hold:

  • The glossary config block (if present) — needed for Phase 7/8 conditional logic. Key fields: glossary.enabled, glossary.auto_stubs, glossary.content_type, glossary.bidirectional_linking.
  • The guidelines value for the relevant content type. Note that guidelines is a list of paths per content type (not a single path).

Also read (always):

  1. The backlog file (backlog_file) if mode is autonomous

Also read (non-glossary content types):

  1. The content strategy (content_strategy) — understand topic context
  2. If content_pillars_path is configured: find the matching pillar file for this topic

SEO integration check (at end of Phase 2):

If .content-seo/config.md exists:
  Set seo_enabled = true
  Check env: DATAFORSEO_LOGIN and DATAFORSEO_PASSWORD both set?
  If both set → set seo_brief_available = true
  Else → set seo_brief_available = false (continue without SEO brief, no hard stop)
Else:
  Set seo_enabled = false
  Set seo_brief_available = false

Phase 2.5: SEO Brief (conditional)

Skip this phase entirely if any of the following are true:

  • seo_enabled is false
  • seo_brief_available is false
  • The content type is the glossary type (i.e., matches glossary.content_type in config)

If all conditions pass:

  1. Read .content-seo/config.md to get seo_rules for this content type.
  2. Spawn the keyword-researcher agent via Task with a focused prompt:
Use the keyword-researcher agent.

Keyword: [topic — proposed primary keyword based on title/topic]
Locale: [default_language from content-ops config]
Audience: [summary from content_strategy]
Pillars context: [pillar names from content_pillars_path, comma-separated, or "not configured"]
Cache path: .content-seo/keyword-cache/
Cache TTL days: [research_cache_ttl_days from content-ops config]

Check the cache first. Only call DataForSEO for cache misses or stale entries.
Return: primary keyword, volume, related keywords (top 5 with volumes), content gaps (3 subtopics).
  1. Store seo_brief = { keyword, volume, related_keywords, content_gaps }.
  2. In autonomous/backlog mode: auto-confirm the best keyword returned by the agent. Do not ask the user.
  3. In interactive mode: Show the proposed keyword and ask for confirmation before proceeding.

Phase 3: Plan

Behavior depends on mode:

Interactive mode (single article or glossary)

For articles: Ask the user 3-5 quick questions using a single AskUserQuestion call:

  1. Audience level — Beginners or readers who know the basics?
  2. Key angle — Main perspective or focus?
  3. Must-cover points — Specific aspects, examples, or facts to include?
  4. Exclusions — Anything to leave out?

For glossary: Check for duplicates using Glob on the glossary content path from config:

  • If a term already exists: show it and ask to update or skip.
  • For terms created as part of another workflow: skip duplicates silently.

Autonomous mode (backlog)

  1. Parse the selection argument:
    • all or (empty) → all pending articles in order
    • 3 or first 3 → first 3 pending articles
    • last 2 → last 2 pending articles
    • #1,#3,#5 → specific articles by their # number in the backlog
  2. List selected articles in order with backlog numbers
  3. For each article, find matching topic in the content strategy / pillar files and note context
  4. Present the batch plan and confirm with the user before proceeding. After confirmation, work autonomously through all phases.

Phase 4: Research

Spawn the content-researcher agent via the Task tool with a focused prompt. Pass only what the agent needs:

For articles:

Use the content-researcher agent.

Topic: [topic]
Task: Research key facts — dates, numbers, technical details, historical context.
Config:
  source_hierarchy=[from config, or "not configured"]
  research_cache_ttl_days=[from config]
  research_cache_path=[from config]

Check the research cache first. Only do web searches for uncached or stale topics.
Return a structured research report.

For glossary:

Use the content-researcher agent.

Terms: [comma-separated terms]
Task: Verify precise definitions, accurate examples, and correct technical details for each term.
Config:
  source_hierarchy=[from config, or "not configured"]
  research_cache_ttl_days=[from config]
  research_cache_path=[from config]

Check the research cache first. Return a structured research report per term.

Store the agent's research report — it is passed as input to Phase 5.


Phase 5: Write

Spawn the draft-writer agent via the Task tool. Pass the research report from Phase 4 and focused context using this generic prompt:

Use the draft-writer agent.

Content type: [content type name]
Topic: [topic or terms]
Output path: [content_types.<type>.path]/[default_language]/[slug].md
Author: [from config]
Frontmatter fields: [content_types.<type>.frontmatter from config]
[If interactive: Audience level, Key angle, Must-cover points, Exclusions from Phase 3]
[If backlog: Pillar context and objectives from the matching pillar file]

Research findings:
[full research report from Phase 4]

Follow the structure guide and formatting rules from the content-style skill for this content type.
Return the file path of the created content.

If seo_brief is set, add the following to the Task prompt before the closing line:

SEO brief:
  Target keyword: [seo_brief.keyword] ([seo_brief.volume]/mo)
  Related keywords: [seo_brief.related_keywords as comma-separated list]
  Content gaps to address: [seo_brief.content_gaps as numbered list]

SEO instructions:
  - Include the target keyword in the first 100 words of the article body
  - Include the target keyword in at least one H2 heading (naturally)
  - Weave related keywords into headings and body text naturally — no keyword stuffing
  - Cover all 3 content gaps as sections or meaningful subsections

For backlog items: Also include articles created earlier in this batch as potential cross-references.

Store the created file path(s) — they are passed as input to Phase 5.5 and beyond.

After Phase 5 — inject seo_keyword into frontmatter:

If seo_brief is set:
  Read the created file
  Add seo_keyword: "[seo_brief.keyword]" to the frontmatter
  Write the updated file

Phase 5.5: Image Generation

Skip this phase entirely if any of the following are true:

  • image_generation.enabled is not true in config (or the image_generation section is absent)
  • The content type is in image_generation.skip_types (e.g., glossary)

The image-generator agent will handle per-article word count and API key checks internally and report if skipped.

For articles, spawn the image-generator agent via the Task tool:

Use the image-generator agent.

Article path: [file path from Phase 5]
Article slug: [slug from Phase 5]
Content type: [article or glossary]
Image generation config:
  enabled: [image_generation.enabled from config]
  provider: [image_generation.provider from config]
  model: [image_generation.model from config, if set]
  guidelines: [image_generation.guidelines from config]
  output_path: [image_generation.output_path from config]
  hero_dimensions: [image_generation.hero_dimensions from config]
  inline_dimensions: [image_generation.inline_dimensions from config]
  placement: [image_generation.placement from config]
  min_word_count: [image_generation.min_word_count from config]
  skip_types: [image_generation.skip_types from config]
  max_inline_images: [image_generation.max_inline_images from config, if set]

After the agent returns:

  1. If the agent reported "skipped" or an error: note it and continue to Phase 6 without inserting images.
  2. If images were generated:
    • Insert the hero image markdown after the closing --- of the frontmatter, before the first paragraph
    • Insert each inline image markdown before its target ## Heading as specified in the agent's output
    • Store the list of created image file paths — they are added to the git commit in Phase 9

For glossary entries: Skip this phase.


Phase 6: Style Review

Spawn the style-enforcer agent via the Task tool with a focused prompt:

Use the style-enforcer agent to review the content at [file path from Phase 5].
Content type: [content type name]
Config:
  word_range: [content_types.<type>.word_range from config]
  guidelines: [content_types.<type>.guidelines list from config]
  reference_content: [reference_content list from config]
Check sentence length, paragraph density, scope discipline, tone, plain English, structure, and linking.
Return a structured style review report with must-fix issues.

Apply any must-fix issues from the report before proceeding. If fixes are needed, edit the file(s) directly (they are in scope for this skill).


Phase 7: Auto-create Missing Glossary Entries

Skip this phase if any of the following are true:

  • The glossary config block is absent or glossary.enabled is false
  • glossary.auto_stubs is false
  • The current content type is the glossary type itself (i.e., content_type_name == glossary.content_type)

If all conditions pass, spawn the glossary-creator agent via the Task tool:

Use the glossary-creator agent.

Article path: [file path from Phase 5]
Glossary path: [content_types.[glossary.content_type].path]/[default_language]/
Author: [from config]
[If batch: also list glossary entries created earlier in this batch to avoid duplicates]

Scan the article for technical terms that don't have glossary entries.
Create entries for any missing terms. Skip terms that already exist.
Return the list of created glossary entry file paths (or empty list if none created).

Store the list of created glossary paths — passed to Phase 8.


Phase 8: Bidirectional Linking

Spawn the content-linker agent via the Task tool.

Base prompt (always included):

Use the content-linker agent.

New content: [file path from Phase 5]
Content type: [content type name]
Default language: [from config]
Config:
  content_index_path: [content_index_path from config]
  linking_max_candidates: [linking_max_candidates from config, default 50]
  linking_max_links: [linking_max_links from config, default 10]
  url_patterns: [url_patterns from config, if set]

Ensure all cross-content links are complete:
- Update related content's relatedArticles
- Add inline links in existing content where the new topic is mentioned without a link

Return a linking report.

If glossary.enabled AND glossary.bidirectional_linking are true, add to the prompt:

Glossary entries created in this run: [list from Phase 7, if any]

Also ensure glossary bidirectional links:
- Update glossary relatedArticles for terms referenced by the content
- Add inline links in existing articles where glossary terms are mentioned

Review the report. If there are warnings about broken references, fix them before committing.


Phase 9: Reindex + Build + Commit

  1. Regenerate the content index: Run the reindex procedure before committing:

    • Read config for content_types, languages, content_index_path
    • Use Glob to find all .md/.mdx files per type/language
    • Read each file's frontmatter only
    • Write .content-ops/content-index.json with the structured metadata
    • (Or invoke the reindex skill if available)
  2. Stage all created and modified files (including the updated content-index.json and any image files from Phase 5.5)

  3. Commit:

content: add [content type] "<title>"

- New [content type]: [file path]
- Images: [N images, or "none"]
- New glossary: [list any created in Phase 7, or "none" or "skipped — glossary disabled"]
- Updated: [list any modified existing files from Phase 8]

Co-Authored-By: Claude <[email protected]>

The post-commit hooks will automatically update trackers when content files are written.

The content index is regenerated in this phase (before commit) so it stays in sync with the committed content.


Phase 10: Suggest Backlog Additions

Skip this phase entirely if the backlog_suggester key is absent or commented out in .content-ops/config.md.

Spawn the backlog-suggester agent via the Task tool:

Use the backlog-suggester agent.

Mode: orchestrator
Triggered by: [file path from Phase 5]
Config:
  backlog_file: [backlog_file from config]
  content_index_path: [content_index_path from config]
  content_strategy: [content_strategy from config]
  content_pillars_path: [content_pillars_path from config, or "not configured"]
  backlog_suggester: [full backlog_suggester block from config]

After the agent returns:

  • If auto_add is true and entries were appended: stage backlog_file and commit it separately with message content: update backlog after [slug]
  • If auto_add is false: the agent has already handled user interaction and writing; nothing more to do
  • If no new entries were found: nothing to commit

Batch Mode (Backlog)

When mode is autonomous (source is backlog), phases 4-9 loop per item:

  1. For each article in the batch, execute phases 4 through 9 sequentially
  2. Each article gets its own commit — progress is saved even if the session is interrupted
  3. Track across items: new glossary entries and new articles created so far, so subsequent items can reference earlier ones (pass this context to draft-writer and glossary-creator agents)
  4. After all items complete, print a batch summary:
## Batch Complete

### Articles written
- #1 "Title" → [path]
- ...

### Glossary entries created
- term1, term2, ...

### Existing content updated
- [list modified files]

### Commits
- [list of commit hashes and messages]