Claude Cursor Skill

google-ads-audit

Google Ads account audit and business context setup. Run this first — it gathers business information, analyzes account health, and saves context that all other ads skills reuse. Trigger on "audit my ads", "ads audit", "set up my ads", "onboard", "account overview", "how's my acc

LLM Mart · 0 points · 23 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download nowork-studio-notfair-plugin-google-ads_audit-f5275f1.zip · 17 KB
Part of nowork-studio/notfair-plugin — 88 skills

Install

skills CLI npx skills add https://github.com/nowork-studio/notfair-plugin/tree/main/google-ads/audit
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install nowork-studio-notfair-plugin@llmmart
Git git clone https://github.com/nowork-studio/notfair-plugin.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole nowork-studio/notfair-plugin collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Google Ads Audit

Diagnose account health and persist business context for downstream skills (/google-ads, /google-ads-copy, /google-ads-landing). Read-only — never mutates the account. The user runs /google-ads to execute fixes you recommend.

Setup

Follow ../shared/preamble.md (MCP detection, account selection) and ../shared/analysis-principles.md (evidence requirement, guardrails). Both apply throughout this skill.

Filesystem contract (must persist)

Artifact Path When
Business context {data_dir}/business-context.json First full audit, or refresh when audit_date is >90 days old. Skip on scoped audits if file is fresh.
Personas {data_dir}/personas/{accountId}.json Every full audit.

These are the handoff to every other ads skill — write them even if the report is short. Otherwise /google-ads-copy and /google-ads-landing operate without business context and produce generic output.

business-context.json schema: business_name, industry, website, services[], locations[], target_audience, brand_voice{tone, words_to_use[], words_to_avoid[]}, differentiators[], competitors[], seasonality{peak_months[], slow_months[], seasonal_hooks[]}, keyword_landscape{high_intent_terms[], competitive_terms[], long_tail_opportunities[]}, social_proof[], offers_or_promotions[], landing_pages{}, unit_economics{aov_usd, profit_margin, source}, notes, audit_date, account_id.

personas JSON schema: {account_id, saved_at, personas: [{name, demographics, primary_goal, pain_points[], search_terms[], decision_trigger, value}]}. See references/persona-discovery.md.

Policy freshness check (run first)

Read ../shared/policy-registry.json. For each entry where last_verified + stale_after_days < today:

  • Any entry without a direct current first-party Google source is a hypothesis, not an audit rule or benchmark. Do not use it for a finding or recommendation without verification.
  • High-volatility → search the official Google Ads Help, Ads & Commerce blog, or Google Ads developer documentation for the category; compare the source with the recorded rule. If it drifted, omit the stale rule and banner the limitation.
  • Moderate-volatility → verify it when it could affect a material finding; otherwise omit it rather than repeating a stale caveat.
  • Stable → skip silently.

Phase 1 — Pull the audit dataset

Choose available read capabilities for the requested audit scope. Batch related reads where useful and supported; consult current server guidance for schemas and limits.

You decide the exact GAQL shape, but a defensible audit needs to see, at minimum:

  • Account-level rollups (customer)
  • Campaign performance with bidding strategy, network, and impression-share metrics (campaign, 90-day cap for impression-share data)
  • Ad-group performance (ad_group)
  • Keyword performance with Quality Score and components (keyword_view)
  • Search terms (search_term_view)
  • Negative keywords and shared lists (campaign_criterion + shared sets)
  • Conversion actions (conversion_action) — including counting type, attribution model, primary/secondary
  • Network segmentation (segments.ad_network_type) when diagnosing CPA/CVR shifts or Search Partners
  • RSA assets (ad_group_ad)
  • Geo targeting (campaign_criterion LOCATION + PROXIMITY)
  • Recent change events (change_event, last 30 days) — for explaining regressions

Aggregate inside the script. Return summarized JSON, not raw rows. The agent narrates; the script does the math.

Use platform recommendations or account-setup diagnostics as optional cross-checks when available and relevant to the question.

If a read fails, follow actionable recovery guidance. Clearly report missing evidence; continue independent findings only when the available data supports them.

Skip scoring entirely if totalSpend == 0 or activeCampaigns == 0. Go straight to business context.

Phase 2 — Scope handling

If the user narrows the audit ("focus on one campaign", "campaign X", "just check waste"):

  • Match campaign names by case-insensitive substring. If no match, list available campaigns and ask.
  • Filter the in-memory dataset before analysis — no extra API calls.
  • Account-level dimensions (conversion tracking, account guardrails) stay account-wide. Note "Scoped to: X" in the report.
  • Skip Phase 4 (business context refresh) on scoped audits if business-context.json is fresh.

Phase 3 — Diagnose

The audit's headline output is three pulse metrics — Waste (\(/mo), Demand captured (%), CPA (\)) — each annotated with its top contributor and a pointer to the fix. Read references/account-health-scoring.md for the formula, annotation rules, signal-failure overrides, and audit-history.json schema. The pulse metric IS the verdict; you don't add a letter grade or 0–5 score on top.

To compute and back the pulse metrics, you'll need to look across these seven areas. They are diagnostic surface area, not graded dimensions:

  1. Signal Quality (account-level) — measurement integrity. If broken, STOP here and recommend pausing spend until it's fixed. Pulse metrics are meaningless without measurement (apply the signal-failure override on the Waste line per the reference).
  2. Campaign Structure — keywords per ad group, brand vs. non-brand separation, channel mixing, naming, budget logic.
  3. Keyword Health — Quality Score weighted by spend, zombie keywords, match-type discipline.
  4. Search-Term Quality — wasted spend, brand-leakage, negative coverage, conversion-worthy terms not yet keywords.
  5. Ad Copy & Creative — RSA coverage, asset variety, sitelink/callout/structured-snippet completeness, PMax asset-group health.
  6. Impression Share — read rank-lost vs budget-lost together (see the 2×2 matrix in account-health-scoring.md); they're different problems with different fixes.
  7. Spend Efficiency — waste vs. headroom, brand vs. non-brand split, concentration risk.

For Signal Quality and network-mix questions, read references/conversion-network-audit.md. It adds the prerequisite checks for conversion-action integrity, Search Partners, Display leakage in Search campaigns, and regression decomposition.

Per-area findings only show up in the report when the area surfaced something material. Cite specific entities, dollars, and time windows. "Some keywords are underperforming" is not a finding; "Campaign X has $1,840 in last-30-day spend on 12 keywords with 0 conversions and QS ≤ 4" is.

For unit-economics-aware framing: if business-context.json.unit_economics.aov_usd and profit_margin exist, frame waste and headroom in dollars saved / captured per month, not "above account average". See ../shared/ppc-math.md.

Phase 4 — Business context

Derive what you can from data already pulled:

Field Source
business_name customer.descriptive_name
services Campaign + ad-group names, top converting keywords
locations campaign_criterion LOCATION + PROXIMITY
brand_voice Top-performing RSA headlines / descriptions
keyword_landscape.high_intent_terms Converting keywords with strong CVR
keyword_landscape.competitive_terms Keywords in campaigns with high rank-lost-IS
keyword_landscape.long_tail_opportunities Converting search terms not yet promoted to keywords
website Apex domain from ad final URLs

Then crawl the website (homepage + about + services + top 3 ad landing pages, parallel WebFetch) and merge into the schema. See references/business-context.md.

Ask the user — it's faster than guessing — for: differentiators, competitors, seasonality, unit economics (AOV, margin). Ask for everything else only if the data + crawl can't answer it.

Phase 5 — Personas

Discover 2–3 personas from search terms, top keywords, ad-group themes, landing pages, geo, and device split — all from the dataset already in memory. Persist to {data_dir}/personas/{accountId}.json. Each persona must be grounded in 5+ actual search terms; if not, drop it. See references/persona-discovery.md.

Phase 6 — Report

Structure: pulse metrics (3 lines, each with number + top contributor + fix pointer) → per-area findings (only those that surfaced something material) → Quick Wins section (per the rules in references/account-health-scoring.md). Cap at ~80 lines. Every claim cites a specific entity, number, and window.

End with a single closing line after the handoff to /google-ads:

State where any audit artifacts were actually saved. Do not claim hosted audit history unless a live result confirms it.

Guardrails

  1. Read-only skill. Diagnose; don't mutate. Every fix routes through /google-ads (or /google-ads-copy, /google-ads-landing). End the report with one handoff tied to the #1 action.
  2. STOP condition. If conversion tracking is broken, recommend pausing spend until it's fixed before recommending anything else.
  3. Always persist business-context.json and personas/{accountId}.json even if the report is short — downstream skills depend on them.
  4. Name names. Every finding cites specific campaigns, keywords, search terms, and dollar amounts. No generic verdicts.
  5. Show the data, not the score. The pulse metrics are the verdict — three numbers with named contributors and pointers to the fix. No letter grades, no 0–5 ratings hiding the reasoning behind a label.
Files (notfair-plugin)
  • evals
    • evals.json 5.4 KB
      {
        "skill_name": "google-ads-audit",
        "evals": [
          {
            "id": 1,
            "prompt": "Run a full audit of my Google Ads account. Here's the data:\n\n```json\n{\"account\": {\"name\": \"Bay Area Roofing Co\", \"id\": \"9876543210\", \"currency\": \"USD\", \"timezone\": \"America/Los_Angeles\", \"auto_tagging\": true},\n\"campaigns\": [{\"name\": \"Brand\", \"spend\": 800, \"clicks\": 420, \"impressions\": 5200, \"conversions\": 38, \"cpa\": 21.05, \"ctr\": 8.1, \"budget_lost_is\": 5, \"rank_lost_is\": 10}, {\"name\": \"Roof Repair\", \"spend\": 3200, \"clicks\": 580, \"impressions\": 14000, \"conversions\": 22, \"cpa\": 145.45, \"ctr\": 4.1, \"budget_lost_is\": 35, \"rank_lost_is\": 25}, {\"name\": \"Gutter Cleaning\", \"spend\": 1500, \"clicks\": 390, \"impressions\": 8800, \"conversions\": 28, \"cpa\": 53.57, \"ctr\": 4.4, \"budget_lost_is\": 12, \"rank_lost_is\": 15}],\n\"keywords\": [{\"campaign\": \"Roof Repair\", \"keyword\": \"roof repair near me\", \"spend\": 1100, \"clicks\": 180, \"conversions\": 12, \"qs\": 7}, {\"keyword\": \"premium roof repair\", \"campaign\": \"Roof Repair\", \"spend\": 900, \"clicks\": 120, \"conversions\": 2, \"qs\": 4}, {\"keyword\": \"cheap roof repair\", \"campaign\": \"Roof Repair\", \"spend\": 650, \"clicks\": 140, \"conversions\": 0, \"qs\": 3}, {\"keyword\": \"roofing jobs\", \"campaign\": \"Roof Repair\", \"spend\": 280, \"clicks\": 65, \"conversions\": 0, \"qs\": 2}, {\"keyword\": \"gutter cleaning san francisco\", \"campaign\": \"Gutter Cleaning\", \"spend\": 800, \"clicks\": 210, \"conversions\": 20, \"qs\": 9}],\n\"conversion_actions\": [{\"name\": \"Phone Call\", \"type\": \"PHONE_CALL\", \"status\": \"ENABLED\"}, {\"name\": \"Form Submit\", \"type\": \"WEBPAGE\", \"status\": \"ENABLED\"}],\n\"search_terms\": [{\"term\": \"roof repair san francisco\", \"clicks\": 45, \"conversions\": 8, \"spend\": 270}, {\"term\": \"how to start a roofing business\", \"clicks\": 32, \"conversions\": 0, \"spend\": 190}, {\"term\": \"roof repair prices\", \"clicks\": 28, \"conversions\": 3, \"spend\": 140}],\n\"ads_per_group\": {\"Roof Repair - General\": 1, \"Gutter Cleaning - SF\": 3}}\n```",
            "expected_output": "A comprehensive audit with three pulse metrics (Waste $/mo, Demand captured %, CPA $) plus per-area findings. Should identify: Roof Repair campaign has 35% budget-lost IS (severe constraint) AND 25% rank-lost IS (both dimensions problematic — relevance fix first per the 2x2 matrix). 'cheap roof repair' and 'roofing jobs' are waste ($930 combined, 0 conversions). 'premium roof repair' has QS 4 and poor CPA. Roof Repair ad group has only 1 RSA (needs more). Conversion tracking exists but is basic (no enhanced conversions). Should recommend adding 'roof repair san francisco' as keyword (8 conversions). Should flag 'how to start a roofing business' as negative.",
            "files": [],
            "expectations": [
              "Reports three pulse metrics (Waste, Demand captured, CPA) with named top contributor and a fix pointer for each",
              "Does NOT emit a 0-5 dimension score or a numeric overall health score",
              "Identifies Roof Repair campaign's impression share problem using the 2x2 matrix (budget-lost 35% + rank-lost 25%) and recommends fixing relevance before adding budget",
              "Identifies 'cheap roof repair' ($650, 0 conv) and 'roofing jobs' ($280, 0 conv) as wasted spend with specific dollar amounts",
              "Flags 'premium roof repair' QS 4 as needing quality improvement",
              "Notes Roof Repair ad group has only 1 RSA and recommends adding more",
              "Recommends adding 'roof repair san francisco' as a keyword (8 conversions from search terms)",
              "Recommends adding 'how to start a roofing business' as a negative keyword",
              "Quick Wins section present (or top actions with dollar impact) — every claim cites specific entities and numbers"
            ]
          },
          {
            "id": 2,
            "prompt": "Audit my Google Ads account. Here's the data:\n\n```json\n{\"account\": {\"name\": \"Quick Print Co\", \"id\": \"1112223334\", \"currency\": \"USD\"},\n\"campaigns\": [{\"name\": \"All Services\", \"spend\": 2500, \"clicks\": 800, \"impressions\": 20000, \"conversions\": 0, \"cpa\": null, \"ctr\": 4.0}],\n\"conversion_actions\": [],\n\"keywords\": [{\"keyword\": \"printing services\", \"spend\": 800, \"clicks\": 260, \"conversions\": 0, \"qs\": 5}, {\"keyword\": \"business cards\", \"spend\": 600, \"clicks\": 200, \"conversions\": 0, \"qs\": 6}]}\n```",
            "expected_output": "The audit should immediately flag that conversion tracking is completely missing (no conversion actions at all). This is the STOP condition — the audit halts and recommends pausing spend until tracking is fixed. The Waste pulse metric should apply the signal-failure override (cannot compute — conversion tracking broken). The entire $2500 in spend cannot be evaluated for ROI. The #1 recommendation must be to set up conversion tracking before any other optimization.",
            "files": [],
            "expectations": [
              "Flags missing conversion tracking as the #1 critical issue",
              "Applies the signal-failure override on the Waste pulse metric (does not report a dollar waste figure when tracking is broken)",
              "States that the $2500 spend cannot be evaluated without conversion tracking",
              "Recommends setting up conversion tracking as the first action before any keyword or bid optimization",
              "Does not recommend bid or keyword changes since there's no conversion data to base decisions on"
            ]
          }
        ]
      }
      
  • references
    • account-health-scoring.md 9.7 KB
      # Account Health — Diagnostic Reference
      
      Domain knowledge and the audit's structured output format. **Not a checklist.** Pull this account's data, find what's actually broken, and let the data drive the recommendation. Every claim cites specific entities, numbers, and time windows (per `../../shared/analysis-principles.md`).
      
      ---
      
      ## The seven areas to look at
      
      These are where you look for evidence — not boxes to grade.
      
      | Area | The question it answers |
      |---|---|
      | Signal Quality | Can I trust the data? |
      | Campaign Structure | Am I in the right auctions, organized so each one makes sense? |
      | Keyword Health | Are my keywords pulling weight, or burning money? |
      | Search Term Quality | Are queries reaching me actually relevant? |
      | Ad Copy & Creative | Are my ads competitive once an auction starts? |
      | Impression Share | Why am I losing the auctions I lose? |
      | Spend Efficiency | Where is the money going, and is it producing customers? |
      
      ---
      
      ## Signal Quality — diagnostic facts
      
      If signals are broken, every downstream decision is built on lies. Smart Bidding can't optimize what it can't measure. Patterns to recognize:
      
      - **0 conversions in 30 days with spend > 5× account CPA** → tracking is almost certainly broken or missing.
      - **Conversion rate > 50%** → tag firing on page load, not on actual conversions.
      - **All conversions = "Website" with no action names** → default tracking only; no segmentation.
      - **Last-click attribution** → deprecated since 2023; data-driven attribution is the only model accepted for new conversion actions. Existing conversion actions on last-click should migrate.
      - **Smart Bidding (tCPA / tROAS) with < ~15 conversions/month** → insufficient data density for the algorithm to learn (typical floor is 30+ for tCPA, 50+ for tROAS).
      - **Missing Consent Mode v2 with EU traffic** → degraded conversion modeling in privacy-regulated markets; bid strategies underperform.
      - **Enhanced conversions disabled** → cross-device / cross-browser attribution gaps; Smart Bidding is missing signal.
      
      **STOP condition:** if Signal Quality is broken, recommend pausing spend until it's fixed before any other recommendation.
      
      ---
      
      ## Impression Share — the most important read in the audit
      
      Rank-lost-IS and budget-lost-IS are two different problems with two different fixes. Treating them as one "impression share problem" produces bad advice — telling someone with a relevance problem to spend more burns money faster.
      
      - **Lost IS (Rank)** = relevance problem. Ad Rank is low; the auction is telling you the ads, themes, or landing page aren't competitive. Fix with better creative, tighter themes, better landing pages — not more money.
      - **Lost IS (Budget)** = scaling opportunity. You're winning auctions but running out of gas. Fix with more budget or narrower targeting, *if* the campaign is profitable.
      
      **The 2×2:**
      
      | | Rank-Lost IS LOW | Rank-Lost IS HIGH |
      |---|---|---|
      | **Budget-Lost IS LOW** | Healthy — optimize at the margins | Relevance problem — fix QS / ads / pages, do **not** add budget |
      | **Budget-Lost IS HIGH** | Capital problem — add budget if profitable, or narrow geo / daypart | Structural problem — wrong keywords or wrong audience entirely; rebuild |
      
      **Data caveat:** impression-share metrics in GAQL only return up to 90 days. Don't use `LAST_365_DAYS` on any query that selects `metrics.search_*_impression_share`.
      
      ---
      
      ## Wasted spend — formula
      
      Wasted spend is three things, summed and de-duplicated:
      
      ```
      Keyword waste     = Spend on Tier 2/3 keywords with 0 conversions AND clicks past significance gate
      Search-term waste = Spend on search terms with low relevance (irrelevant intent, clear non-buyer signals)
      Structural waste  = Spend on Display Network impressions inside Search-channel campaigns
                          (network_settings.target_content_network = TRUE on a SEARCH campaign)
      ```
      
      **De-duplicate:** search-term waste from a wasted keyword is already counted in keyword waste — don't double-count. Tier 1 (core) keyword underperformance is an optimization opportunity, not waste.
      
      ---
      
      ## Brand vs. non-brand — the most misleading rollup
      
      Many accounts show great overall ROAS where the majority comes from brand traffic — paying Google a tax on existing customers, not acquiring new ones. Always report the split:
      
      > "Overall CPA is $X, but brand CPA is $Y and non-brand CPA is $Z."
      
      If brand and non-brand aren't in separate campaigns, that itself is a structural finding. PMax running alongside Search with declining brand impression-share = cannibalization; flag and recommend brand exclusions.
      
      ---
      
      ## Pulse Metrics — the audit's headline output
      
      The audit doesn't emit a letter grade or a 0–5 score. It surfaces **three pulse metrics**, each annotated with its biggest contributor and a pointer to the fix. The metric IS the verdict — read it as dollars and act on it directly.
      
      Every pulse metric answers three questions inline:
      1. **What's the number?** (raw value)
      2. **What's driving it?** (the single biggest contributor, named)
      3. **Where do I fix it?** (specific handoff or action)
      
      ### The three metrics
      
      | Metric | Measures | Better = | Compute |
      |---|---|---|---|
      | **Waste** | $/mo burning on zero-conversion spend | Lower | Wasted-spend formula above, extrapolated to 30 days |
      | **Demand captured** | % of eligible impressions won on profitable campaigns | Higher | Spend-weighted avg `search_impression_share` across campaigns with ≥ 1 conversion. If `unit_economics` exists, filter to campaigns with `CPA ≤ Break-Even CPA` |
      | **CPA** | Cost per conversion | Lower or stable | `total spend / total conversions` — compare to industry benchmarks below or to `unit_economics.break_even_cpa` if available |
      
      ### Annotation rules
      
      **Waste line.** Dollar value extrapolated to 30 days (`$X/mo, Y% of spend`) + top contributor (keyword / search term / campaign) + pointer to the fix.
      - *Signal-failure override:* if conversion tracking is broken, replace the dollar figure with `⚠️ Cannot compute — conversion tracking broken` and point to the tracking fix. Waste is meaningless when conversions can't be measured.
      
      **Demand-captured line.** Percentage + top single opportunity (campaign + headroom in $/mo, margin-aware where possible) + pointer.
      - *Relevance override:* if rank-lost-IS > 30% on the named campaign, flag that more budget won't help — "fix relevance first" — and point at the relevance fix instead.
      
      **CPA line.** Dollar value + context (`vs industry $Y–$Z` from `industry-templates.json`, or `vs break-even $Y` from `unit_economics`) + the single biggest structural driver if CPA is unhealthy (which campaign is pulling it up; which QS component is below average).
      
      ### Quick Wins section
      
      After the pulse metrics and per-area findings, emit a `## Quick Wins` section containing every finding where:
      
      ```
      dollar_impact_usd >= 200 AND time_to_fix IN ('<5min', '<15min')
      ```
      
      Plus any **signal/tracking/policy fix** regardless of dollar value — these qualify unconditionally because they unblock measurement.
      
      Sort by dollar impact descending (signal fixes pinned to top). Max 5 items. If none qualify, omit the section — don't fabricate.
      
      Every Quick Win includes the executable command where applicable. Examples:
      - `Add 7 negatives to Example City Search — saves ~$340/mo (<5 min) · /google-ads add negatives to Example City Search: jobs, careers, salary, diy, free, reddit, training`
      - `Enable Enhanced Conversions — unblocks measurement (<15 min) · Configure in Google Ads UI`
      
      ### `time_to_fix` field
      
      Every finding carries `time_to_fix ∈ <5min | <15min | <30min | <2h | >2h`. This is how long the fix takes, not how important it is. The dollar figure carries priority — sort and filter on dollars.
      
      ### Persisting to `audit-history.json`
      
      ```json
      {
        "date": "2026-04-14",
        "date_range": "2026-03-15 to 2026-04-14",
        "account_id": "7521406707",
        "mode": "full",
        "total_spend": 14320.00,
        "total_conversions": 72,
        "metrics": {
          "waste": {
            "usd_per_month": 1240,
            "pct_of_spend": 8.7,
            "top_contributor": "keyword 'free estimate' — $340/mo",
            "tracking_blocker": false
          },
          "demand_captured": {
            "pct": 42.7,
            "top_opportunity": "Example City Search — ~$2,100/mo headroom at 35% budget-lost IS",
            "rank_lost_blocker": false
          },
          "cpa": {
            "usd": 19.88,
            "benchmark_low": 25,
            "benchmark_high": 65,
            "break_even": 72,
            "trend_vs_last": -2.14
          }
        },
        "top_actions": [
          "Paused 'free estimate' keyword ($120 waste)",
          "Budget-lost IS 40% on Example City Search at $14 CPA"
        ],
        "next_milestone": null
      }
      ```
      
      On re-audits, diff the three numbers directly:
      
      - `Waste: $640/mo (4.1%) _(was $1,240/mo — 3 fixes applied)_`
      - `Demand captured: 58% _(was 42% — Example City budget increased)_`
      - `CPA: $18.40 _(was $19.88 — stable)_`
      
      Three numbers, three deltas, zero artificial ratings. If a number didn't move, say "unchanged." If it moved the wrong way, show the delta without sugar-coating.
      
      ---
      
      ## Industry CPA Benchmarks — calibration anchors
      
      Directional benchmarks for evaluating spend efficiency. Actual CPA varies by market, geography, competition, and offer — use as a reference point, not an absolute target. Always prefer `unit_economics.break_even_cpa` when available.
      
      | Industry | Avg CPA (Search) | Good | Excellent |
      |----------|------------------|------|-----------|
      | Legal | $85–$120 | <$70 | <$50 |
      | Home Services | $40–$65 | <$35 | <$25 |
      | Healthcare | $55–$85 | <$50 | <$35 |
      | B2B / SaaS | $75–$120 | <$65 | <$45 |
      | E-commerce | $30–$50 | <$25 | <$15 |
      | Finance / Insurance | $70–$110 | <$60 | <$40 |
      | Real Estate | $50–$80 | <$45 | <$30 |
      | Education | $45–$75 | <$40 | <$25 |
      | Travel | $35–$60 | <$30 | <$20 |
      | Automotive | $40–$65 | <$35 | <$25 |
      
    • business-context.md 6.4 KB
      # Business Context — Website Crawl & Data Collection
      
      This reference covers the website crawl procedure and business context JSON schema used by the `/google-ads-audit` skill.
      
      ## Website Crawl
      
      ### Step 1: Resolve the website URL
      
      Find the website URL from Phase 1 data, in priority order:
      1. Ad final URLs already pulled in Phase 1 (the `ad_group_ad` rows from the audit dataset) — extract the root domain (e.g., `https://example.com`). Normalize to the apex domain (strip `www.` and subdomain prefixes) before frequency-counting across all ads. Use the most common domain.
      2. If no URL found in ad data, ask the user: "What's your website URL?"
      
      ### Step 2: Crawl the website
      
      Issue all `WebFetch` calls in a single tool-use turn so they run in parallel. If any individual fetch fails (404, timeout, blocked), skip that page and continue.
      
      | Page | URL pattern | Why |
      |------|-------------|-----|
      | Homepage | `{root_url}` | Services overview, hero messaging, trust signals, brand voice |
      | About page | `{root_url}/about` | Differentiators, history, team, social proof |
      | Services page | `{root_url}/services` | Full service list, service descriptions |
      | Top ad landing pages | Up to 3 unique final URLs from ads, **excluding any URL that matches the homepage, about, or services pages already being fetched** | What the ads actually link to — offers, CTAs, messaging |
      
      **Fallback if `/about` or `/services` return 404:** Try one fallback each:
      - About: try `/about-us` (most common variant)
      - Services: try `/our-services` (most common variant)
      
      If the fallback also 404s, move on — don't spider the site.
      
      **Detecting unusable pages:** If a fetched page has fewer than 50 words of visible text (excluding HTML tags, scripts, and navigation), or if the primary content is a login/auth form (email/password fields, "Sign In" as the main heading), treat it as a failed fetch and skip it for extraction.
      
      ### Step 3: Extract business context from crawled pages
      
      Scan the fetched page content for these signals. Merge with what you already inferred from account data — website data fills gaps, account data confirms what's active.
      
      | Field | What to look for on the website |
      |-------|-------------------------------|
      | `services` | Service names from navigation, headings, service cards. **Merge** with services inferred from campaigns — the website may list services not yet advertised |
      | `differentiators` | "Why choose us" sections, hero subheadings, unique value claims (e.g., "Family-owned since 1998", "Same-day service guaranteed") |
      | `social_proof` | Review counts, star ratings, award badges, "As seen in" logos, certifications, years in business, number of customers served |
      | `offers_or_promotions` | Banner offers, hero CTAs with discounts, seasonal promotions, "Free estimate" or "X% off" |
      | `brand_voice` | Tone of headlines and body copy — professional vs casual, technical vs approachable. Capture 3-5 literal phrases from the site that exemplify the tone |
      | `target_audience` | Who the site speaks to — homeowners vs businesses, specific industries, demographic cues |
      | `locations` | Footer addresses, "Areas we serve" pages, location-specific content |
      | `landing_pages` | Map each ad final URL to a summary of what's on that page (headline, primary CTA, offer if any) |
      | `industry` | What the business clearly does — confirm or refine what campaign names suggest |
      | `competitors` | Look for comparison tables or "vs" pages linked from the nav |
      
      **Important:** Only extract from pages you actually retrieved with usable content. If the homepage is all you got, that's fine — it usually has the most signal. Extract in the site's original language — downstream skills handle translation when generating English ad copy.
      
      **If all pages failed or returned no usable content**, skip website extraction entirely and proceed to the full question set (do not skip any questions).
      
      ## Business Context JSON Schema
      
      Write the complete business context to `{data_dir}/business-context.json`:
      
      ```json
      {
        "business_name": "",
        "industry": "",
        "industry_template_key": "",
        "website": "",
        "services": [],
        "locations": [],
        "target_audience": "",
        "brand_voice": {
          "tone": "",
          "words_to_avoid": [],
          "words_to_use": []
        },
        "differentiators": [],
        "competitors": [],
        "seasonality": {
          "peak_months": [],
          "slow_months": [],
          "seasonal_hooks": []
        },
        "keyword_landscape": {
          "high_intent_terms": [],
          "competitive_terms": [],
          "long_tail_opportunities": []
        },
        "unit_economics": {
          "aov_usd": null,
          "profit_margin": null,
          "ltv_usd": null,
          "avg_customer_lifespan_months": null,
          "source": "user_provided | inferred_from_template | unknown",
          "last_confirmed": ""
        },
        "social_proof": [],
        "offers_or_promotions": [],
        "landing_pages": {},
        "notes": "",
        "audit_date": "",
        "account_id": ""
      }
      ```
      
      Include `audit_date` (today's date) and `account_id` so future skills know when this was last refreshed.
      
      ## Unit Economics — How to Populate
      
      Unit economics drive margin-aware profitability framing (see `../../shared/ppc-math.md`). Three ways to populate, in priority order:
      
      1. **User-provided (strongest):** During Phase 3 intake, ask: "What's your average order value and rough profit margin?" Set `source: "user_provided"` and stamp `last_confirmed` with today's date.
      
      2. **Inferred from industry template (fallback):** If the user doesn't know, read `../../shared/industry-templates.json` and copy `typical_margin` + the midpoint of `aov_range_usd`. Set `source: "inferred_from_template"`. Flag prominently in the audit: "_Profitability estimates use industry defaults — confirm your actual AOV and margin for sharper recommendations._"
      
      3. **Leave null (last resort):** If no industry template match and user declines to provide, leave all fields `null`. The audit falls back to account-average heuristics and skips break-even / headroom framing.
      
      **Never compute break-even CPA when `source == "inferred_from_template"` without surfacing the assumption.** A template-inferred margin that's off by 15% changes every dollar-impact figure in the audit. Transparency is non-negotiable.
      
      ### Industry template matching
      
      During Phase 3, after resolving `industry`, also set `industry_template_key` by matching against `templates.*.aliases` in `industry-templates.json` (case-insensitive substring). If no match, use `"generic"`. This key is the stable handle downstream skills use — industry names drift across audits, the key doesn't.
      
    • conversion-network-audit.md 3 KB
      # Conversion + Network Integrity Audit
      
      Use this during first-time audits, regression explanations, Smart Bidding/budget recommendations, Search Partners questions, or any account where CPA/ROAS looks suspicious.
      
      Bid, budget, negative, and ad-test decisions are only as good as the conversion and traffic segment data behind them. Measurement and network mix are prerequisite checks, not afterthoughts.
      
      ---
      
      ## 1. Conversion tracking integrity
      
      Pull conversion-action inventory and campaign conversion settings before recommending aggressive optimization.
      
      Check:
      
      - Enabled primary conversion actions exist and match the business goal.
      - Lead-gen counting is normally `ONE_PER_CLICK`; purchases/subscriptions may be `MANY_PER_CLICK`.
      - Conversion values/currencies are present and sane when using value-based bidding.
      - Imported/read-only actions are not treated as mutable API targets.
      - Campaign-specific goals do not optimize one campaign for irrelevant actions.
      - Duplicate actions/tags are not double-counting the same event.
      - Offline/call/form coverage matches how revenue actually happens.
      - Recent zero-conversion cliffs are not caused by tag/site/form changes.
      
      Stop condition:
      
      - If tracking is materially broken, say so and recommend fixing measurement before bid/budget changes. Do not optimize on bad signal.
      
      ---
      
      ## 2. Network segmentation
      
      Use for Search Partners, Display-in-Search leakage, unexplained CPA/CVR shifts.
      
      Pull by `segments.ad_network_type` when compatible:
      
      - cost, clicks, impressions, conversions, conversion value
      - CTR, CVR, CPA/ROAS
      - campaign/ad group
      - search terms where available
      
      Interpretation:
      
      - **Google Search better, Partners material and worse:** recommend disabling Search Partners or testing without them.
      - **Partners better or similar:** keep, but monitor because partner mix is opaque and can shift.
      - **Tiny partner volume:** do not overreact.
      - **Display Network in Search campaign:** flag as structural leakage unless intentionally enabled.
      
      Search Partners decision must be based on the account's goal metric, not CTR alone. A lower CTR partner segment can still be profitable; a high CTR segment can still produce junk leads.
      
      ---
      
      ## 3. Regression lens
      
      When conversions or CVR change, split the diagnosis:
      
      - Traffic volume changed: impressions/clicks/search demand/impression share.
      - Traffic quality changed: search terms, network, match type, geography/device.
      - Conversion rate changed: landing page, tracking, form, offer, site speed.
      - Bidding changed: strategy, targets, learning, budget caps.
      - Measurement changed: conversion actions, primary flags, tag health, offline import.
      
      Always overlay recent changes from NotFair changes and Google `change_event` before attributing causality.
      
      ---
      
      ## Sources
      
      - `../../shared/ppc-optimization-pattern-playbook.md` — conversion tracking, Search Partners, and conversion-change diagnosis modules.
      - General conversion tracking, Search Partners evaluation, assisted conversions, and KPI monitoring patterns.
      
    • persona-discovery.md 3.7 KB
      # Persona Discovery
      
      Discover 2-3 customer personas from the ad data. This runs in parallel with business context questions — it uses only the data already pulled in Phase 1.
      
      ## Data Sources for Persona Construction
      
      | Source | What it reveals | Where it lives in the audit dataset |
      |--------|----------------|---------------|
      | Search terms | What customers actually search for — their language, pain points, urgency | `search_term_view` rows |
      | Converting keywords | What they buy — terms that lead to conversions reveal purchase intent | `keyword_view` rows filtered to `metrics.conversions > 0` |
      | Ad-group themes | How the business segments services — each theme may serve a different persona | `ad_group.name` distribution |
      | Landing page URLs | Where they land — different pages suggest different customer journeys | `ad_group_ad.ad.final_urls` |
      | Geographic data | Where they are — metro vs rural, specific cities | `campaign_criterion` LOCATION + PROXIMITY rows |
      | Device split | How they search — mobile-heavy suggests on-the-go / urgent need | Segment any `metrics.*` query by `segments.device` |
      | Time-of-day patterns | When they search — business hours vs. evenings vs. weekends | Segment by `segments.day_of_week` and `segments.hour` |
      
      ## Persona Template
      
      Use this full template for the persisted JSON file. In the **report output**, personas appear as a compact 3-column table (name, example searches, value). The JSON file has the full detail for downstream skills like `/google-ads-copy`:
      
      | Field | Description | Example |
      |-------|-------------|---------|
      | **Name** | Descriptive label capturing their defining trait | "The Emergency Caller" |
      | **Demographics** | Role, context, location type | Homeowner, suburban, dual-income household |
      | **Primary goal** | What they're trying to accomplish RIGHT NOW | Fix a burst pipe before it damages the floor |
      | **Pain points** | What's driving them to search | Can't wait for regular business hours. Worried about cost. Doesn't know who to trust |
      | **Search language** | Actual search terms from the data that this persona uses | "emergency plumber near me", "plumber open now", "burst pipe repair cost" |
      | **Decision trigger** | What makes them click the ad and convert | Seeing "24/7" and "Same Day" in the headline. Phone number in the ad. Reviews mentioned |
      | **Value to business** | Estimated revenue or conversion value | High urgency = willing to pay premium. Avg ticket $350-800 |
      
      ## Derivation Rules
      
      - Each persona MUST be grounded in actual search term clusters from the data. If you can't point to 5+ search terms that this persona would use, the persona is speculative — drop it
      - If all search terms look the same (single-intent account), identify 1-2 personas max. Don't force 3
      - Name personas by their dominant behavior, not demographics: "The Comparison Shopper" is more useful than "Female 35-44"
      - Include the actual search terms from the data that map to each persona — this directly informs ad copy decisions
      
      ## Persist Personas
      
      Save to `{data_dir}/personas/{accountId}.json`:
      
      ```json
      {
        "account_id": "1234567890",
        "saved_at": "2024-01-15T10:30:00Z",
        "personas": [
          {
            "name": "The Emergency Caller",
            "demographics": "Homeowner, suburban, any age",
            "primary_goal": "Fix an urgent problem right now",
            "pain_points": ["Can't wait", "Worried about cost", "Doesn't know who's reliable"],
            "search_terms": ["emergency plumber near me", "plumber open now", "burst pipe repair"],
            "decision_trigger": "24/7 availability, phone number visible, reviews",
            "value": "High — willing to pay premium for urgency"
          }
        ]
      }
      ```
      
      These personas feed directly into `/google-ads-copy` for headline generation and `/google-ads` for keyword strategy.
      
  • SKILL.md 9.7 KB
    ---
    name: google-ads-audit
    description: Google Ads account audit and business context setup. Use for account-health audits and business-context setup. Trigger on "audit my ads", "ads audit", "set up my ads", "onboard", "account overview", "how's my account", "ads health check", "what should I fix in my ads", or when the user is new to NotFair and hasn't run an audit before.
    argument-hint: "<account name or 'audit my ads'>"
    ---
    
    # Google Ads Audit
    
    Diagnose account health and persist business context for downstream skills (`/google-ads`, `/google-ads-copy`, `/google-ads-landing`). **Read-only** — never mutates the account. The user runs `/google-ads` to execute fixes you recommend.
    
    ## Setup
    
    Follow `../shared/preamble.md` (MCP detection, account selection) and `../shared/analysis-principles.md` (evidence requirement, guardrails). Both apply throughout this skill.
    
    ## Filesystem contract (must persist)
    
    | Artifact | Path | When |
    |---|---|---|
    | Business context | `{data_dir}/business-context.json` | First full audit, or refresh when `audit_date` is >90 days old. Skip on scoped audits if file is fresh. |
    | Personas | `{data_dir}/personas/{accountId}.json` | Every full audit. |
    
    These are the handoff to every other ads skill — write them even if the report is short. Otherwise `/google-ads-copy` and `/google-ads-landing` operate without business context and produce generic output.
    
    **business-context.json schema:** `business_name, industry, website, services[], locations[], target_audience, brand_voice{tone, words_to_use[], words_to_avoid[]}, differentiators[], competitors[], seasonality{peak_months[], slow_months[], seasonal_hooks[]}, keyword_landscape{high_intent_terms[], competitive_terms[], long_tail_opportunities[]}, social_proof[], offers_or_promotions[], landing_pages{}, unit_economics{aov_usd, profit_margin, source}, notes, audit_date, account_id`.
    
    **personas JSON schema:** `{account_id, saved_at, personas: [{name, demographics, primary_goal, pain_points[], search_terms[], decision_trigger, value}]}`. See `references/persona-discovery.md`.
    
    ## Policy freshness check (run first)
    
    Read `../shared/policy-registry.json`. For each entry where `last_verified + stale_after_days < today`:
    - Any entry without a direct current first-party Google source is a hypothesis, not an audit rule or benchmark. Do not use it for a finding or recommendation without verification.
    - **High-volatility** → search the official Google Ads Help, Ads & Commerce blog, or Google Ads developer documentation for the `category`; compare the source with the recorded `rule`. If it drifted, omit the stale rule and banner the limitation.
    - **Moderate-volatility** → verify it when it could affect a material finding; otherwise omit it rather than repeating a stale caveat.
    - **Stable** → skip silently.
    
    ## Phase 1 — Pull the audit dataset
    
    Choose available read capabilities for the requested audit scope. Batch related reads where useful and supported; consult current server guidance for schemas and limits.
    
    You decide the exact GAQL shape, but a defensible audit needs to see, at minimum:
    
    - Account-level rollups (`customer`)
    - Campaign performance with bidding strategy, network, and impression-share metrics (`campaign`, 90-day cap for impression-share data)
    - Ad-group performance (`ad_group`)
    - Keyword performance with Quality Score and components (`keyword_view`)
    - Search terms (`search_term_view`)
    - Negative keywords and shared lists (`campaign_criterion` + shared sets)
    - Conversion actions (`conversion_action`) — including counting type, attribution model, primary/secondary
    - Network segmentation (`segments.ad_network_type`) when diagnosing CPA/CVR shifts or Search Partners
    - RSA assets (`ad_group_ad`)
    - Geo targeting (`campaign_criterion` LOCATION + PROXIMITY)
    - Recent change events (`change_event`, last 30 days) — for explaining regressions
    
    Aggregate inside the script. Return summarized JSON, not raw rows. The agent narrates; the script does the math.
    
    Use platform recommendations or account-setup diagnostics as optional cross-checks when available and relevant to the question.
    
    If a read fails, follow actionable recovery guidance. Clearly report missing evidence; continue independent findings only when the available data supports them.
    
    **Skip scoring entirely if** `totalSpend == 0` or `activeCampaigns == 0`. Go straight to business context.
    
    ## Phase 2 — Scope handling
    
    If the user narrows the audit ("focus on one campaign", "campaign X", "just check waste"):
    
    - Match campaign names by case-insensitive substring. If no match, list available campaigns and ask.
    - Filter the in-memory dataset before analysis — no extra API calls.
    - Account-level dimensions (conversion tracking, account guardrails) stay account-wide. Note "Scoped to: X" in the report.
    - Skip Phase 4 (business context refresh) on scoped audits if `business-context.json` is fresh.
    
    ## Phase 3 — Diagnose
    
    The audit's headline output is **three pulse metrics** — Waste ($/mo), Demand captured (%), CPA ($) — each annotated with its top contributor and a pointer to the fix. Read `references/account-health-scoring.md` for the formula, annotation rules, signal-failure overrides, and `audit-history.json` schema. The pulse metric IS the verdict; you don't add a letter grade or 0–5 score on top.
    
    To compute and back the pulse metrics, you'll need to look across these seven areas. They are diagnostic surface area, not graded dimensions:
    
    1. **Signal Quality** *(account-level)* — measurement integrity. If broken, **STOP** here and recommend pausing spend until it's fixed. Pulse metrics are meaningless without measurement (apply the signal-failure override on the Waste line per the reference).
    2. **Campaign Structure** — keywords per ad group, brand vs. non-brand separation, channel mixing, naming, budget logic.
    3. **Keyword Health** — Quality Score weighted by spend, zombie keywords, match-type discipline.
    4. **Search-Term Quality** — wasted spend, brand-leakage, negative coverage, conversion-worthy terms not yet keywords.
    5. **Ad Copy & Creative** — RSA coverage, asset variety, sitelink/callout/structured-snippet completeness, PMax asset-group health.
    6. **Impression Share** — read rank-lost vs budget-lost together (see the 2×2 matrix in `account-health-scoring.md`); they're different problems with different fixes.
    7. **Spend Efficiency** — waste vs. headroom, brand vs. non-brand split, concentration risk.
    
    For Signal Quality and network-mix questions, read `references/conversion-network-audit.md`. It adds the prerequisite checks for conversion-action integrity, Search Partners, Display leakage in Search campaigns, and regression decomposition.
    
    Per-area findings only show up in the report when the area surfaced something material. Cite specific entities, dollars, and time windows. "Some keywords are underperforming" is not a finding; "Campaign X has $1,840 in last-30-day spend on 12 keywords with 0 conversions and QS ≤ 4" is.
    
    For unit-economics-aware framing: if `business-context.json.unit_economics.aov_usd` and `profit_margin` exist, frame waste and headroom in dollars saved / captured per month, not "above account average". See `../shared/ppc-math.md`.
    
    ## Phase 4 — Business context
    
    Derive what you can from data already pulled:
    
    | Field | Source |
    |---|---|
    | `business_name` | `customer.descriptive_name` |
    | `services` | Campaign + ad-group names, top converting keywords |
    | `locations` | `campaign_criterion` LOCATION + PROXIMITY |
    | `brand_voice` | Top-performing RSA headlines / descriptions |
    | `keyword_landscape.high_intent_terms` | Converting keywords with strong CVR |
    | `keyword_landscape.competitive_terms` | Keywords in campaigns with high rank-lost-IS |
    | `keyword_landscape.long_tail_opportunities` | Converting search terms not yet promoted to keywords |
    | `website` | Apex domain from ad final URLs |
    
    Then crawl the website (homepage + about + services + top 3 ad landing pages, parallel `WebFetch`) and merge into the schema. See `references/business-context.md`.
    
    Ask the user — it's faster than guessing — for: differentiators, competitors, seasonality, unit economics (AOV, margin). Ask for everything else only if the data + crawl can't answer it.
    
    ## Phase 5 — Personas
    
    Discover 2–3 personas from search terms, top keywords, ad-group themes, landing pages, geo, and device split — all from the dataset already in memory. Persist to `{data_dir}/personas/{accountId}.json`. Each persona must be grounded in **5+ actual search terms**; if not, drop it. See `references/persona-discovery.md`.
    
    ## Phase 6 — Report
    
    Structure: pulse metrics (3 lines, each with number + top contributor + fix pointer) → per-area findings (only those that surfaced something material) → Quick Wins section (per the rules in `references/account-health-scoring.md`). Cap at ~80 lines. Every claim cites a specific entity, number, and window.
    
    End with a single closing line after the handoff to `/google-ads`:
    
    State where any audit artifacts were actually saved. Do not claim hosted audit history unless a live result confirms it.
    
    ## Guardrails
    
    1. **Read-only skill.** Diagnose; don't mutate. Every fix routes through `/google-ads` (or `/google-ads-copy`, `/google-ads-landing`). End the report with one handoff tied to the #1 action.
    2. **STOP condition.** If conversion tracking is broken, recommend pausing spend until it's fixed before recommending anything else.
    3. **Always persist** `business-context.json` and `personas/{accountId}.json` even if the report is short — downstream skills depend on them.
    4. **Name names.** Every finding cites specific campaigns, keywords, search terms, and dollar amounts. No generic verdicts.
    5. **Show the data, not the score.** The pulse metrics are the verdict — three numbers with named contributors and pointers to the fix. No letter grades, no 0–5 ratings hiding the reasoning behind a label.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related