agentsclimarketplace

Optimizer

Skill chanktb/claude-google-ads/skills/optimizer

Find where your Google Ads budget leaks, cut waste, and scale your ecommerce store safely — 15 skills: audit, money-leak diagnostics, plan, campaign builders, safe push, monitor, optimize.

Install
npx -y skills add chanktb/claude-google-ads --skill optimizer

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 9 stars9 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Acts on a running account to improve performance: campaign tiering, search-term mining into negatives, budget reallocation, target ROAS/CPA adjustments, bid-strategy changes, asset refresh, and consolidation. Diagnoses root cause (not just symptoms) and produces a dated action plan. Proposes changes — applies via pusher with approval. Generalized fork of a production optimizer; reads account-context.yaml, respects guardrails and margin tiers. Use when the user says "optimize", "improve ROAS", "lower CPA", "add negatives", "raise tROAS", "reallocate budget", "fix underperformers", "weekly ads review".

SKILL.md

12.5 KB, as published. Nobody here has run it

Google Ads — Optimizer (act)

Find what's dragging the account and fix it — with data backing and root-cause reasoning, not symptom swatting. The optimizer proposes changes; it applies them only through pusher (approval gate). For a scored health check use audit; this skill is about performance and money.

Operating rules

  • Every recommendation has data backing (specific numbers, not vague advice).
  • Read everything from account-context.yaml (margin_tiers, brand_terms, guardrails, AOV). If the context is missing, run setup first — never optimize a live account without it.
  • Read the context connections block first. If store/GA4 is missing, do the in-platform analysis and label every true-ROAS / store-revenue conclusion UNVERIFIED — connect store/GA4; never fabricate a store-revenue figure to compute "true ROAS". Prefer guiding the user to connect over guessing.
  • 3-source attribution (see ${CLAUDE_PLUGIN_ROOT}/references/optimization-playbook.md): store revenue = ground truth; Google Ads in-platform = for Smart Bidding; GA4 = channel mix. A 20-35% Ads-vs-GA4 gap is normal.
  • Honor guardrails: change-event cooldown, ignore paused, margin-tier ROAS. Don't flag a house-brand line for low ROAS above its tier min_roas.

Model dispatch (run cheap, decide expensive) — see ${CLAUDE_PLUGIN_ROOT}/references/model-tier-dispatch.md

  • Scout (haiku) — running tiering.py and search_term_miner.py (scripts return their own output).
  • Routine (sonnet) — STEP 1 performance pull + store/GA4 fetch; STEP 3 dual-source search-term pull (per-PMax campaign_search_term_insight loop). Dispatch as general-purpose sub-agents; return raw, don't conclude.
  • Judge (main session) — tier verdicts, what to block vs keep (esp. never-block-brand), STEP 5 profitability call, STEP 6 root-cause, STEP 7 dated action plan. The numbers come cheap; the decisions stay here.

STEP 1 — Collect performance

Scope to the active set first (ENABLED + impressions in the window) — never tier or "optimize" a campaign that hasn't served in the period; entity status=ENABLED can include long-dead campaigns' assets (see ${CLAUDE_PLUGIN_ROOT}/skills/audit/references/gaql-notes.md). Then pull campaign performance, search terms (≤30d or explicit dates), and asset-group performance via the MCP; pull store revenue + GA4 channel mix via the data-source/GA4 fallback chain. Note what's unavailable.

STEP 2 — Tier campaigns

Classify Gold / Silver / Bronze / Dead by ROAS relative to the margin-tier target, plus conversion volume per period. Output a ranked verdict table (Scale / Keep / Reduce / Pause / Kill) with the numbers. Runnable: python ${CLAUDE_PLUGIN_ROOT}/skills/optimizer/scripts/tiering.py campaigns.json [--target-roas T] (margin-tier aware; flags learning risk + pause-candidate savings).

STEP 3 — Mine search terms → negatives (BOTH sources)

Pull from both: search_term_view (Search/Branded) AND campaign_search_term_insight looped per active PMax/Demand-Gen campaign (requires a single campaign_id filter — see gaql-notes.md; it returns categories + conv/value, not per-term cost). search_term_view does NOT contain PMax terms, so PMax-heavy accounts need the insight or you'll miss most of the spend. Find top spend, top revenue, wasted (Search:

$10 spend & 0 conv; PMax: irrelevant categories with clicks but no conversions). Propose Exact/Phrase negatives in themed lists; respect the "what NOT to block" rules (store / coupon / cheap-brand) in the playbook; never block brand terms (brand intent, even at 0 conv). Catch cross-brand leakage via brand_terms.

🛑 RESELLER BRAND RULE — never negate a brand the store SELLS (the #1 false-positive)

For a distributor/reseller, a search term containing a brand you carry is buying intent, not a competitor — auto-negating it kills your own sales. Classify every brand-bearing term into THREE buckets, not two:

  1. OWN house brand → never block (brand defense). [brand_terms]
  2. CARRIED brand (a brand in your catalog/feed) — split by whether it already has its own campaign:
    • has a dedicated campaign → blocking it in a catch-all (e.g. "Lite") is OK only to ROUTE traffic to the specialist campaign (anti-cannibalization). Label it "route to <brand> campaign", NOT "competitor".
    • no dedicated campaignNEVER block. The catch-all is its ONLY home — it lives there because it hasn't been split out yet, not because it's a competitor. A 0-conv carried-brand term is a stock/PDP/feed problem OR a split-into-its-own-campaign candidate once volume justifies it. Recommend the split, never a negative.
  3. NOT carried (store genuinely doesn't sell it) → a candidate competitor negative — flag for the user to CONFIRM it isn't carried before blocking. Never assert "competitor" on your own. Populate the miner's brand inputs every run (don't run it blind): carried_brands = the distinct brand values from the store catalog / the Shopping feed (shopping_performance_view product brand, or the D14 inventory join) ∪ any brands_carried in account-context.yaml; brands_with_own_campaign = brands that map to an existing dedicated campaign (derive from the active-campaign names, e.g. "ND pMax OPI" → opi). Without these, the miner will mislabel carried brands (Kupa/Chaun Legend/Kiara Sky on ND) as competitors — exactly the bug to avoid. Runnable: python ${CLAUDE_PLUGIN_ROOT}/skills/optimizer/scripts/search_term_miner.py terms.json (input keys: brand_terms, carried_brands, brands_with_own_campaign, min_spend, terms — each term may carry conversions/conv_value) → wasted spend + categorized, ready-to-copy wrapped negatives, with carried-brand / routing / competitor-candidate buckets kept separate and carried-brand-without-a-campaign held back from blocking.

N-gram modifiers + conflict detection (built in to the miner)

  • N-gram surfacing: the miner aggregates 1- and 2-word tokens across the whole waste pool, ranked by $ and by how many terms each appears in (≥2 = recurring). One phrase negative on a recurring junk modifier (e.g. "how to", "jobs", "tutorial") kills many wasted queries at once — far more efficient than per-term negatives. Own-brand and carried-brand tokens are excluded from the surfacing (never propose cutting a brand you sell). Treat as candidates — a broad modifier can over-reach; eyeball before pushing.
  • Conflict detection — NEVER negate a converter: the miner cross-checks every proposed negative against the CONVERTING queries (conversions/conv_value > 0) and drops any that would block one (exact = same query, phrase = substring), listing them under "Conflict-blocked". Always pass terms WITH their conversion fields so this fires. At push time, also run validate_changeset.py --converting-terms <converters.json> (a JSON list of converting queries) so the pusher gate re-blocks any negative that would cut proven revenue — defense-in-depth alongside the never-block-brand and carried-brand gates.

STEP 4 — Asset groups / creative

Flag asset groups with ROAS below the tier target and meaningful spend, POOR ad strength (ENABLED + has impressions — GUARD-4), and URL overlap. Recommend creative refresh via assets.

STEP 5 — Cross-reference store + GA4 (true profitability)

True ROAS = store revenue (by product/vendor) ÷ Google Ads spend. If a line's TOTAL store revenue (all channels) is below its Ads spend, it's losing money regardless of in-platform ROAS. Surface channel mix (how much is organic/direct/email) so pausing decisions account for non-paid revenue.

STEP 5.5 — Run the seven money-leak diagnostics (the core engine)

Work through D1-D14 in ${CLAUDE_PLUGIN_ROOT}/references/diagnostic-playbook.md against the active set: D1 bid/ target health (decode bidding_strategy_system_status first — budget-capped vs tROAS-too-high vs starved vs learning; breakeven ROAS = 1/margin), D2 budget pacing & allocation (misallocation: shift $ low-ROAS→high-ROAS), D3 geo waste, D4 dayparting, D5 search-term/spam/wrong-brand, D6 structure, D7 Quality Score, D8 PMax channel/placement distribution (don't assume "Display burns it" — pull segments.ad_network_type, name the real surface; most levers DON'T work on PMax), D9 ad copy/assets/extensions (missing sitelinks/callouts, duplicate headlines, LOW assets). Each becomes a prescription with a real $ impact, a step size, and a cooldown gate. Caveat baked in: budget-lost-IS is blind on Smart Bidding/PMax — diagnose budget constraint via spend-vs-budget + system_status, not budget-lost-IS. For any tROAS/budget scale move, follow the Scaling Ladder (budget first then target, never both same week, ≤+20% budget / 10–20% target steps, 14-day cooldowns, never reverse inside the window).

STEP 6 — Diagnose root cause (System Thinking)

Don't stop at symptoms. For each problem ask "why" until you reach the cause (e.g. low ROAS → irrelevant search terms → missing negatives → no weekly review process). Check interactions: campaigns competing for the same queries, cross-brand budget leakage, over-reliance on paid vs organic. Project the cost of inaction vs the savings from fixing.

STEP 7 — Action plan

Produce a dated plan in three buckets, each item with: what to do · exact UI/Editor steps · why (data) · status checkbox.

  • Today: pause Dead campaigns, cut budget on Bronze, add negative lists, fund under-invested branded.
  • Tomorrow: review yesterday; raise tROAS ≤0.2-0.3x (only if ≥15 conv/week, else consolidate first); scale Gold (+budget gradually); plan consolidations.
  • 2-week roadmap: dated timeline, each action typed (urgent/optimize/plan). Respect the tROAS step-up discipline and change-event cooldown throughout.

STEP 8 — Emit the change-set (the pusher hand-off)

Serialize the action plan into a change-set.json (contract: ${CLAUDE_PLUGIN_ROOT}/references/change-set.md; template: ${CLAUDE_PLUGIN_ROOT}/templates/change-set.json) — a typed list of edits to the LIVE account (add_negatives, exclude_geo, exclude_products, adjust_budget, adjust_target_roas, pause, add_extensions, add_audience_signal, exclude_placements). This is distinct from a builder's campaign-spec.json (which creates a NEW campaign).

  • Every action carries a reason (the $ leak / numbers), its diagnostic D-code, est_impact_per_mo, and a push_path. Express a budget reallocation as a pair (a down/pause on the source + an adjust_budget up on the receiver) so each leg is gated on its own.
  • Encode the discipline in the data, don't rely on prose: mark target.within_cooldown: true for any campaign with a recent change_event (D13) or under the change-event-cooldown guardrail, set target.last_change_direction, and pass conv_per_week on a tROAS raise. The pusher's validator BLOCKS a scale move on a within-cooldown target — so a campaign that "shouldn't scale yet" must NOT carry a do-now scale action (defer it to the prose roadmap, or omit it). PMax scales DOWN by pausing a low-ROAS asset group, never a budget cut.
  • The optimizer proposes the change-set; it never writes. Hand it to pusher (or /google-ads-push), which validates (validate_changeset.py) and renders operator actions (changeset_to_actions.py) behind the approval gate.

Applying changes

Account mutations go through pusher (approval gate + spend cap). The optimizer prepares the change-set (STEP 8); it does not write to the account directly.

To build / refine later

  • Runnable analyzers: scripts/tiering.py + scripts/search_term_miner.py. Done.
  • Emit changes as a structured change-set the pusher can consume (STEP 8 → change-set.json; validated/rendered by pusher's validate_changeset.py + changeset_to_actions.py). Done.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.