← Files NimbleARCHIVED FILE

skills/seo-intel/references/wf-ai-visibility.md

19.7 KB · Oct 5, 2026 · 18:08 UTC

↓ Download file

# AI Visibility Audit

Measures brand presence across AI-generated answers and surfaces competitive gaps.


---

## Instructions

### Step 0: Preflight

Follow the transport selection + standard preflight from `references/nimble-playbook.md` — pick CLI or MCP at session start, then run the standard preflight calls (date calc, today, profile, memory index) in parallel.

From the results:
- CLI missing or API key unset → `references/profile-and-onboarding.md`, stop
- Tag all `nimble` CLI calls: `nimble --client-source skill-seo-intel <subcommand>`. MCP path: not yet supported — see `references/nimble-playbook.md` for status.
- Profile exists → check for prior snapshot data at
  `~/.nimble/memory/ai-visibility/{brand-slug}/`. If a `snapshot-*.json` file exists,
  load the most recent one — it provides the baseline for delta detection in Step 8.
  Also check `~/.nimble/memory/reports/seo-ai-visibility-{brand-slug}-*.md` for
  same-day runs. If a same-day report exists, ask: "Already ran today. Run again for
  fresh data?" Don't silently re-run.
- No profile → Step 1

### Step 1: First-Run Onboarding (2 prompts max)

Follow `references/profile-and-onboarding.md` for the full onboarding flow. If a
business profile already exists, skip this step entirely.

### Step 2: Shape Scope (2 prompts max)

**Prompt 1** — ask in plain text (NOT AskUserQuestion with options):

> "Which brand should I audit for AI visibility? (Brand name + domain, e.g.,
> Acme Corp / acme.com)"

If `$ARGUMENTS` already contains a brand name or domain, skip this prompt. If only
one is provided, infer the other from the business profile or ask.

**Prompt 2** — confirm scope and gather inputs (use AskUserQuestion):

> I'll audit **{brand}** ({domain}) across AI search surfaces.
>
> **Query set:** How should I choose which queries to test?
> - **From profile** — pull from your `industry_keywords` + recent
>   `seo-keyword-research` results
> - **I'll provide a list** — paste queries directly
> - **Auto-discover** — I'll search for queries where your brand appears
>   and category queries where it should appear
>
> **Competitors:** {list from profile, or "Who are your main competitors?"}
>
> **Platforms:**
> - **All five** — ChatGPT + Perplexity + Google AI + Gemini + Grok
> - **Top three** — ChatGPT + Perplexity + Google AI (fastest, most reliable)
> - **Specific platforms** — pick which to test

Parse the brand name, domain, query source preference, competitor list, and platform
selection from the responses.

### Step 3: Query Discovery (if auto or profile)

**User-provided list:** Parse and deduplicate. Cap at 40 queries.

**From profile:** Combine `industry_keywords` + top keywords from the most recent
`seo-keyword-research` report. Target 20-40 queries.

**Auto-discover:** Run 4 parallel searches (`"{brand} {category}"`, `"best {category}"`,
`"{category} comparison"`, `"{category} reviews"`) using `industry_keywords` as
category terms. Filter for queries where the brand or competitors surface. Present
the list for user confirmation. Target 20-40 queries.

### Step 4: WSA Discovery

Never hardcode agent template names — discover them dynamically every run
and validate before use, per `references/nimble-playbook.md`.

Search for relevant agents in parallel. Run separate searches per surface so
the AI platform agents and the SERP agents can be discovered independently:

```bash
# SERP surfaces
nimble extract:templates list --limit 100  # then filter items for "google serp"
nimble extract:templates list --limit 100  # then filter items for "search engine"

# AI platform surfaces
nimble extract:templates list --limit 100  # then filter items for "chatgpt"
nimble extract:templates list --limit 100  # then filter items for "perplexity"
nimble extract:templates list --limit 100  # then filter items for "google ai"
nimble extract:templates list --limit 100  # then filter items for "gemini"
nimble extract:templates list --limit 100  # then filter items for "grok"
```

For each promising match, validate with `nimble extract:templates get --extract-template-name {name}`
and confirm the expected input param (`prompt` or `keyword`) and output fields
(`answer`, `sources`). Cache the discovered template names for this run as
`{chatgpt_agent}`, `{perplexity_agent}`, `{google_ai_agent}`, `{gemini_agent}`,
`{grok_agent}`, and `{serp_agent}`. Use those variables everywhere in Step 5
rather than string literals.

If a platform agent is not discovered or fails validation, drop that platform
from the run (or fall back to `nimble search --include-answer` where useful)
and note reduced coverage in the report.

### Step 5: AI Platform Querying via Dedicated Agents

Read `references/ai-platform-profiles.md` for the full agent schemas, per-platform
ranking factors, and Princeton GEO optimization methods.

Use Nimble's dedicated AI platform agents to query **5 platforms** directly. Each
agent sends a real prompt to the platform and returns structured `answer` text +
`sources` with URLs. This replaces the previous approach of `--include-answer`
proxying and flaky Perplexity extraction.

**The 5 platforms** (each row maps to a variable resolved in Step 4):

| Platform | Resolved variable | Input | Key outputs |
|----------|-------------------|-------|-------------|
| ChatGPT | `{chatgpt_agent}` | `prompt` | `answer`, `markdown`, `sources` [{url, title, source, snippet}], `links` |
| Perplexity | `{perplexity_agent}` | `prompt` | `answer`, `markdown`, `sources` [{url, icon, title, snippet, description, startPosition, endPosition}], `links` |
| Google AI Mode | `{google_ai_agent}` | `keyword` | `answer`, `sources` [{url, title}] |
| Gemini | `{gemini_agent}` | `prompt` | `answer`, `markdown`, `answer_html`, `sources` [{icon, title, snippet, description, startPosition, endPosition, source_domain}], `links` |
| Grok | `{grok_agent}` | `prompt` | `answer`, `answer_html`, `sources` [{url, title}], `links`, `images` |

**Query construction:** Phrase queries as natural questions an end-user would ask
an AI assistant. Example: for keyword "web scraping api", the prompt becomes
"What is the best web scraping API?" or "Compare the top web scraping APIs."
For the Google AI agent, use the `keyword` param directly (it's a search query,
not a conversational prompt).

**Execution:** Spawn `nimble-researcher` sub-agents (max 4, `bypassPermissions`).
Reference `references/ai-visibility-agent-prompt.md` for the prompt template.
Assign each agent a batch of 5-8 queries for one platform. Substitute the
discovered template names from Step 4 — do not use the placeholder strings
literally:

```bash
# Per-query, per-platform — {*_agent} come from Step 4 discovery
nimble extract:templates run --template "{chatgpt_agent}" --params '{"prompt": "{query}", "skip_sources": false}'
nimble extract:templates run --template "{perplexity_agent}" --params '{"prompt": "{query}"}'
nimble extract:templates run --template "{google_ai_agent}" --params '{"keyword": "{query}"}'
nimble extract:templates run --template "{gemini_agent}" --params '{"prompt": "{query}", "skip_sources": false}'
nimble extract:templates run --template "{grok_agent}" --params '{"prompt": "{query}"}'
```

For 6+ queries per platform, use `nimble extract:templates batch` with the same
discovered template name:

```bash
nimble extract:templates batch \
  --template "{chatgpt_agent}" \
  --input '{"params": {"prompt": "query 1", "skip_sources": false}}' \
  --input '{"params": {"prompt": "query 2", "skip_sources": false}}'
```

**Also run traditional SERP** for Google AI Overview detection:

```bash
nimble search --query "{query}" --search-depth deep --country US --max-results 10
```

This captures whether the query triggers an AI Overview in standard Google Search
(distinct from Google AI Mode). Check the response for AI Overview indicators.

**Agent coordination:**
- 5 platform agents + 1 SERP track = 6 data sources per query
- Max 4 sub-agents concurrently — cycle through platforms
- Each agent returns `data.parsing.answer` + `data.parsing.sources`
- If an agent fails, retry once. If still failing, exclude that platform for
  that query and note it. Don't fabricate data for unreachable platforms.

**Fallback for agent unavailability:** If any agent is not found during WSA
discovery (Step 4), fall back to `nimble search --include-answer` for that
platform's queries and note reduced data quality in the report.

### Step 6: Citation & Mention Analysis

For each query + platform combination, analyze the returned content and extract:

| Field | Description |
|-------|-------------|
| `brand_mention` | Brand name appears in the AI answer text (case-insensitive, including common variants) |
| `domain_citation` | Brand's domain appears in the cited sources list |
| `position_in_answer` | Character offset of first brand mention, or ordinal position among cited sources |
| `sentiment` | Sentiment of the sentence containing the brand mention: positive / neutral / negative / unknown |
| `competitor_mentions` | List of competitor names found in the answer text |
| `competitor_domain_citations` | List of competitor domains found in the cited sources |
| `answer_excerpt` | 1-2 sentence snippet around the brand or competitor mention for evidence |

Detection rules:
- **Brand mention:** case-insensitive match of brand name and common abbreviations
  (e.g., "Acme Corp", "Acme", "ACME"). Check the full AI answer text.
  **False-positive guard for common-word brands:** If the brand name is a common
  English word (e.g., "Nimble", "Stripe", "Notion", "Craft"), require EITHER:
  (a) the brand name appears within 5 words of a domain-relevant term (e.g.,
  "Nimble" near "API", "scraping", "web data"), OR
  (b) the brand's domain is also cited in the sources list.
  A standalone mention of the common word without contextual proximity or domain
  citation is likely generic usage, not a brand mention. Record as `brand_mention:
  false` with a note: "generic usage — not brand mention."
- **Domain citation:** normalize URLs to root domain (strip `www.`, protocol, path)
  and compare against brand domain and competitor domains.
- **Sentiment:** analyze only the sentence(s) containing the brand mention. Use
  positive/negative signal words. Default to "unknown" if ambiguous.

### Step 7: Scoring

Compute the following metrics from the per-query analysis:

**AI Visibility Score** = percentage of queries where the brand has at least one
mention OR domain citation in any AI answer across tested platforms.

```
visibility_score = queries_with_brand_presence / total_queries * 100
```

**Share of AI Voice** = brand's share of total mentions and citations across all
queries, compared against competitors.

```
brand_signals = brand_mentions + brand_citations
all_signals = brand_signals + sum(competitor_mentions + competitor_citations)
share_of_voice = brand_signals / max(all_signals, 1) * 100
```

**Zero-signal guard:** If `all_signals == 0` (no brand or competitor appears in any
AI answer), set `share_of_voice = 0` and note in the report: "No brands detected in
AI answers for the tested queries — the space may not yet trigger AI-generated
responses." Do not divide by zero.

**Platform Breakdown** — per-platform metrics:
- Queries where an AI answer was present
- Brand mentioned count
- Brand domain cited count
- Coverage rate (brand present / AI answers present)

**Citation Rate** — how often brand mentions translate into domain citations:

```
citation_rate = domain_citations / max(brand_mentions, 1)
```

A high mention count with low citation rate means the brand is discussed but not
linked — an optimization opportunity.

**Competitor Gap** — queries where at least one competitor appears in an AI answer
but the brand does not. These are the highest-priority optimization targets.

If a platform was unreachable for some or all queries, compute scores only from
platforms that returned data. Never fabricate metrics for missing platforms — report
the gap explicitly.

### Step 8: Delta Tracking

If a prior snapshot exists (loaded in Step 0), compare the current run against it:

- **New citations gained:** queries where brand was absent before but appears now
- **Citations lost:** queries where brand was present before but absent now
- **Competitor movements:** competitors that gained or lost visibility since last run
- **AI Overview coverage changes:** queries that newly trigger (or stop triggering)
  AI Overviews
- **Score changes:** visibility score and share of voice delta (current - previous)

Surface only changes in the TL;DR. If this is the first run (no prior snapshot),
skip delta tracking and present the full baseline.

### Step 9: Save & Update Memory

Make all Write calls simultaneously:

- **Report** → `~/.nimble/memory/reports/seo-ai-visibility-{brand-slug}-{YYYY-MM-DD}.md`
  (save the full audit report)

- **Snapshot** → `~/.nimble/memory/ai-visibility/{brand-slug}/snapshot-{YYYY-MM-DD}.json`
  (structured JSON of all per-query results — used for delta tracking in future runs)

  Snapshot schema:
  ```json
  {
    "brand": "...",
    "domain": "...",
    "date": "YYYY-MM-DD",
    "competitors": [...],
    "queries": [
      {
        "query": "...",
        "platforms": {
          "chatgpt": { "ai_answer_present": true, "brand_mention": false, "domain_citation": false, "sources": [...], "error": null },
          "perplexity": { ... },
          "google_ai": { ... },
          "gemini": { ... },
          "grok": { ... }
        }
      }
    ],
    "scores": {
      "visibility_score": 65.0,
      "share_of_voice": 28.5,
      "citation_rate": 0.42,
      "platform_breakdown": { ... }
    }
  }
  ```

- **Profile** → update `last_runs.seo-ai-visibility` in
  `~/.nimble/business-profile.json`

- Follow the wiki update pattern from `references/memory-and-distribution.md`: update
  `index.md` rows for affected directories, append a `log.md` entry for this run.
  Create `~/.nimble/memory/ai-visibility/` and
  `~/.nimble/memory/ai-visibility/{brand-slug}/` directories if they don't exist.

### Step 10: Share & Distribute

**Always offer distribution — do not skip this step.** Follow
`references/memory-and-distribution.md` for connector detection, sharing flow, and
source links enforcement.

**Slack-specific:** Post TL;DR only — not the full report. Format:

> AI Visibility: {brand} — {visibility_score}% visible across {N} queries.
> Share of Voice: {share_of_voice}% (vs {top_competitor} at {competitor_share}%).
> {Top finding}. Full report saved locally.

### Step 11: Follow-ups

- **Drill into a query** → show full answer text, all citations, competitor detail
- **Expand query set** → add more queries and re-run for those
- **Compare with previous run** → load prior snapshot and diff
- **"Looks good"** → done

**Sibling skill suggestions:**

> **Next steps:**
> - Run `seo-content-gap` to find content opportunities for queries where
>   competitors are cited but you're not
> - Run `seo-keyword-research` to expand the query set with high-intent terms
> - Schedule weekly re-runs to track visibility trends over time

If the user's agent platform supports a `loop` pattern, mention it as an option
for automated weekly monitoring.

---

## Output Format

```
# AI Visibility Report — {Brand} — {Date}

## TL;DR
{Brand} appears in AI answers for {X}% of {N} queries tested.
Share of AI Voice: {Y}% vs {top competitor} at {Z}%.
{Delta summary if re-run: "+3 new citations gained, -1 lost since {last_date}."}
{Top 2-3 findings with specifics.}

## Platform Breakdown

### Google AI Overviews
- Queries triggering AI Overview: {N}/{total}
- Brand mentioned in AIO: {N}
- Brand domain cited in AIO: {N}
- Coverage rate: {N}%
- Top queries where brand appears:
  - "{query}" — mentioned + cited, position {N} — [source]({url})
  - "{query}" — mentioned only — [source]({url})

### Perplexity
- Queries with answers: {N}/{total}
- Brand mentioned: {N}
- Brand domain cited: {N}

### ChatGPT
- Queries with answers: {N}/{total}
- Brand mentioned: {N}
- Brand domain cited: {N}

### Gemini
- Queries with answers: {N}/{total}
- Brand mentioned: {N}
- Brand domain cited: {N}

### Grok
- Queries with answers: {N}/{total}
- Brand mentioned: {N}
- Brand domain cited: {N}

## Query-Level Results

| Query | Google AIO | ChatGPT | Perplexity | Gemini | Grok | Competitor(s) |
|-------|-----------|---------|------------|--------|------|---------------|
| {query} | Cited | Mentioned | Cited | — | — | WidgetCo |

## Competitor Comparison

| Brand | Visibility Score | Share of Voice | Citation Rate | Queries Present |
|-------|-----------------|----------------|---------------|-----------------|
| {Brand} | {X}% | {Y}% | {Z} | {N}/{total} |
| {Competitor1} | {X}% | {Y}% | {Z} | {N}/{total} |
| {Competitor2} | {X}% | {Y}% | {Z} | {N}/{total} |

## Optimization Opportunities

### High-Priority (competitor visible, you're not)
- "{query}" — {Competitor} cited on {platform}, you're absent
  Suggested action: {specific recommendation}

### Quick Wins (mentioned but not cited)
- "{query}" — brand mentioned in {platform} answer but domain not in sources
  Suggested action: {specific recommendation to earn citation}

### Coverage Gaps (no AI answer yet, but emerging)
- "{query}" — no AI Overview triggered yet; early content positioning opportunity

## GEO Optimization Playbook

For each high-priority gap (competitor visible, you're not), recommend specific
Princeton GEO methods from `references/ai-platform-profiles.md`:

| Gap Query | Platform(s) | Recommended GEO Methods | Expected Boost |
|-----------|-------------|------------------------|----------------|
| {query} | ChatGPT, Perplexity | Cite Sources (+40%), Add Statistics (+37%) | High |
| {query} | Google AIO | Authoritative Tone (+25%), Technical Terms (+18%) | Medium |

Per-platform actions and content block sizing guidance are in
`references/ai-platform-profiles.md` — reference it directly rather than
restating here. Key rules: GEO blocks = 134-167 words; AEO blocks = 40-55
words; keyword stuffing hurts AI visibility by -10%.

## What This Means
Strategic interpretation: what the visibility scores mean for the brand's
discoverability in AI-first search. Where the brand is strong, where it's
vulnerable, and what the competitor landscape looks like across AI surfaces.

Recommended follow-ups:
- Run `seo-content-gap` for content opportunities on high-priority gap queries
- Run `seo-keyword-research` to expand the query set
- Schedule weekly re-runs to track visibility trends
```

---

## Error Handling

See `references/nimble-playbook.md` for the standard error table (missing API key,
429, 401, empty results, extraction garbage). Skill-specific errors:

- **Agent returns error or empty:** Retry once. If still failing, log the platform
  as "unavailable" for that query and continue with others. Do not abort for a
  single platform failure. Common agent errors: timeout (the AI platform took too
  long), rate limit (too many concurrent requests — reduce batch size).

- **Gemini 500 errors:** Intermittent platform issue. Retry once; if still failing,
  exclude Gemini and note "Gemini unavailable" in the report. Score from remaining
  platforms only.

- **429 on Google searches:** Reduce sub-agent concurrency from 4 to 2. If 429
  persists, switch to sequential queries with a brief pause between calls. Note
  reduced throughput in the report but complete the run.

- **Google AI Overview not present for a query:** This is expected — not all queries
  trigger AI Overviews. Record `ai_answer_present: false` and move on. Do not treat
  this as an error.

- **Sub-agent returns partial results:** Merge what the agent returned. Re-run only
  the missing queries directly from the main context. Don't re-run queries that
  already have data.

- **All platforms fail for a query:** Record the query as "all platforms unreachable"
  in the results. Exclude it from score calculations but list it in the report so
  the user knows the query was tested.

SHA-256: 1aba0b456206419456dac56df8a3a7fdfffa5d6951aa2b7cf098b896c655607b