Wire Agent Protocol

IMPORTANT: Read this document completely before taking any action. Skipping sections will cause BUILD REFUSED errors, destroyed content, or wasted API credits on work that gets overwritten. Wire uses the Claude API for all content operations.

Wire is a content pipeline and static site generator for AI-powered content sites. It is installed as a pip package and operated via CLI commands. Every Wire site has a wire.yml configuration file at its root. Wire is opinionated: it refuses to build on missing config rather than silently degrading.

Quick Start

  1. Install: pip install https://wire.wise-relations.com/dist/wire-latest.whl
  2. Find wire.yml: ls wire.yml. If missing, you are not in a site directory
  3. Test build: python -m wire.build
  4. If build passes: the site is healthy. Read on for operations.
  5. If build fails: fix the error. See the BUILD REFUSED section below.
  6. Initialize a new site: python -m wire.chief init

Requirements:

  • claude login (Claude Max subscription) OR ANTHROPIC_API_KEY in .env (API fallback)
  • Optional: BFL_API_KEY (for AI image generation)
  • Optional: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, token.json (for Google Search Console)
  • Must run from the site directory (where wire.yml lives)
  • Git Bash on Windows: use /c/Users/... paths, NOT C:\Users\...

All Commands (in correct order)

The standard Wire workflow runs these commands in sequence. Each command depends on the output of previous commands.

# Command Cost Modifies files? What it does
0 init Free Yes Initialize Wire in current directory (generates wire.yml, styleguide, templates)
1 data Free .wire/gsc.db only Pull Google Search Console metrics into local SQLite database
2 audit [topic] Free No SEO audit: opportunities, gaps, overlaps, dead pages. Read-only analysis.
3 deduplicate [topic] ~$0.10/page Yes Merge or differentiate pages that cannibalize each other's keywords
4 news <topic> ~$0.02/page Yes (pending files) Gather news for all items in a topic via web search
5 refine <topic> ~$0.05/page Yes Integrate pending news files into page content
6 reword <topic> ~$0.05/page Yes Rewrite pages to target GSC keyword opportunities
7 consolidate --topic <topic> ~$0.10/page Yes Create hub pages from comparison pages
8 crosslink [topic] ~$0.03/page Yes Add internal links to underlinked pages
9 sanitize [topic] Free Yes Fix broken internal links (no API calls, just re-save)
10 enrich <topic> ~$0.06/page Yes Analyze locally (free), then improve actionable pages
11 images ~$0.04/image Yes Generate missing AI images via BFL/FLUX
12 newsweek ~$0.30/run Yes Generate weekly market intelligence report
13 translate [topic] ~$0.01/page Yes Translate page bodies to match their language directory
14 migrate-gsc Free .wire/gsc.db only Rekey GSC database after page restructure via redirects
15 migrate-lang Free Yes Scaffold multi-language site from single-language
16 download [topic] Free Yes Download WordPress pages as clean markdown
17 redirects Free No Analyze GSC coverage gaps, suggest strict redirects
18 lint-fix [topic] Free Yes Rewrite links pointing through redirects to direct targets
- build Free Yes (site/ dir) Build the static site. Separate module: python -m wire.build

Command Syntax

All commands use this pattern:

python -m wire.chief <command> [arguments] [--flags]

Build uses a separate module:

python -m wire.build [--dirty] [--serve] [--port N]

Global Flags

Flag Effect
--dry-run Preview changes without saving (writes .preview files instead)
--nogsc Skip GSC database lookups (for new sites without search data)
--lang <code> Language code for multi-language sites (e.g., de, en). Required for multi-language content commands.
--version Print Wire version and exit

Per-Command Flags

Command Flag Effect
data --force Re-fetch even if data is fresh
data --workers N Concurrent workers (default 1, max 8)
audit --min N Minimum shared keywords to flag (default 3)
audit --min-impressions N Minimum impressions to include (default 100)
deduplicate --min N Minimum shared keywords to act on (default 3)
news --resume Resume interrupted run
news --workers N Concurrent workers (default 1, max 8)
refine --resume Resume interrupted run
refine --workers N Concurrent workers (default 1, max 8)
reword --resume Resume interrupted run
reword --workers N Concurrent workers (default 1, max 8)
enrich --resume Resume interrupted run
consolidate --topic <name> Required. Topic containing items to consolidate.
consolidate --min N Minimum primary comparisons to consolidate (default 3)
crosslink --resume Resume interrupted run
translate --resume Resume interrupted run
images --force Regenerate all images, even cached ones
images --prune Remove orphan images not referenced by any !makeIMG
newsweek --from YYYY-MM-DD Start date (default: 7 days ago)
newsweek --to YYYY-MM-DD End date (default: today)
newsweek --skip-review Skip Phase 3 editorial review
newsweek --resynth Skip Phase 1, reuse cached extracts (for prompt iteration)
download --force Re-download even if page has content
migrate-lang --from <code> Required. Current site language code.
migrate-lang --add <code> Required. Language(s) to add (repeatable).

What NOT to Do

  • Do NOT run commands out of order. refine before news wastes API credits on empty content.
  • Do NOT skip deduplicate when audit reports hard overlaps. Build will REFUSE until resolved.
  • Do NOT run content commands without committing your work first. Wire refuses if docs/ has uncommitted changes.
  • Do NOT run content generation without a project styleguide at docs/_styleguide.md. Wire refuses.
  • Do NOT omit --lang on multi-language sites for content commands. Wire fails loud.
  • Do NOT run data from outside the site directory. Wire reads wire.yml from the current directory.

Frontmatter Schema

Every markdown file MUST have YAML frontmatter delimited by ---. Wire validates frontmatter at build time. Unknown keys cause BUILD REFUSED.

Required Fields (all pages)

Key Type Purpose
title string Page title for <title> tag and H1. Must be non-empty.
description string Meta description for search results. Must be non-empty.

Required for Content Pages (pages with both topic and slug)

Key Type Purpose
created string (YYYY-MM-DD) First publication date. Needed for JSON-LD, RSS, sitemap.

Required for Topic Pages (pages within a topic directory)

Key Type Purpose
short_title string (max 20 chars) Short label for nav tabs and sidebar. No fallback to title.

Required for Parent Pages (index.md with child content directories)

Key Type Purpose
layout string Page layout type. Required when directory has child content dirs.

Optional Fields

Key Type Purpose
date string (YYYY-MM-DD) Last modification date. Updated on every write by stamp().
template string Jinja2 template name (default: page.html).
og_type string Open Graph type (website, article).
image string Featured image path for OG tags and social sharing.
tags list Content tags for categorization.
sources list External citation URLs (append-only).
wire_action string Last Wire action that modified the page. Set automatically.
wire_reworded string (YYYY-MM-DD) Date of last SEO reword. Set automatically.
wire_differentiated string (YYYY-MM-DD) Date of last differentiation. Set automatically.
wire_differentiated_from string Slug of page this was differentiated from. Set automatically.
author string Author slug (resolves to author page in docs/authors/).
role string Author role/title override for byline display.
short_title string Short label for navigation (max 20 characters).
layout string Page layout. Valid values: page, article, landing, raw, home.
schema_type string JSON-LD schema.org @type override.
company string Company name for vendor/organization pages.
product string Product name for product pages.
hide_title any Hide the page title in rendered output.
hide_meta any Hide the meta info block (author, date) in rendered output.
extra_css list Additional CSS files to load on this page.
extra_js list Additional JS files to load on this page.
alternate dict Cross-language page mappings. Format: {lang_code: /lang/path/}.
draft boolean Mark page as draft (excluded from build).
summary string Short summary for listings and cards.
linkedin string LinkedIn URL for author pages.
organization string Organization name for structured data.
categories list Content categories.
last_updated string Last updated date (separate from date).
hide any Hide page from navigation and listings.

Complete List of All Known Keys

These are the ONLY keys Wire accepts in frontmatter. Any other key triggers BUILD REFUSED:

alternate, author, categories, company, created, date, description, draft, extra_css, extra_js, hide, hide_meta, hide_title, image, last_updated, layout, linkedin, og_type, organization, product, role, schema_type, short_title, sources, summary, tags, template, title, wire_action, wire_differentiated, wire_differentiated_from, wire_reworded

Valid Layout Values

Layout Use case
page Default for topic index pages (nav, no sidebar)
article Content pages with TOC, sidebar CTA, reading progress bar
landing Marketing pages split into alternating sections at <hr>
raw Bare HTML, no chrome (for embeds, widgets)
home Homepage with hero section

Rejected Keys (BUILD REFUSED)

Key Why rejected What to do instead
redirect Not a Wire feature Add redirect to wire.yml under redirects:

Frontmatter Examples

Minimal index page:

---
title: Vendor Directory
description: Compare all IDP vendors in one place
layout: page
short_title: Vendors
---

Content page:

---
title: ABBYY FlexiCapture Review
description: Independent analysis of ABBYY FlexiCapture for document processing
created: 2025-06-15
short_title: ABBYY
---

Landing page:

---
title: Get Started with Our Platform
description: Book a free consultation
layout: landing
short_title: Get Started
---

BUILD REFUSED Gates

Wire refuses to build when it detects configuration problems. Every gate has a specific error message and fix. Gates are checked in this order.

Gate: Missing wire.yml

  • Trigger: No wire.yml file in the site directory
  • Error: No wire.yml found in {site_dir}. Create one with at least site_name and docs_dir.
  • Fix: Create wire.yml or run python -m wire.chief init

Gate: Missing Required wire.yml Fields

  • Trigger: site_name, site_url, lang (or languages), or nav missing from wire.yml
  • Error: wire.yml missing required field(s): {fields}. These are strategic decisions - Wire does not guess.
  • Fix: Add the missing fields to wire.yml

Gate: Missing sidebar_cta

  • Trigger: extra.wire.sidebar_cta missing or incomplete in wire.yml
  • Error: wire.yml missing required 'extra.wire.sidebar_cta'. Article pages need a sidebar CTA card.
  • Fix: Add to wire.yml:
extra:
  wire:
    sidebar_cta:
      title: "Your CTA title"
      description: "Short description"
      link: /your-page/
      link_text: "Learn more"

Gate: Missing article_cta

  • Trigger: extra.wire.article_cta missing or incomplete in wire.yml
  • Error: wire.yml missing required 'extra.wire.article_cta'. Every article needs a bottom CTA banner.
  • Fix: Add to wire.yml:
extra:
  wire:
    article_cta:
      title: "Ready to get started?"
      description: "Book a free consultation."
      link: /contact/
      link_text: "Get in touch"
  • Trigger: logo key missing from wire.yml
  • Error: wire.yml missing required 'logo'.
  • Fix: Add logo: assets/logo.svg (path to logo) or logo: false (text-only mode)

Gate: Missing favicon

  • Trigger: favicon key missing from wire.yml
  • Error: wire.yml missing required 'favicon'.
  • Fix: Add favicon: assets/favicon.svg (path to favicon) or favicon: false (no favicon)

Gate: Logo/Favicon File Not Found

  • Trigger: Logo or favicon path in wire.yml points to a nonexistent file
  • Error: logo file not found: {path}. Expected at: {full_path}
  • Fix: Create the file at the specified path or update the path in wire.yml

Gate: Language Directories Without Config (single-language sites)

  • Trigger: docs/ contains 2+ directories that look like ISO 639-1 language codes (e.g., de/, en/) but no languages config in wire.yml
  • Error: docs/ contains directories that look like language codes: {codes}. This creates broken nav tabs instead of a proper multi-language site.
  • Fix: Configure languages: in wire.yml with proper language entries

Gate: Missing Required Frontmatter

  • Trigger: Page missing title or description in frontmatter
  • Error: BUILD REFUSED: {count} page(s) missing required frontmatter (title, description)
  • Fix: Add the missing fields to each page's frontmatter

Gate: Unknown Frontmatter Keys

  • Trigger: Frontmatter contains keys not in the known keys list
  • Error: unknown frontmatter key(s): {keys}. Wire only supports: {known_keys}
  • Fix: Remove the unknown keys or use a known key instead. See the Complete List of All Known Keys above.

Gate: redirect: in Frontmatter

  • Trigger: Page uses redirect: key in frontmatter
  • Error: 'redirect:' in frontmatter is not supported. Add redirect to wire.yml instead
  • Fix: Remove redirect: from frontmatter and add to wire.yml under redirects:

Gate: MkDocs Extension Syntax

  • Trigger: Markdown files use MkDocs extension syntax ({.class} attr_list, <div markdown>, !!! admonitions, === tabs)
  • Error: BUILD REFUSED: {count} MkDocs syntax error(s). Wire does not process MkDocs extensions.
  • Fix: Replace with Wire equivalents:
    • {.class} attribute lists: Use :::cta for buttons, or remove class annotation
    • <div markdown>: Use Wire shortcodes (:::stats, :::cards, :::split, :::banner)
    • !!! admonitions: Use > blockquote instead
    • === tabs: Use ## headings to organize content

Gate: Missing Layout on Parent Pages

  • Trigger: An index.md page has child content directories but no layout: in frontmatter
  • Error: BUILD REFUSED: {count} page(s) missing required layout
  • Fix: Add layout: page (or another valid layout) to the page's frontmatter

Gate: Missing or Invalid short_title on Topic Pages

  • Trigger: A topic page (index or content) missing short_title, or short_title exceeds 20 characters, or short_title ends with a trailing preposition or punctuation
  • Error: BUILD REFUSED: {count} topic page(s) missing or invalid short_title
  • Fix: Add short_title: to frontmatter (max 20 chars, complete label, no trailing prepositions like "for", "and", "with")

Gate: Numbered Slugs

  • Trigger: Content directory name starts with 1-2 digit number prefix (e.g., 2-adding-chatbot/, 7-advanced/)
  • Error: BUILD REFUSED: {count} page(s) with numbered slugs
  • Fix: Rename directories to use descriptive slugs without number prefixes. Date-based slugs (YYYY-MM-DD-*) are allowed for news articles.

Gate: Empty Topic Directories

  • Trigger: A topic directory has only an index page, no content child pages (except standalone landing pages)
  • Error: BUILD REFUSED: {count} empty topic(s)
  • Fix: Add content pages to the topic or remove the empty directory

Gate: Placeholder Created Dates

  • Trigger: Content page has created date on January 1st (e.g., 2025-01-01)
  • Error: created date '{date}' looks like a placeholder (January 1st). Run python -m wire.migrate to backfill from git history.
  • Fix: Run python -m wire.migrate to backfill real dates from git history, or set the actual creation date

Gate: Invalid Created Date Format

  • Trigger: created date is not in YYYY-MM-DD format
  • Error: created date '{date}' must be YYYY-MM-DD format.
  • Fix: Fix the date to use YYYY-MM-DD format

Gate: Pending News Files

  • Trigger: Unprocessed YYYY-MM-DD.md news files exist in content directories (not archived)
  • Error: BUILD REFUSED: {count} page(s) have unprocessed pending news
  • Fix: Run python -m wire.chief refine <topic> to integrate the pending news

Gate: Migration Artifacts

  • Trigger: .py files at site root (except setup.py, conftest.py)
  • Error: BUILD REFUSED: Migration artifact at site root: {filename}
  • Fix: Delete migration scripts after migration is complete

Gate: Nav Orphans

  • Trigger: Pages exist that are not reachable via the nav: configuration in wire.yml
  • Error: BUILD REFUSED: {count} page(s) not reachable via nav in wire.yml
  • Fix: Add pages to nav: in wire.yml, or exclude them with exclude: patterns

Gate: Missing header.offerings

  • Trigger: Site has topics with child pages but header.offerings is not configured
  • Error: BUILD REFUSED: header.offerings not configured in wire.yml
  • Fix: Add to wire.yml:
header:
  offerings: <topic-name>

Set to false to explicitly disable.

Gate: Missing header.cta

  • Trigger: Site has topics but header.cta is not configured
  • Error: BUILD REFUSED: header.cta not configured in wire.yml
  • Fix: Add to wire.yml:
header:
  cta:
    label: Get Started
    url: /getting-started/

Set to false to explicitly disable.

Gate: Nav Config Validation

Checked after offerings resolution:

  • Alphabetical Nav Order: 3+ nav sections in alphabetical order = uncurated. Reorder by importance.
  • Duplicate URLs: Same URL in offerings and content nav. A page cannot be both.
  • CTA URL in Nav: CTA URL duplicates a content nav URL. CTA is its own nav element.
  • CTA Page Layout: CTA page must have layout: landing.
  • Offerings Page Layout: Offerings pages must have layout: landing, page, or article.

Gate: Missing Article Labels

  • Trigger: extra.wire.labels missing required keys
  • Error: Missing required labels: {keys}
  • Fix: Add to wire.yml:
extra:
  wire:
    labels:
      on_this_page: "On This Page"
      related_articles: "Related Articles"
      read_more: "Read More"

Gate: Missing Discovery Labels (non-English sites with discovery steps)

  • Trigger: Non-English site has discovery steps but missing discovery_start or discovery_read labels
  • Error: Non-English site (lang={lang}) has discovery steps but missing labels: {keys}
  • Fix: Add to wire.yml:
extra:
  wire:
    labels:
      discovery_start: "Start Reading"
      discovery_read: "Continue"

Gate: Content Cannibalization (SEO Audit)

  • Trigger: Hard overlaps detected (keyword overlap ratio > 0.4, traffic skew > 0.7, same topic)
  • Error: BUILD REFUSED: {count} content cannibalization issue(s) detected.
  • Fix: Run python -m wire.chief audit <topic> then python -m wire.chief deduplicate <topic>

Gate: GSC Coverage

  • Trigger: Google Search Console URLs with impressions have no corresponding page and no redirect
  • Error: Build blocked: {count} GSC URL(s) have no page and no redirect.
  • Fix: Add redirects in wire.yml redirects: section, or run python -m wire.chief data to refresh GSC URLs

Gate: Redirect Source Conflicts

  • Trigger: A redirect source path also has a corresponding docs/ file (ambiguous state)
  • Error: BUILD REFUSED: CONFLICT: redirect /{path}/ has a source file
  • Fix: Delete the docs file or remove the redirect entry

Gate: Alternate Frontmatter Validation (multi-language)

  • Trigger: alternate: frontmatter points to nonexistent pages, or mappings are not reciprocal
  • Error: BUILD REFUSED: alternate target /{lang}{path} does not exist or Non-reciprocal alternate
  • Fix: Ensure all alternate targets exist and map back to the source page

Multi-Language Gates

Gate Trigger Error
Language Asymmetry Language has less than 20% of default language's page count Language '{code}' has {n} pages ({pct}% of '{default}')
Orphan Language Dirs Language directory exists with content but is not in wire.yml config Directory '{code}/' exists with content but is not in languages config
Orphan Language Config Language configured in wire.yml but directory does not exist Language '{code}' is configured but its docs directory does not exist
Duplicate Titles Same title appears in multiple language directories Title '{title}' appears in both '{lang1}' and '{lang2}'
Untranslated Bodies Page content detected in wrong language via stopword analysis (min 50 words) Page '{url}' is in '{expected}' directory but content appears to be '{detected}'
Unsupported Language Language has no stopword set for detection Must add stopwords to _STOPWORDS in build.py. Supported: de, en, es, fr

Gate: Uncommitted Changes (content commands only)

  • Trigger: docs/ directory has uncommitted git changes when running content-modifying commands
  • Error: REFUSED: {count} uncommitted change(s) in docs/
  • Fix: git add docs/ && git commit before running content commands

Gate: Missing Styleguide (content generation only)

  • Trigger: No docs/_styleguide.md file when running content generation commands
  • Error: Content generation refused: no project styleguide found at docs/_styleguide.md
  • Fix: Create docs/_styleguide.md based on wire/prompts/example_styleguide.md

Gate: Banner Value Too Long

  • Trigger: :::banner shortcode value exceeds 6 characters
  • Error: :::banner value '{value}' is {len} chars (max 6). Short values only.
  • Fix: Shorten the banner value to 6 characters or fewer

Gate: Unsupported Section Variant

  • Trigger: :::section dark or :::section accent used in markdown
  • Error: BUILD ERROR: :::section {variant} is not supported. Wire removed dark/accent sections.
  • Fix: Use :::section (light) or custom CSS for dark styling

Lint Rules

Wire runs an HTML linter after every build. Lint failures block deployment. Every rule has a specific, actionable message. Rules can be filtered with --rules RULE-01,RULE-05.

Group A: Metadata

Rule What it checks
RULE-01 H1 present and non-empty. Exactly one required.
RULE-02 Exactly one H1 per page. Multiple H1s = demote extras to H2.
RULE-03 Heading hierarchy not skipped (no H2 followed by H4).
RULE-04 Target query appears in H1 or title (when meta target-query is set).
RULE-05 Title tag present, unique, 20-65 characters. Duplicate titles flagged.
RULE-06 Exactly one title tag per page.
RULE-07 Meta description present, max 158 characters.
RULE-08 Body content has at least 200 words (thin content detection).
RULE-09 Open Graph tags present (og:title, og:description).
RULE-10 Viewport meta tag present.
RULE-11 Charset declared in first 1024 bytes.

Group B: Canonicalization

Rule What it checks
RULE-12 Canonical tag present with href.
RULE-13 Exactly one canonical tag per page. Multiple = Google ignores both.
RULE-15 Canonical URL appears in sitemap.xml.
RULE-16 No duplicate H1 across the entire site. Differentiate or consolidate.

Group C: URL Structure

Rule What it checks
RULE-17 No uppercase characters in URL path.
RULE-18 No spaces or special characters in URL path. Only a-z, 0-9, hyphens, slashes allowed.
RULE-19 No underscores in URL path. Replace with hyphens.
RULE-20 URL length within 115 characters.
RULE-21 No query parameters on canonical page URLs.
RULE-22 Internal links use consistent trailing slash.

Group D: Sitemap

Rule What it checks
RULE-23 sitemap.xml exists at site root.
RULE-24 All indexable pages appear in sitemap.xml.
RULE-25 No noindex pages listed in sitemap.
RULE-26 No robots.txt-blocked pages in sitemap.

Group E: robots.txt

Rule What it checks
RULE-28 robots.txt exists at site root.
RULE-29 robots.txt does not block CSS or JS (Google needs these to render).
RULE-30 robots.txt contains Sitemap directive.
RULE-31 robots.txt does not block all crawlers with Disallow: /.
RULE-32 Site has imprint and privacy links (required by EU/German law).
Rule What it checks
RULE-33 No internal links returning 404 (target file missing).
RULE-34 No internal links with empty anchor text.
RULE-35 At least one inbound internal link per page (orphan detection).
RULE-36 Internal links must not point to redirect stubs. Update href to target.

Group G: Security

Rule What it checks
RULE-37 No HTTP resources on HTTPS pages (mixed content).

Group H: Images

Rule What it checks
RULE-40 All images have alt text.
RULE-41 No broken local image sources.
RULE-42 Images have explicit width and height (prevents layout shift).

Group I: Hreflang (multi-language sites)

Rule What it checks
RULE-43 Hreflang tags use valid BCP 47 language codes.
RULE-44 Hreflang includes x-default when other hreflang tags exist.
RULE-45 Every hreflang alternate has a reciprocal link back (JSON-LD page also checks for at least one block).

Group J: Structured Data

Rule What it checks
RULE-45 JSON-LD structured data present on every page.
RULE-46 JSON-LD contains valid JSON.
RULE-47 JSON-LD @type is a recognized schema.org type.
RULE-48 No broken local URLs in JSON-LD structured data fields.
RULE-55 JSON-LD includes Google-recommended fields for its @type (headline, datePublished, author).

Group K: Indexability

Rule What it checks
RULE-49 Important pages (with inbound links) not accidentally noindexed.
RULE-50 No nofollow on internal links.

Group L: Privacy / GDPR

Rule What it checks
RULE-51 No raw YouTube/Vimeo iframes. Use !yt[VIDEO_ID] shortcode for GDPR-safe embeds.
RULE-52 No em dashes or double hyphens in body text. Em dashes are the top tell of AI-generated content.

Group M: Discovery

Rule What it checks
RULE-53 Discovery step IDs must match article H2 anchors.

Group N: Content Quality

Rule What it checks
RULE-63 Stats bar must have exactly 4 items (mobile grid is 2-col).
RULE-64 Landing page custom section in hero must declare section-hero class.
RULE-65 og:image present for social sharing previews.
RULE-66 Form element must have a submission handler (action= or form_id).
RULE-67 Headings should not start with numbered prefixes (migration artifact detection).
RULE-68 Progress bar fill width must be 0-100%. Values over 100% break layout.
RULE-69 Internal links must stay in the same language directory (cross-language link detection).
RULE-70 No absolute URLs pointing to own domain. Use relative paths instead.

Workflow

The standard Wire content workflow runs these commands in sequence:

data -> audit -> deduplicate -> news -> refine -> reword -> consolidate ->
crosslink -> sanitize -> enrich -> images -> newsweek

Phase 1: Data Collection (free)

python -m wire.chief data              # Pull GSC metrics into .wire/gsc.db
python -m wire.chief audit             # See SEO opportunities

data fetches Google Search Console metrics into a local SQLite database at .wire/gsc.db. This is the only command that calls the GSC API. All other commands read from the local database.

audit is read-only. It produces a structured report with sections:

  • HEALTH: dead pages, redirect gaps, stale GSC data
  • ACTION: merge/differentiate recommendations
  • SEO: keyword opportunities, position improvements
  • INFO: topic summaries, page counts

Phase 2: Deduplication

python -m wire.chief deduplicate vendors

Resolves keyword cannibalization detected by audit. Hard overlaps (ratio > 0.4, skew > 0.7, same topic) get merged. Soft overlaps (ratio > 0.15) get differentiated. Build REFUSES until hard overlaps are resolved.

Phase 3: Content Updates

python -m wire.chief news vendors          # Gather news
python -m wire.chief refine vendors        # Integrate news into pages
python -m wire.chief reword vendors        # Rewrite for GSC keywords
python -m wire.chief consolidate --topic vendors  # Hub pages
python -m wire.chief crosslink vendors     # Internal links
python -m wire.chief sanitize vendors      # Fix broken links (free)
python -m wire.chief enrich vendors        # Targeted improvements

Each command modifies pages. Commit after each command.

Phase 4: Assets and Reports

python -m wire.chief images                # Generate AI images
python -m wire.chief newsweek              # Weekly intelligence report

Phase 5: Build and Deploy

python -m wire.build                       # Build static site
python -m wire.build --serve               # Build and serve locally
python -m wire.build --dirty               # Rebuild only modified pages

Resume and Dry Run

All batch commands support --resume to continue after interruption. Progress is tracked in .wire/progress-*.json files. Stale progress files from crashes are harmless.

--dry-run writes .preview files instead of modifying content. Safe for inspection. Skips stamp() (no metadata changes).

Shortcodes

Wire provides markdown shortcodes for common UI components. Use :::name to open and ::: to close.

:::stats

4-item statistics bar. Must have exactly 4 items (RULE-63 enforces this).

:::stats
500+ | Customers
99.9% | Uptime
24/7 | Support
50ms | Response
:::

:::cards

Feature cards in a responsive grid. Optional links.

:::cards
## Card Title
Card description text.
[Learn more](/link/)

## Another Card
More description.
:::

:::split

Two-column layout separated by ---.

:::split
Left column content with **markdown**.

---

Right column content.
:::

Variant: :::split comparison highlights the second column.

Big number callout. VALUE max 6 characters.

:::banner 223%
Companies using structured content see 223% higher ROI.
:::

:::progress

Horizontal bar chart for comparisons.

:::progress
Content-Engagement | 34%
Webinar-Einladung | 31%
:::

:::cta

Call-to-action buttons. First link auto-primary, rest secondary.

:::cta
[Get Started](/start/)
[Learn More](/about/)
:::

Override with {primary} / {secondary} markers.

:::tabs

Tabbed content sections.

:::tabs
## Tab One
Content for tab one.

## Tab Two
Content for tab two.
:::

:::faq

FAQ section with schema.org FAQPage JSON-LD.

:::faq
## Question one?
Answer to question one.

## Question two?
Answer to question two.
:::

:::badges

Badge/tag list.

:::alert

Alert/notification box.

:::section

Section wrapper. Only :::section (light) or :::section light allowed. :::section dark and :::section accent are NOT supported (BUILD ERROR).

:::testimonial

Customer testimonial with quote styling.

:::logos

Logo cloud with grayscale hover effect.

:::logos
![Company A](logo-a.png)
![Company B](logo-b.png)
:::

:::pricing

Pricing grid with plan cards.

:::pricing
:::plan
## Basic
$9/mo
- Feature one
- Feature two
[Start Free](/signup/)
:::plan

:::plan featured
## Pro
$29/mo
- Everything in Basic
- Priority support
[Get Pro](/signup/)
:::plan
:::pricing

:::steps

Step-by-step guide.

:::visual

Site visual component (logo + site name).

!makeIMG[slug]{preset}

AI image shortcode. Generates image via BFL/FLUX.

!makeIMG[document-processing]{wide}

Presets: default (1024x576), wide (1440x480), square (512x512).

Image stored at docs/assets/images/{slug}.png. Missing image at build time = FileNotFoundError (build error). Inside backticks or code fences, !makeIMG is skipped by the parser (safe in docs).

!yt[VIDEO_ID]

YouTube embed shortcode. GDPR-safe (no iframe until user clicks).

!yt[dQw4w9WgXcQ]

Gotchas

These are common pitfalls that cause confusing errors. Read before operating.

Prompt and API Gotchas

  • topic is auto-injected in load_prompt(). Passing topic= in kwargs causes TypeError: got multiple values for argument.
  • Prompts larger than 50KB auto-stream. Non-streaming times out at 10 minutes.
  • claude_text() is CLI-first: tries claude -p first, falls back to API. Requires claude login for CLI path.
  • API model (fallback only): claude-sonnet-4-6.
  • Rate limit retries: 15s then 30s, max 2 retries.
  • Markdown fences are stripped from output automatically (both CLI and API paths).

Filesystem Gotchas

  • frontmatter.load() crashes on missing files. Always check .exists() first.
  • {#slug} in prompts must be escaped as {{#slug}} (Python .format() KeyError).
  • Every markdown file needs YAML frontmatter with title:.
  • Content.from_path() requires title in frontmatter. No fallback.
  • Content item body via item.read_index() not item.content (Content has no content attribute).
  • Git Bash on Windows: /c/Users/... not C:\Users\....

Command Gotchas

  • Must run from site directory (where wire.yml lives).
  • compare syntax: vendors/X vendors/Y (no "vs" between paths).
  • consolidate requires --topic CLI argument (no default).
  • --lang is required for Chief commands on multi-language sites. Exception: data runs all languages automatically when --lang is omitted.
  • refine() saves content first, then archives news. Archive failure is safe.
  • .wire/progress-*.json cleaned on completion. Stale ones from crashes are harmless.

Build Gotchas

  • site_name, site_url, lang are required in wire.yml. Build refuses without them.
  • logo, favicon are required in wire.yml (path to file or false).
  • extra.wire.labels is required: on_this_page, related_articles, read_more (all sites); discovery_start, discovery_read (sites with steps.md).
  • extra.wire.sidebar_cta is required. Build refuses without title, description, link, link_text.
  • extra.wire.article_cta is required. Build refuses without title, description, link, link_text.
  • header.offerings required on sites with topics. String resolves from topic name. false to disable.
  • header.cta required on sites with topics. Build refuses without it. false to disable.
  • header.cta.url must not duplicate a content nav URL. CTA is its own nav element.
  • CTA page must have layout: landing. Build refuses otherwise.
  • Offerings child pages must have layout: landing, page, or article.
  • nav is required. No auto-discovery. Site structure is a strategic decision.
  • Nav sections in alphabetical order (3+) = BUILD REFUSED (uncurated). Skipped when offerings configured.
  • resolve_offerings() runs AFTER check_nav_orphans(). Do not reorder.
  • validate_nav_config() runs AFTER resolve_offerings(). Nav is already filtered when checked.
  • redirect: in frontmatter causes BUILD REFUSED (use wire.yml redirects: instead).
  • Unknown frontmatter keys cause BUILD REFUSED.
  • short_title trailing preposition/punctuation causes BUILD REFUSED (incomplete nav label).
  • Numbered slugs (e.g., 2-foo/) cause BUILD REFUSED (use descriptive slugs; date-slugs YYYY-MM-DD allowed).
  • created date on January 1st causes BUILD REFUSED (placeholder detection).
  • Migration artifacts (*.py at site root) cause BUILD REFUSED.
  • Empty topic dir (layout: page, no content children) causes BUILD REFUSED.
  • Search page + icon are auto-generated. Disable with search: false.

Multi-Language Gotchas

  • Language asymmetry (<20% of default) causes BUILD REFUSED (ghost-town translation).
  • Duplicate titles across languages causes BUILD REFUSED (untranslated content detection).
  • Untranslated page body in wrong language dir causes BUILD REFUSED (stopword detection, min 50 words, supports: de, en, es, fr).
  • Unsupported language in Gate 14 causes BUILD REFUSED (must add stopwords to _STOPWORDS in build.py).
  • Stale per-language DBs (.wire/{lang}/gsc.db) cause BUILD REFUSED (delete old DBs, re-run data).
  • Unified GSC at .wire/gsc.db with lang column.
  • Progress at .wire/{lang}/progress-*.json for multi-language.

Image Gotchas

  • Missing BFL_API_KEY = RuntimeError (hard crash, not silent).
  • Missing image at build time = FileNotFoundError (build error).
  • Images stored as normal files at docs/assets/images/{slug}.png. No cache layer.
  • BFL API: POST to api.bfl.ai/v1/{model}, poll until Ready, download signed URL.

Wire Metadata

  • stamp(**fields) sets date=today, created from git, merges kwargs.
  • Workflow gates: MERGE then DIFFERENTIATE then REFINE then REWORD (cascade blocking).
  • _overlap_slugs(topic) pre-computes merge/diff slug sets for gate checks.
  • _maybe_rediscover() auto-regenerates steps.md after refine/seo/merge/differentiate/improve.

Multi-Language

Wire supports multi-language sites with per-language directories and shared configuration.

Setup

Add to wire.yml:

languages:
  - code: en
    name: English
    docs_dir: docs/en
    default: true
  - code: de
    name: Deutsch
    docs_dir: docs/de

Each language gets its own docs_dir. Pages are matched across languages by URL path convention (same relative path = same content in different language).

Alternate Frontmatter

For pages with different slugs across languages, use alternate: in frontmatter:

---
title: About Us
alternate:
  de: /de/ueber-uns/
---

Alternate mappings must be reciprocal. If EN points to DE, DE must point back to EN. Non-reciprocal mappings cause BUILD REFUSED.

Language Switcher

Wire auto-generates hreflang tags and a language switcher component. Pages without explicit alternate: get homepage links for other languages.

Content Commands with --lang

Content commands require --lang on multi-language sites:

python -m wire.chief refine topics --lang de
python -m wire.chief reword topics --lang en

Exception: data runs all languages automatically when --lang is omitted.

Translate Command

For pages that need translation:

python -m wire.chief translate --lang de

Translates page bodies to match their language directory at roughly $0.01/page.

Multi-Language Scaffold

To convert a single-language site to multi-language:

python -m wire.chief migrate-lang --from en --add de --add fr

This moves docs, generates redirects, and updates wire.yml.

Multi-Language Build Output

  • Root /index.html redirects to default language
  • Each language builds to /{code}/ (e.g., /en/, /de/)
  • Per-language sitemap, RSS, search index, and llms.txt
  • Shared assets go to root output directory

Multi-Language Validation Gates

  1. Language asymmetry: every language must have at least 20% of the default language's page count
  2. Orphan language dirs: language directories with content must be in wire.yml config
  3. Orphan language config: configured languages must have existing directories
  4. Duplicate titles: same title in multiple languages = likely untranslated
  5. Untranslated bodies: stopword analysis detects wrong language in wrong directory (supports de, en, es, fr)
  6. Alternate validation: targets must exist, mappings must be reciprocal

Configuration (wire.yml)

wire.yml is the central configuration file. It must exist in the site root directory.

Required Top-Level Keys

Key Type Purpose
site_name string Site name (used in title, header, JSON-LD)
site_url string Full production URL (e.g., https://example.com)
lang string Site language code (e.g., en, de). Not needed if languages is set.
nav list Navigation structure. No auto-discovery.
logo string or false Logo image path (relative to docs_dir) or false for text-only
favicon string or false Favicon path (relative to docs_dir) or false for none

Optional Top-Level Keys

Key Type Default Purpose
description string "" Site description for meta tags
docs_dir string "docs" Path to markdown source directory
templates_dir string "templates" Path to custom templates directory
output_dir string "site" Path to build output directory
theme dict {} Theme configuration
exclude list [] File patterns to exclude from build
footer dict {} Footer configuration
search bool true Enable/disable search page generation
extra_css list [] Additional CSS files to include
header dict {} Header configuration (offerings, CTA, links)
languages list none Multi-language configuration
redirects list [] URL redirects
copy_dirs list [] Directories to copy verbatim to output
deploy dict {} Deployment configuration
exclude_gsc_data_for_path list [] Path prefixes skipped by GSC coverage check

Header Configuration

header:
  offerings: vendors          # Topic name for offerings bar (Row 1)
  cta:                        # CTA button in sticky nav (Row 2)
    label: Get Started
    url: /getting-started/
  links:                      # Custom links in Row 1 (external auto-open in new tab)
    - title: Blog
      url: https://blog.example.com

extra.wire Configuration

extra:
  wire:
    sidebar_cta:
      title: "Your CTA title"
      description: "Short description"
      link: /your-page/
      link_text: "Learn more"
    article_cta:
      title: "Ready to get started?"
      description: "Book a free consultation."
      link: /contact/
      link_text: "Get in touch"
    labels:
      on_this_page: "On This Page"
      related_articles: "Related Articles"
      read_more: "Read More"
      discovery_start: "Start"        # Only needed with discovery steps
      discovery_read: "Continue"      # Only needed with discovery steps
    refresh_days:                       # Per-topic news refresh interval
      vendors: 21
    default_refresh_days: 21            # Default news refresh interval
    reword_tiers:
      full: 20                          # Top 20% get full reword
      light: 30                         # Next 30% get light reword
    max_articles: 20                    # Max articles per news search
    rate_limit_delay: 1                 # Seconds between API calls
    min_opportunity_score: 15.0         # Minimum score for SEO opportunities
    newsweek_batch_chars: 150000        # Batch size for newsweek extraction
    newsweek_max_news_age_days: 14      # Max age of news for newsweek
    image_style: "Your image style"     # Style prompt for AI image generation
    image_sizes:                        # Custom image presets
      default: [1024, 576]
      wide: [1440, 480]
      square: [512, 512]
    issues_repo: "org/repo"             # GitHub repo for filing issues via skills
    form_id: "abc123"                   # Wire form submission handler ID

Multi-Language wire.yml Configuration

extra:
  wire:
    sidebar_cta:
      en:
        title: "Your CTA"
        description: "Description"
        link: /en/your-page/
        link_text: "Learn more"
      de:
        title: "Ihr CTA"
        description: "Beschreibung"
        link: /de/ihre-seite/
        link_text: "Mehr erfahren"
    article_cta:
      en:
        title: "Ready?"
        description: "Book a consultation."
        link: /en/contact/
        link_text: "Get in touch"
      de:
        title: "Bereit?"
        description: "Beratung buchen."
        link: /de/kontakt/
        link_text: "Kontakt"
nav:
  - Vendors:
    - vendors/vendor-a/
    - vendors/vendor-b/
  - Guides:
    - guides/getting-started/
    - guides/configuration/
  - About: about/

Navigation is explicit. Wire does NOT auto-discover pages. Pages not in nav cause BUILD REFUSED (nav orphan check). Wire auto-discovers children within declared topic sections. Topic sections with 3+ items in alphabetical order cause BUILD REFUSED (uncurated).

Redirects Configuration

redirects:
  - from: /old/path/
    to: /new/path/
    status: 301           # Default: 301 (permanent redirect)
  - from: /removed/page/
    to: ""
    status: 410           # 410 Gone

Redirects are also read from .wire/redirects.yml (internal Wire moves). Both sources are merged. wire.yml wins on conflict.

Exclude Patterns

exclude:
  - '*/comparisons/*'     # Auto-generated comparisons
  - '*/archive/*'         # Archived content
footer:
  copyright: "2025 Company Name"
  links:
    - title: Imprint
      url: /imprint/
    - title: Privacy
      url: /privacy/

Dataclasses and Key Objects

Wire uses these core data structures throughout:

Site

@dataclass
class Site:
    title: str       # From wire.yml site_name
    url: str         # From wire.yml site_url
    description: str # From wire.yml description

Auto-injected as {site} in all prompts.

Content

@dataclass
class Content:
    slug: str      # "abbyy" -- folder name
    title: str     # "ABBYY" -- H1 from index.md
    path: str      # "/vendors/abbyy/" -- absolute URL path
    summary: str   # First paragraph / description

Key methods:

  • item.read_index(): Read the page's index.md content
  • item.save_index(content, preview=False): Save content (with validation, sanitization, quality warnings)
  • item.index_path: Path to index.md file
  • item.fs_path: Filesystem path to item directory
  • item.topic: Topic directory name (e.g., "vendors")
  • Content.from_path(directory, slug): Create from path
  • Content.from_location("vendors/abbyy"): Create from location string

Topic

class Topic:
    directory: str      # "vendors"
    title: str          # From index.md frontmatter
    description: str    # From index.md frontmatter

Key methods:

  • topic.list(): All Content items in this topic
  • topic.get(slug): Single Content item by slug
  • topic.needing_news(days=21): Items needing news refresh
  • topic.find_news(date_str): News files for a date

Module-Level Globals

These are set at import time from wire.yml:

  • SITE: Site object with title, url, description
  • DOCS_DIR: Path to docs directory
  • SITE_DIR: Path to site root (where wire.yml lives, always Path.cwd())
  • WIRE_CONFIG: Dict from extra.wire section of wire.yml
  • TOPIC_LANGUAGES: Per-topic language mapping
  • SITE_LANGUAGE: Site default language name

Prompt System

Wire uses a 3-layer prompt system for AI content generation.

How Prompts Load

load_prompt(topic, action, output="markdown_file", **variables) does three things:

  1. Prepends the styleguide (_style.md). Rules apply to every prompt
  2. Loads the topic prompt from {docs_dir}/{topic}/_{action}.md or falls back to wire/prompts/{action}.md
  3. Calls .format(**variables) with auto-injected {topic} and {site}

Auto-Injected Variables

NEVER pass these manually (causes TypeError: got multiple values):

  • {topic}: Topic object (has .title, .description)
  • {site}: Site object (has .title, .url, .description)

Variable Patterns

  • {item.title}: When variable is a dataclass (Content object)
  • {item_name}: When variable is a string
  • Never mix patterns in the same prompt

Escaping

Python .format() treats { and } as variable delimiters:

  • {{#slug}} for HTML anchor IDs like {#slug}
  • {{Brand}} for literal braces in output examples

Output Formats

  • "markdown_file": Outputs frontmatter + body only (for content pages)
  • "decision": Prefixes with WHY: RELEVANT or WHY: NOT_RELEVANT
  • "none": No format instructions appended

Content Quality System (3 layers)

  1. PREVENT (_style.md): Styleguide teaches Claude rules (title format, linking, citations)
  2. FIX (_sanitize_content()): 9 auto-fixes (pipe to dash, H1 dedup, dedup links, broken links)
  3. DETECT (_warn_content_quality() + _audit_content_quality()): Returns dict of issues

Prompt Structure

Every prompt follows this skeleton:

## Your Role: [Specific Role] for [Context]

[1-2 sentences: what this prompt accomplishes]

## [Instructions / Rules]

[Numbered rules with BAD/GOOD examples]

## [DATA SECTION] [Label: {variable}]

{variable_content}

Prompt Authoring Rules

  1. Start with ## Your Role: and a specific role title
  2. Reference {site.title} or {topic.title} in the role line
  3. Use consistent variable patterns
  4. Escape literal braces as {{ and }}
  5. Data sections use ## LABEL [Context: {variable}] headers
  6. Wrap page content in markdown code fences
  7. Do NOT duplicate rules from _style.md
  8. No hardcoded site names or domains
  9. Include BAD/GOOD examples for non-obvious rules

Newsweek Prompts

Newsweek prompts use _load_newsweek_prompt() which:

  • Injects {site} only (no {topic})
  • Does NOT prepend the styleguide automatically
  • 3-phase map-reduce: EXTRACT, SYNTHESIZE, REVIEW

GSC Database

Wire stores Google Search Console data in a local SQLite database.

Location

  • Single-language: .wire/gsc.db
  • Multi-language: .wire/gsc.db (unified, with lang column on Content table)
  • Stale per-language DBs (.wire/{lang}/gsc.db) cause BUILD REFUSED

Tables

Table Purpose
Content Page-level metrics (impressions, clicks, position)
Keyword Per-keyword metrics per page
Snapshot Data freshness tracking
GscUrl Bulk URL discovery data (dimensions=['page'])

Key Operations

  • fetch_and_store(): ONLY called by data command. All other commands are DB-read-only.
  • discover_urls(): Bulk GSC fetch, stores in GscUrl table, 28-day caching.
  • find_overlaps(): Detect keyword cannibalization between pages.
  • find_dead_pages(): Pages with very low impressions (accepts redirects= parameter).
  • find_missing_redirects(): GSC paths with no page and no redirect.
  • trending_keywords(): Keywords gaining position/impressions.

Overlap Classification

  • Hard overlap: ratio > 0.4, skew > 0.7, same topic = MERGE
  • Soft overlap: ratio > 0.15 = DIFFERENTIATE
  • Monitor: everything else

Redirect-Aware Operations

  • load_redirects(site_dir): Reads both .wire/redirects.yml AND wire.yml redirects: key (merged, wire.yml wins)
  • rekey_from_redirects(redirects, lang=): Rekey Content rows to match redirect targets after restructure
  • find_dead_pages() accepts redirects=: Excludes pages with known redirect sources

Build Guard: GSC Coverage

Every GscUrl with impressions must have a page, redirect, or 410 entry. Build is blocked otherwise. Configure exclude_gsc_data_for_path in wire.yml for web apps on the same domain that are not Wire content.

Audit Output

The audit command produces a structured report with cascade filtering.

Report Structure

  1. HEALTH: Dead pages, redirect gaps, stale data
  2. ACTION: Merge/differentiate recommendations (cascade: merge then diff then news then refine then reword)
  3. SEO: Keyword opportunities, position improvements
  4. INFO: Topic summaries, page counts

Three-State Classification

  • Dead: Truly low impressions, no meaningful traffic
  • Missing redirect: Has impressions but no page (needs redirect or 410)
  • Untracked: No GSC data at all

Templates and Assets

Template Fallback

Jinja2 FileSystemLoader chain:

  1. Customer templates/ directory (first priority)
  2. wire/templates/ defaults shipped with pip (fallback)

Customers do NOT need their own templates or CSS. Defaults ship with pip.

Asset Fallback

  1. Copy wire/assets/ (defaults) first
  2. Overlay customer docs/assets/ on top

Customer assets override defaults for individual files.

Template Types

Template Used for
page.html Default page layout
article.html Content pages (auto-selected for pages with topic+slug). IBM Think-style 3-column (TOC, Content, Sidebar).
landing.html Landing pages. Full customer freedom (no Wire H1, no width constraints).
home.html Homepage with hero section.
base.html Base template with header, footer, scripts.

Landing Page Behavior

  • _landing_sections() splits at <hr> into alternating .section-* bands (hero/light/default)
  • No dark/accent sections. Customers own their own dark styling via custom CSS.
  • Customer owns the H1. Wire does NOT inject one.
  • If first landing section contains <section class="section-hero ...">, Wire skips its wrapper

JavaScript Files Shipped

scroll-title, toc-spy, back-to-top, reading-progress, code-copy, discovery, yt-embed, tabs, forms

Two-Row Header

  • Row 1: Offerings bar (scrolls away) + optional custom links
  • Row 2: Content nav (sticky) + CTA button

Build Output Files

Wire auto-generates these files during build:

  • sitemap.xml
  • robots.txt
  • feed.xml (RSS)
  • feed_rss_created.xml (RSS sorted by creation date)
  • search_index.json (search page data)
  • llms.txt and llms-full.txt (AI-readable site summaries)
  • .htaccess (Apache redirect rules)
  • 404.html
  • CNAME (if configured)
  • bot/ directory (agent-facing site context)

Content Quality System

Layer 1: PREVENT (styleguide)

docs/_styleguide.md is prepended to every prompt via _style.md. Teaches Claude: title format, linking style, citation style, editorial voice, prose over lists.

Layer 2: FIX (sanitize)

_sanitize_content() runs 9 auto-fixes on every save:

  • Pipe characters to dashes
  • H1 dedup (remove extra H1s)
  • Dedup links (same URL linked multiple times in one paragraph)
  • Broken internal link repair
  • And more

Layer 3: DETECT (warn + audit)

_warn_content_quality(): Returns dict of quality issues (warnings, does not block). _audit_content_quality(): Returns dict of issues for audit reporting.

Merge Guard

Merge output must be at least 80% of keeper body length. Below that = preview only (no save).

Source Diversity

analyze_source_diversity(): Flags dominant sources. Threshold: (>3 citations AND >20% from one source) OR (>40% from one source AND >=5 total citations).

File Layout

Site Root

site-root/
  wire.yml           # Required. Central configuration.
  docs/              # Markdown source (configurable via docs_dir)
    _styleguide.md   # Required for content generation
    index.md         # Homepage
    topic-a/         # Topic directory
      index.md       # Topic index page (needs layout: page, short_title)
      item-1/        # Content item directory
        index.md     # Content page (needs created, short_title)
        2025-03-15.md  # Pending news file (integrate with refine)
        news/        # Archived news (after refine)
      item-2/
        index.md
    assets/          # Customer assets (override defaults)
      images/        # Images including AI-generated ones
      css/           # Custom CSS
  templates/         # Customer template overrides (optional)
  site/              # Build output (configurable via output_dir)
  .wire/             # Wire internal data (gitignored)
    gsc.db           # GSC SQLite database
    redirects.yml    # Wire-internal redirect moves
    progress-*.json  # Batch progress tracking (cleaned on completion)

Multi-Language Layout

site-root/
  wire.yml
  docs/
    en/              # English docs directory
      index.md
      topic-a/
        index.md
    de/              # German docs directory
      index.md
      topic-a/
        index.md
  site/
    en/              # English build output
    de/              # German build output
    index.html       # Root redirect to default language

Naming Conventions

  • Directories: lowercase, hyphens (e.g., getting-started/)
  • No numbered prefixes on directories (e.g., NOT 2-setup/)
  • Date-based slugs allowed for news: YYYY-MM-DD-title/
  • Underscore-prefixed files are excluded from build: _style.md, _styleguide.md
  • Pending news files: YYYY-MM-DD.md (processed by refine command)

Newsweek (Weekly Market Intelligence Report)

3-Phase Pipeline

  1. EXTRACT (Phase 1): Evaluate news articles for each topic. Cache at .wire/newsweek_extracts_{date}.md.
  2. SYNTHESIZE (Phase 2): Map-reduce extracts into weekly report.
  3. REVIEW (Phase 3): Editorial review and polish.

Output

docs/news/YYYY-MM-DD-news.md

Shortcuts

python -m wire.chief newsweek --resynth       # Skip Phase 1, reuse cached extracts
python -m wire.chief newsweek --skip-review   # Skip Phase 3 editorial review
python -m wire.chief newsweek --resynth --skip-review  # Fastest iteration on Phase 2 prompt

Redirects

Sources

Wire reads redirects from two locations (merged, wire.yml wins on conflict):

  1. .wire/redirects.yml: Internal Wire moves (auto-generated by merge, restructure commands)
  2. wire.yml redirects: key: User-configured redirects

Format

# List format (preferred):
redirects:
  - from: /old/path/
    to: /new/path/
    status: 301
  - from: /removed/
    to: ""
    status: 410

# Dict format (legacy):
redirects:
  /old/path/: /new/path/

Status Codes

Status Meaning When to use
301 Permanent redirect (default) Page moved to new URL
410 Gone Page permanently removed, no replacement

Build Guard

Every GSC URL with impressions must be covered by a page, redirect, or 410 entry. Use exclude_gsc_data_for_path for web apps that share the domain but are not Wire content.

Redirect Conflict Detection

If a redirect source path also has a docs/ file, build REFUSES (ambiguous state). Delete the file or remove the redirect.

Redirect Chain/Dead-End Detection

Audit detects redirect chains (A redirects to B, which redirects to C) and dead-end redirects (redirect target does not exist).

Migrate GSC After Restructure

After restructuring pages with redirects:

python -m wire.chief migrate-gsc

This rekeys Content rows in the GSC database to match redirect targets.

Enrich Command

The enrich command is a 4-phase content improvement pipeline:

  1. Analyze (free): Local keyword presence, BM25, keyword routing
  2. Filter: Select pages with actionable improvement potential
  3. Web Research (~$0.01/page): Research specific gaps
  4. Improve (~$0.06/page): Apply improvements to content

Position threshold: > 5 (not > 3, due to ads and YouTube results pushing organic down).

python -m wire.chief enrich vendors

Images Command (BFL/FLUX)

Shortcode

!makeIMG[document-processing]{wide}

Presets

Preset Size
default 1024x576
wide 1440x480
square 512x512

Configurable via extra.wire.image_sizes in wire.yml.

Two-Layer Prompt

  1. image_style from wire.yml (site-wide style guidance)
  2. Slug (hyphens converted to spaces) as subject

Storage

Images at docs/assets/images/{slug}.png. No cache layer. CSS class: .wire-img.

CLI

python -m wire.chief images                # Generate missing images
python -m wire.chief images --force        # Regenerate all
python -m wire.chief images --prune        # Remove orphan images

Requirements

  • BFL_API_KEY environment variable (hard crash without it)
  • Missing image at build time = FileNotFoundError

Build Command

Separate module from Chief:

python -m wire.build                       # Full build
python -m wire.build --dirty               # Only modified pages
python -m wire.build --serve               # Build + dev server
python -m wire.build --serve --port 8080   # Custom port

Pipeline

markdown -> frontmatter parse -> mistune render -> Jinja2 template -> HTML

Features:

  • Parallel rendering (8 workers)
  • HTML minification (strips comments, collapses whitespace)
  • CSS minification
  • Auto-lint after build (no opt-out)
  • Auto-QA after build
  • Link sculpting (absolute to relative URL conversion)
  • H1 dedup (strips first H1 from rendered HTML on page/article layouts)
  • TOC extraction (h2/h3, inline details, requires 3+ headings)
  • Reading time calculation (220 words/minute)
  • Image dimension injection (auto width/height attributes)
  • Code copy buttons
  • Back-to-top button
  • Reading progress bar
  • Smooth scroll with header offset
  • External link indicator (CSS-only arrow)

base_path

Auto-derived from site_url via urlparse().path. Enables GitHub Pages subpath deployments (e.g., https://user.github.io/repo/).

Dirty Build

Only rebuilds pages modified since last build. Timestamp tracked via .build_timestamp marker file in output directory.

Excluded Files

These patterns are automatically excluded from build:

  • */YYYY-MM-DD.md (pending news files)
  • _*.md (underscore-prefixed files like _style.md)
  • */comparisons/* (auto-generated comparisons)
  • *.steps.md / steps.md (discovery step files)

Development

Install

pip install -e ".[dev]"    # Development install with test deps

Test

pytest tests/              # Run all tests

Build This Site

python -m wire.build       # Build from repo root (this IS a Wire site)

Test Patterns

  • tmp_path + patch('wire.tools.DOCS_DIR') for filesystem isolation
  • content.py tests: patch BOTH wire.tools.DOCS_DIR and wire.content.DOCS_DIR
  • test_db.py: seeded_db fixture with 5 pages for overlap scenarios
  • TestLoadPrompt: autouse fixture creating docs/vendors/index.md in tmp_path
  • Tests that assert claude_text call count need patch.object(ContentEditor, "_maybe_rediscover")

Visual QA

from wire.qa import capture_screenshots
capture_screenshots(site_dir, output_dir, pages=['guides/foo'])

Requires: pip install playwright && playwright install chromium

Claude AI Integration

  • claude_text(prompt, description, max_tokens=2000, temperature=0.3): CLI-first dispatcher
  • _run_cli(prompt, description, tools): Single entry point for CLI subprocess calls
  • web_search(query, max_results, context, exclude_domains): CLI-first with --tools WebSearch
  • CLI requires claude login (Max subscription)
  • API fallback: auto-streams for prompts >50KB or max_tokens >16K
  • API model: claude-sonnet-4-6

Fixing Lint Issues in Content

When wire.build reports lint issues, fix the content, not the rules. The rules are evidence-based. Every error message tells you what is wrong and how to fix it.

Workflow

# Build once, capture all issues to a file
python -m wire.build 2>&1 | grep -E '\[RULE-' -A1 | grep -v "^--$" > lint.txt

# Read the file. Fix content. Rebuild.
# Do NOT rebuild after every file. Batch your fixes.

Common patterns

Page titles: use : not - as separator. Dashes in titles are best practice for SERPs (Zyppy study), but Wire renders page titles as link text in sidebar and related articles. The spaced hyphen - triggers RULE-52 inside that link text. Colons don't.

BAD:  title: SEO Reference - Thresholds and Evidence
GOOD: title: "SEO Reference: Thresholds and Evidence"

Every article page needs links: 2 sibling links (pages in the same topic), 1 link to the topic index page, and 1 external citation. Add them on first mention of the concept, not as a list at the bottom.

Wire's prompt system determines what Claude sees for every content
operation. See [Writing for Wire](/guides/writing-for-wire/) for
editing guidance and the [Guides overview](/guides/) for all docs.

External citations: use real, authoritative sources relevant to the page content. Google, NNG, SearchPilot, W3C, MDN, GitHub. Not generic links.

Math formulas: no spaces around operators inside backticks. (1-CTR) not (1 - CTR). The spaced minus triggers RULE-52.

Card shortcodes: each card in a :::cards block should link to a DIFFERENT page. Multiple cards pointing to the same URL triggers RULE-81 (conflicting anchor text).

Code examples: use placeholder URLs like /your-topic/slug/ not real site URLs. Real URLs in code fences render as links and trigger lint rules.

PDF links: add (PDF) to the anchor text so visitors know they are downloading. [Title (PDF)](url.pdf) not [Title](url.pdf).

Headings: use parentheses not dashes. ## Module (Description) not ## Module - Description. Same RULE-52 reason as titles.

Troubleshooting: if X fails, try Y

RULE-52 keeps firing after you fixed the markdown: the - is in a page title that appears as link text on OTHER pages (sidebar, related articles). Fix the title in frontmatter, not the body text. Use : instead of -.

RULE-52 fires on a math formula in backticks: the HTML parser splits text nodes around <code> tags. The spaced minus leaks into adjacent text. Fix: remove spaces around minus (1-CTR) not (1 - CTR).

RULE-81 fires on card shortcodes: you have multiple :::cards entries linking to the same URL with different card descriptions. Each card MUST link to a different page. If you're demoing cards, use real different destinations, not the same page 3 times.

RULE-81 fires on pricing CTAs: "Get started" and "Start free trial" both point to the same page. Point the secondary CTA to a different page (e.g., managed service, workflow, about).

RULE-71 says "needs topic index link" but you added one: check if the link renders with the correct href. Links in the last line of a file sometimes don't render if there's no trailing newline. Ensure the file ends with \n.

RULE-85 says "no external links" but you added one: if the link points to a PDF, it may count for RULE-85 but trigger RULE-102 (missing PDF indicator). Add (PDF) to anchor text.

Lint count goes UP after fixing: you probably introduced broken links (RULE-33) or trailing slash issues (RULE-22) with anchor fragment links like /page/#section. Wire checks that the target page exists but anchor fragments on pages with trailing slashes cause false positives. Point to the page URL without fragments.

Build says "0 lint issues" locally but CI fails: check for !makeIMG shortcodes in headings. ### !makeIMG[slug] is parsed as an image reference. Wrap in backticks: ### `!makeIMG[slug]`.

You fixed 30 pages but lint count barely changed: many RULE-52 hits come from the SAME source (a page title with - that appears as link text on 20 other pages). Fix the title once, all 20 hits disappear.

What to do when the rule is wrong for your content

If a rule fires but the content is genuinely correct (a page about "prompts" repeating "prompt", a reference page with many short sections), that is a rule-level issue, not a content issue. Do not change correct content to satisfy a rule. File a ticket, document the issue, and move on. Rule changes get their own ticket with evidence research.