AdCopilot for Claude Code
The AI agent that runs your Google Ads, with Google Analytics, Google Search Console and Google Tag Manager in the same chat. Audit wasted spend, mine search terms, build campaigns and fix conversion tracking from Claude Code. AdCopilot proposes each change first. For a hard stop, set your AI client to ask before AdCopilot's write tools: Needs approval in Claude; in Cursor, use the Allowlist run mode and keep them off the allowlist (Cursor's cloud agents never ask). AdCopilot never deletes anything but a negative keyword. This plugin bundles the hosted AdCopilot connector and carries the steps the connector cannot take for you: what to click in Google's own screens, and the traps that are easy to fall into.
AdCopilot is made by Atromx Intelligence (https://adcopilot.cloud) and is not related to other products named AdCopilot.
The plugin is free (MIT). The connector is a hosted service — plans at https://adcopilot.cloud/pricing.
Install
Two steps on every surface: add the plugin, then sign in to the connector it brings with it. The second step is the one people miss, and nothing works until it is done.
AdCopilot connects to Google Ads through its own Google Cloud project, with your Google sign-in. If you want programmatic access to Google Ads for your own software, get your own Google Cloud account and apply for Google Ads API access there.
claude.ai, Cowork and the desktop app
- Add the plugin: open AdCopilot in the plugin directory, or go to Customize, then Plugins, and search for AdCopilot. Click Add.
- In the plugin, open its Connectors tab, click Connect beside
adcopilot, and sign in.
The connector is already listed — there is nothing to add, and no need for "Add custom connector" or the connector URL: the plugin registers it for you. If you added AdCopilot by hand before installing the plugin, nothing breaks: Claude links a connector by its URL, so you still have one AdCopilot connector, the same one.
If you clicked Try in Cowork straight from the listing, you landed in a chat before step 2 — the connector will report as missing. Do step 2 and say "get me started" again.
Claude Code
/plugin marketplace add AtromxIntelligence/adcopilot-claude-plugin
/plugin install adcopilot@adcopilot-claude-plugin
/mcp
The last step opens the connector list. Choose adcopilot and sign in with your own Google account. There is no claude mcp add step: the plugin registers the connector at https://mcp.adcopilot.cloud/mcp for you. Signing in links your AdCopilot workspace, so you need an AdCopilot account; your Google products are connected once, in AdCopilot at https://app.adcopilot.cloud, not in Claude Code — /adcopilot:setup tells you which are missing and sends you to the right page.
Cursor
Cursor takes AdCopilot's connector on its own; this plugin does not install
there as a whole. Its skills are written for Claude, and Cursor reads them too
when they are copied into .cursor/skills or .claude/skills. Wherever they
work out which client they are in (/adcopilot:setup, adcopilot-connect, the
daily-habit offer), they check for Cursor first: there they never run
claude mcp, look for the connector in Cursor's MCP settings and make no
schedule offer of their own; a check-in offer AdCopilot's answer asks for is
made as it asks.
Add the connector: Add AdCopilot to Cursor opens Cursor with the connector named
adcopilot. From inside Cursor the same link iscursor://anysphere.cursor-deeplink/mcp/install?name=adcopilot&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vbWNwLmFkY29waWxvdC5jbG91ZC9tY3AifQ%3D%3D. Or put it in~/.cursor/mcp.json(every project) or.cursor/mcp.json(one project):{ "mcpServers": { "adcopilot": { "url": "https://mcp.adcopilot.cloud/mcp" } } }Sign in: Cursor opens AdCopilot's page, Let Cursor use your AdCopilot?, in your browser. Press Approve; there is no API key to paste. If Approve did nothing on an earlier try, remove AdCopilot in Cursor's MCP settings, add it again and press Approve once; see Approve does nothing.
Ask: "Get me started with AdCopilot".
Added from cursor.directory? Its Add to Cursor button can name the
connector server instead of adcopilot, so Cursor's MCP list and its
approval questions show server. It is the same connector. The link above
names it adcopilot.
Approvals in Cursor. Cursor has no Needs approval setting; its run modes decide, in Settings, then Agents, then Approvals & Execution (at the time of writing). In Allowlist, a tool on the allowlist runs without asking and any other asks first, so keep AdCopilot's write tools off the allowlist. In Auto-review, Cursor's recommended default, allowlisted calls also run at once, and every other MCP call goes to a classifier that can let a change through without asking. Run Everything asks before nothing, and a Cursor cloud agent never asks you to approve an action. Whatever the mode, AdCopilot proposes each change first, never deletes anything but a negative keyword, and builds every campaign paused.
Then, on every surface
Signing in to the connector links your AdCopilot workspace, so you need an AdCopilot account. Your Google products — Google Ads, Analytics, Search Console, Tag Manager — are connected once, inside AdCopilot at https://app.adcopilot.cloud, not in Claude. So a fresh sign-in can report nothing connected: that is the server answering correctly, not a fault.
Run /adcopilot:setup first. It says what is connected, what is not, and sends
you to the right page for each.
If you had already added the connector by hand before installing (Claude Code only — the web has no such command), Claude Code keeps yours and silently ignores the plugin's: claude mcp list then shows a bare adcopilot: line and no plugin:adcopilot:adcopilot line. Remove yours from every scope it is in — claude mcp remove adcopilot -s user, and claude mcp remove adcopilot -s local run from the directory you added it in, because a local registration belongs to that directory — and the plugin's line appears and asks you to sign in.
What you get
Seven commands, which you type:
/adcopilot:setup— checks the connector is connected and signed in, says what each connected product unlocks, and catches the two ways a fresh install goes wrong: a lapsed sign-in, and, in Claude Code, a copy of the connector you added by hand before installing./adcopilot:launch— a first Search campaign, through theadcopilot-launchskill below./adcopilot:measure— conversion tracking, through theadcopilot-measureskill below./adcopilot:daily— the daily check-in: this month's budget pacing, the last seven days' searches that cost money and brought nothing, any ad Google has disapproved or limited, and what changed in the account since yesterday and who changed it — in that order, as far as the reads AdCopilot sizes for one answer allow./adcopilot:weekly— last Monday to Sunday against the week before: spend, conversions and cost per conversion, the three biggest changes, and three things to do this week, written to be forwarded./adcopilot:negatives— the last seven days' searches that cost money and brought nothing, grouped by theme, with the never-show-for words (negative keywords) that would block them, proposed first (the command asks which themes to block) and read back once added./adcopilot:audit— the connector's full audit and this month's budget pacing, with the findings that are only a symptom of a brand-new account kept separate, and the first fix proposed with what it costs to act and to leave it, read back once made, then the next one proposed.
Nine skills, which Claude draws on when the conversation calls for them:
- adcopilot-connect — sets up AdCopilot, connects your Google products and links them to each other in the order that works, saying what each step bought you as it happens.
- adcopilot-measure — sets up conversion tracking end to end: the Analytics property and data stream, the Tag Manager tags, the key event, the Analytics-to-Ads link and the import, and ends with exactly one Primary conversion, proven by a real click.
- adcopilot-launch — builds a first Search campaign switched off, on the budget and bid you name: locations, never-show-for words, ad groups, keywords with their match types, ads and assets; verifies the settings that leak money by reading the campaign back; hands you the go-live switch, and asks you to say when it is on so it can read the first day's delivery; and runs the first week's checks, reading why a switched-on campaign is not delivering before it sends you to any screen.
- adcopilot-daily — the morning check a Claude scheduled task or a Claude Code routine runs while nobody is there: it is written to read only and never ask a question, opens with "All clear" or "Needs a look" and ends with the one fix worth doing, as a yes/no question for when you open it. When you answer yes, it re-reads what the fix touches and states the change again before making it, because the report is hours old by then. It also holds the steps for setting that schedule up.
- adcopilot-review — the everyday one: reads the account and says what happened, what it cost, what is wasting money and what to change, then proposes the change and applies it when you say yes, reads back what the change's own result does not show, and proposes the next fix the same way. It opens with
full_audit, because one lens is one read and answers what a dozen hand-written queries would, and it treats the number of reads AdCopilot sizes for one answer as something to spend rather than stay under, never as a daily limit. - adcopilot-fix-checks — the fixes for the checks an AdCopilot audit flagged, when you send the question from the audit page's or an email's "Fix all three" or "Fix this" button. It says first what the fixes cost and what today's operations cover, takes the checks foundation first (counting results before anything judged by them), reads only what each fix needs rather than running the audit again, and puts each fix to you as one yes/no, with at most five rows of any list. A search-term check goes to the never-show-for words, zero-conversion keywords to a pause, and a conversion-tracking check to the steps AdCopilot can take or the clicks in Google Ads it cannot. Each change is read back, with how to undo it, before the next check.
- adcopilot-docs — answers questions about AdCopilot itself from your live workspace rather than from memory: which products it reaches, how many look-ups are left today and what happens when they run out, which plan you are on, what it is allowed to change, where data goes, and what to do when a call fails. Plans and prices are linked, never quoted. It answers from that one look at your workspace, without reading your accounts or starting another skill, and says what AdCopilot can do as one list. When the question pointed at something a read of your account would answer, it ends on one question offering that read; after a question about look-ups, billing, privacy or safety it offers nothing.
- adcopilot-seo — answers what Google search makes of your site, through Search Console: which pages earn clicks and which earn nothing, what people typed to find you, whether a sitemap is being read, and when a page was last crawled. It is written around the readings that mislead — the sitemap's dead "indexed" column, an average position that is not a rank, impressions that are not demand — and it says plainly what it cannot see: there is no site-wide coverage list and no penalty read, so it asks you for Search Console's Pages export rather than guessing. Once an edit is live it offers to inspect the page again a few days later, and with Google Ads connected it offers to set the searches you pay for beside the ones that already bring you organic clicks.
- adcopilot-meta — for a question about Meta ads (Facebook ads, Instagram ads) or about Meta and Google together. AdCopilot has no Meta integration (one is on the roadmap, with no date), and Meta publishes a connector of its own for Meta ads, so this skill looks for Meta's connector in the conversation first. When it is not there, it says what it is and how to add it, links Meta's help page and AdCopilot's guide, and states nothing about Meta's terms that Meta has not published. When it is there, Meta's figures come from Meta's connector and Google's from AdCopilot, each labelled with where it came from; nothing read from one is sent to the other; and any change through either connector is proposed first, one platform at a time (for a hard stop, set both connectors' write tools to ask, as at the top of this page). AdCopilot never writes to Meta.
One agent, which Claude hands a job to when it is too big for the conversation:
- adcopilot-auditor — a full account audit end to end, returning findings with the numbers behind them and the changes worth making. It reads and proposes only: it holds no write tool, so it cannot apply anything, and the conversation that asked for it takes your yes.
Together with the connector registration above, the commands, skills and agent above are what the plugin ships at this version.
Every morning, without asking
Every command above, and the connect, launch, measure and fix-checks skills, make one short offer to turn a morning check of the account into a daily habit when AdCopilot's own answer hands them the recipe for your AI client (routine.schedule): an account with a campaign switched on, no schedule reported yet, and a client AdCopilot gives scheduling steps for, which today means Claude. AdCopilot gives none for Cursor, ChatGPT or another client, and there the plugin makes no schedule offer of its own; in Cursor, when AdCopilot's answer asks for an offer of a check-in or a summary, the plugin makes that one as AdCopilot words it. The offer comes, as a paragraph of its own, only in a reply that asks you no yes/no on a fix: the reply that reads back the first fix you approved, where it stands in for the next fix's yes/no (the next fix is named, and proposed in the reply after), or at the close of a reply that proposes none. When that fix was for a finding the morning check also reads (the searches that spent with no sale, a campaign out of budget, a disapproved ad, a drop against the 7-day average, a change nobody asked for, except on Free's 10 a day, where the check leaves what changed for the chat), it is tied to it: a check that runs by itself every weekday at 8:07 would have caught it on its first morning. With any other finding, or none, the offer closes the reply instead, without that claim. In claude.ai, Cowork or the desktop app it is a Claude scheduled task; in Claude Code, a routine made with /schedule, which it offers to create for you. Say "schedule it" and it walks you through the steps; say no and it does not offer again in that conversation, asks once whether your Claude has scheduled tasks, and records the answer, so a no pauses the offer for a week and a Claude with no scheduled tasks ends it. It records that answer only from Claude, because AdCopilot keeps it for the whole workspace. The offer stops for good once AdCopilot reports a schedule — one you recorded with it, or a scheduled run that has checked in on two different days.
The instructions you save are AdCopilot's own morning-check recipe, word for word, filled in with your account. A run reads about six or seven times, and each read counts toward your workspace's daily operations; on a Monday, the daily check and the weekly summary together can use all of a Free workspace's 10. On Free, and on a trial held to Free's 10 a day, an analysis such as budget pacing counts 2 operations and a single read 1, so all five reads would come to about 8 to 10 of Free's 10, some days all of them; there the run makes its first four reads only, about 6, leaving what changed for the chat, so the fix it ends on still fits the same day. On a paid plan or a running trial every read counts 1. If AdCopilot already emails you its daily check-in, that keeps coming as well; the Routine card on AdCopilot's Home page pauses it if you want only one. Scheduled tasks need a Claude plan that has them; on Claude's Free plan, and in Cursor, AdCopilot's emailed daily check-in, when it is on, is the morning check.
A scheduled run is told not to change anything. The instructions adcopilot-daily gives you are read-only and ask no questions; a fix it finds is a question for when you open the report. AdCopilot holds a scheduled run to reads as well: once a run's first call is get_org_context with source: "claude-scheduled", AdCopilot refuses every tool that changes Google Ads, Analytics, Tag Manager or Search Console on that connection for the rest of the run, up to 20 minutes, whatever Claude allows (AdCopilot's own notes aside). It knows a run is scheduled only from that first call, so a run that skips it or sends another source is not held, and a yes typed in the run's own conversation can be refused while the run is on; say it again in a new conversation. Claude Code adds one more hold: AdCopilot marks set_campaign_status, the tool that pauses a campaign or switches one on, as needing a person, so Claude Code (version 2.1.214 or later) will not use it in a run on your computer with nobody there to answer, such as claude -p. The plugin's skills pause or switch on a campaign only with that tool, never with a status in a change to the campaign's settings (update_campaign), and the mark does not hold that change. A Claude Code routine runs its tools without asking, and so does a scheduled task switched to Automatically approve; in those, the first call and the instructions are what keep the run to reads, so keep them as written. In claude.ai, Cowork and the desktop app, setting AdCopilot's other Write/delete tools to Blocked, every one but the two AdCopilot tools below, stops a change outright, in every chat as well as in the run.
The one setting a scheduled task needs in Claude. In claude.ai, Cowork and the desktop app, a run with nobody there stops at the first tool Claude asks about. So the set-up's first step is in AdCopilot's Tool permissions (Customize, then Connectors, then AdCopilot): the Read-only tools on Always allow, and under Write/delete tools two of AdCopilot's own, “AdCopilot: what is connected and where you are” and “AdCopilot: save business notes and progress”, on Always allow too. They keep AdCopilot's own notes and never change Google Ads; every other Write/delete tool stays on Needs approval, so Claude asks before each change (Cursor has no such screen; see Approvals in Cursor above). Claude has listed the first of the two under Write/delete tools since AdCopilot v2.42.3, and it is every run's first call, so a task set up with only the read-only tools on Always allow, as the plugin's steps said up to 0.2.13, stops there; setting those two tools to Always allow is the whole fix.
What it will not do
- Switch on a campaign it built. Every campaign it builds is created paused. You switch it on in Google Ads yourself.
- Delete anything but a negative keyword. The connector refuses a REMOVED status server-side: the campaign, ad group, ad and keyword tools accept only ENABLED or PAUSED, and the asset tools refuse a remove outright. Pausing is as far as it goes for all of those, and it is the server that enforces that rather than the assistant's good manners. The one exception is deliberate: each of the three ways to add a negative keyword has a removal behind it, so a negative can be taken back out — which is what makes "you can undo this" true when AdCopilot says it. Attaching a shared negative-keyword list is the genuinely one-way action; there is no detach tool, and a list is detached in Google Ads under Tools, Shared library, Exclusion lists.
- Change your account unasked, while your AI client asks before AdCopilot's write tools. Reads run freely, and AdCopilot proposes each change first. For a hard stop, set your AI client to ask before AdCopilot's write tools: Needs approval in claude.ai, Cowork and the desktop app; in Claude Code, leave them off the allowlist and the session out of
autoandbypassPermissionsmode; in Cursor, use the Allowlist run mode and keep them off the allowlist (see Approvals in Cursor above; Cursor's cloud agents never ask). Take the question away and changes to what already exists, a budget or a switched-on campaign among them, apply without it; only a new campaign is still built paused. The one exception isset_campaign_status, the tool that pauses a campaign or switches one on: Claude Code (version 2.1.214 or later) asks before every use of it, whatever its settings, and a run with nobody there to answer cannot use it; the Claude desktop app may also ask every time for it. The plugin's skills pause or switch on a campaign only with that tool, never with a status in a change to the campaign's settings (update_campaign), which follows your settings like any other change.
Support
- Something wrong with the plugin (a command, a skill, this README): open an issue here.
- Something wrong with the connector (sign-in, a tool call, your account): support@adcopilot.cloud.
AdCopilot version
0.2.16 (2026-10-23, written against AdCopilot v2.44.1, which must be live before this version is published; v2.44.0 was the release in production when this text was written): the release checklist below must be run before this version is tagged, and none of its steps had been run when this text was written, the structural tests and claude plugin validate --strict aside. The listing's name and description now name every Google product people search for. No change to what a customer or the model reads: no skill, command, agent, tool or .mcp.json says or does anything different. In the manifest, the display name becomes "AdCopilot for Google Ads, Google Analytics (GA4), Google Search Console & Google Tag Manager". The description leads with what the agent does, and each claim in it was read against AdCopilot v2.44.1's tools: Keyword Planner data (get_keyword_ideas, get_keyword_metrics, get_keyword_forecast), Search and Performance Max builds (create_campaign, create_performance_max_campaign), the Merchant Center products that cannot show in Shopping (shopping_product_issues, new in v2.44.1), and negative keywords as the only thing AdCopilot removes. It keeps the approval sentence, with its Cursor clause, and the negative-keyword sentence, word for word, because the structural tests require both. The keywords grow from twenty to twenty-eight: keyword-planner, keyword-research, merchant-center, google-shopping, shopping-ads, seo, local-services-ads (lsa_leads and lsa_rate_lead, new in v2.44.1 and on by default) and google-ads-reports. The marker in every file moves to claude-plugin/0.2.16, and so does the plugin.version the eval fixtures' get_org_context answers carry, because the structural tests hold both to the manifest's version; those are the only changes to a skill, command, agent or eval file. No eval case was run for this text: what the model reads is 0.2.15's with the version number moved.
0.2.15 (2026-10-23, written against AdCopilot v2.44.0, deployed 2026-10-22): the release checklist below must be run before this version is tagged, and none of its steps had been run when this text was written, the structural tests and claude plugin validate --strict aside. No change to what a customer or the model reads: no skill, command, agent, tool or .mcp.json says or does anything different, and nothing in the manifest changes but the version. The plugin directory's pre-submission checklist says to keep every file that is not an image or a font under 256 KiB, or the version is held for a reviewer, and to keep the plugin to 512 files or fewer. At 0.2.14 the structural tests were one file, tests/test_structure.py, of 273,469 bytes, 11,325 over that line; the next largest file, the README at 93,719 bytes, was well under it, and the plugin had 242 files. That file is now six files by topic, tests/test_daily_habit.py, test_unattended_run.py, test_manifest_and_marker.py, test_review_and_audit.py, test_handoff_and_approval.py and test_eval_cases.py, the largest 66,136 bytes, with the helpers they share in tests/structure_helpers.py. The tests moved verbatim, the same classes, test names and assertions: 252 tests before and 252 after. The one edit inside them is that the three tests that pin this README's 0.2.14 note now look that note up by its own version, not the manifest's, so a release that changes none of what it pins does not have to repeat it. A new tests/test_plugin_size.py fails, naming the file, on any file that is not an image or a font and is over 250 KiB, or on a plugin of more than 480 files, so this repository now measures the limit itself; the plugin has 249 files. The marker in every file moves to claude-plugin/0.2.15, and so does the plugin.version the eval fixtures' get_org_context answers carry, because the structural tests hold both to the manifest's version; those are the only changes to a skill, command, agent or eval file. No eval case was run for this text: what the model reads is 0.2.14's with the version number moved.
0.2.14 (2026-10-23, written against AdCopilot v2.44.0, deployed 2026-10-22): the release checklist below must be run before this version is tagged, and none of its steps had been run when this text was written. Changes to what the plugin tells the model, one new skill and two new cases, and nothing to the manifest but the version. First, and the reason this version goes out first: a morning check scheduled from the plugin no longer stops at its first call. Since AdCopilot v2.42.3, Claude lists get_org_context, titled “AdCopilot: what is connected and where you are”, under Write/delete tools, and up to 0.2.13 the set-up put only the read-only tools on Always allow, so a run set up that way waited at its first call for an approval nobody was there to give. The daily skill's first set-up step for Claude now says what AdCopilot's own step says (schedule_recipe.ALWAYS_ALLOW_STEP): in Customize, Connectors, AdCopilot, Tool permissions, the Read-only tools on Always allow, and under Write/delete tools “AdCopilot: what is connected and where you are” and “AdCopilot: save business notes and progress” on Always allow too, because they keep AdCopilot's own notes and never change Google Ads; every other Write/delete tool stays on Needs approval, so Claude still asks before each change (Cursor has no such screen; see Approvals in Cursor above). This README's morning section says the same, and the advice to set the other write tools to Blocked leaves those two out, since blocking them would stop every run. The instructions the set-up hands over are AdCopilot v2.44.0's morning-check recipe word for word, and their first call is now: Call get_org_context first, with source "claude-scheduled" and task "AdCopilot morning check". AdCopilot counts only a run whose first call also names that task as its morning check, so a customer's own scheduled task is a scheduled run, not the morning check, and no longer ends the morning-check offer; the daily skill's run sends the task whenever the instructions open with "AdCopilot morning check", even when an older saved copy of the recipe does not name it, and never for a scheduled task worded any other way, and daily-scheduled-run now fails a first call without it. Second (#22), every file that sends the plugin's marker, source set to claude-plugin/0.2.14, now also says, in one sentence that is the same in each, that a scheduled run, started by a Claude scheduled task or a Claude Code routine with nobody there to answer, sends source: "claude-scheduled" instead, never claude-plugin. Before, only the daily skill's run said so, and a scheduled task in the customer's own words, answered by the review skill, reached AdCopilot as a conversation with a person in it. A new case, review-scheduled-unmarked, owned by adcopilot-review, is that run. What this version does not change: the daily skill's run still calls no save_org_context. Every schedule set up before 0.2.14 keeps that tool asking first, and a scheduled run cannot answer an approval prompt, so a run that kept a note would stop there and never finish. The ban lifts only once AdCopilot has told the people who already schedule the check to set the two tools to Always allow, and that notice has been measured. The third to eighth changes (#24) come from one conversation on 2026-10-20, on a workspace with 10 operations a day: the audit page's "Fix all three in Claude" question was answered with three menus of numbered options and up to three related reads per check, the daily-habit offer came after the first of the three checks, and the day's operations ran out before the fix the customer had agreed to could run. Third, a new skill, adcopilot-fix-checks, for the questions AdCopilot's audit page and emails send: "My AdCopilot audit of Google Ads account … flagged three checks to fix first", or one that "failed" or "warned on" a check, each named with its ID in brackets. It says first what the fixes cost in operations (each check's read, plus one call per keyword a change pauses or per campaign or list it adds never-show-for words to, plus a read-back) and what today covers, recommends the order itself, foundation first (counting results before anything judged by them), reads each check with its own lens and runs no new audit, puts each fix to the customer as one yes/no with at most five rows of any list, reads back every change with how to undo it, and makes the daily-habit offer only once a fix has been applied and read back. A search-term check (G18 and its family) goes to the never-show-for words, zero-conversion keywords (G-WS1) to a pause, and a conversion-tracking check (G43 and its family) to the steps AdCopilot can take, or to the clicks in Google Ads where it cannot make the change. Before a call that would use the last of today's operations, it says what that call covers and asks first. Its case, fix-checks-three-checks, is new and had not run when this text was written. Fourth, the daily-habit offer, in all eight files that carry it (fix-checks carries the block itself) and in adcopilot-review, is made in a reply that asks the customer no yes/no on a fix: the reply that reads back the first fix they approved, where the offer stands in for the next fix's yes/no (the next fix is named in one line and proposed in the reply after), or the close of a reply that proposes none. A fix for a finding the morning check also reads is still what lets the offer say the check would have caught it; on a workspace with 10 operations a day, whose morning check leaves what changed for the chat, a change nobody asked for gets the close instead. Fifth, one decision per reply. adcopilot-review and /adcopilot:negatives end on one yes/no on the step they recommend, never a numbered menu or a request to pick the order; they show at most five rows of any list, costliest first, then "and N more"; and in Claude Code they put that yes/no with the AskUserQuestion tool. adcopilot-review also says what a call covers and asks first before the one that would use the last of a short day's operations. Sixth, related_reads: every surface now runs at most one that bears on the question (it was three), as AdCopilot's own instructions say from v2.43.3. Seventh, /adcopilot:negatives no longer carries disable-model-invocation, so the model can start it when the customer asks to block searches; its description now says checks from an AdCopilot audit are the fix-checks skill's and a question about how the ads are doing is the review's, which is what 0.2.12's flag was for. The fix-checks steps apply its write steps with the search-term read they already hold and do not start it, which would read the last seven days again. Eighth, the unattended morning check, on a workspace with 10 operations a day, makes its first four reads only, leaving what changed for the chat, so the fix its report ends on still fits the same day: at 0.2.13's weights the four count 6 of Free's 10, where the five count 7 and a run about 8 to 10, and the daily skill's run and set-up and this README say so. Ninth (#25, #28), the audit's count. AdCopilot v2.44.0's audit has seventy checks, each listed, and grades its score over the checks that apply to the account. The review skill and the auditor say seventy where they said seventy-three, and give coverage as the count the result gives, "from N of the M checks that apply", still never as a percentage. The eval mocks' audit results carry v2.44.0's counts, and tests/test_check_count.py fails on any count of the checks above seventy, except in the eval tool snapshots (_tools.json), which stay as they were taken until they are refreshed whole from the server (issue #5). Tenth (#26), an audit finding's fix. From v2.44.0 a finding can carry its fix in recommended_actions: a ready call (kind tool), its IDs read from the account and an audit's negatives Exact, or a draft (kind skeleton) whose needs says what only the customer can give, such as the searches to block or the campaign to take budget from. The fix-checks and review skills propose a ready call as it stands, with the condition its why gives, ask for a draft's needs in their one question and never fill it in themselves; the review skill's own PHRASE rule is now for the negatives it writes itself, so it never rewrites an audit's Exact call. Release order: every server half is live, AdCopilot v2.44.0 included, so nothing on the server waits for this version. From v2.43.3 AdCopilot hands an audit question to the fix-checks steps on a running account and tells a Claude that called with no marker, or with the bare claude-plugin, to load the installed skill first, without handing it the turn; against an older server the skill would still run, but next_step would name only the review steps. The marker in every file moves to claude-plugin/0.2.14, and so does the plugin.version the eval fixtures' get_org_context answers carry. No eval case had been run for this text when it was written.
0.2.13 (2026-10-20, written against AdCopilot v2.43.1, deployed 2026-10-20 17:51 UTC): the release checklist below says its steps must be run before this version is tagged, and 0.2.13 is released by the founder's choice on 2026-10-20 ("do the plugin release") without its real-account steps 1 to 6, as 0.2.10 and 0.2.12 were, and without the eval suite, as 0.2.10 was, because the change is text only. Step 7: both claude plugin validate --strict runs pass, and so do the structure tests in tests/test_structure.py; neither the suite nor the tools-only ablation has been run for this text. One change to what the plugin tells the model (#23), and nothing to the manifest but the version: Free's weighted operations. From v2.43.1 AdCopilot counts a Free workspace's calls in weighted operations: a full audit or an account health score counts 4, an analysis such as budget pacing 2, listing audiences 2, and every other call 1, except the four that never counted (AdCopilot's own get_org_context and save_org_context, get_resource_metadata and list_accessible_customers), which still count 0, while a paid plan and a running trial count every call 1, and a trial held to Free's 10 a day (paused for its card, or waiting for its first charge) counts as Free does. The daily skill keys its cost on get_org_context's org.ops_weighted, which v2.43.1 adds to the org block, because a held trial's plan still reads pro. The daily skill's cost (in its run and in its set-up), this README's, /adcopilot:audit's (6 of Free's 10 for its two calls) and /adcopilot:weekly's (about 5 or 6 for its four reads), each with 1 or 2 more for every related read run, now say so: the morning check's five reads come to 7 operations at that table, and a run to about 8 to 10 of Free's 10. Nothing changes for a paid plan. AdCopilot sends ops_weighted only while its weighting switch is not off (v2.43.1 runs it at free); set back to off, the daily skill says reads again on its own, but this README and the two commands would overstate Free's cost, so this text goes back with the switch. .gitignore now keeps Python's compiled caches out, and tests/test_structure.py holds each sentence and fails on a tracked cache. The marker in every file moves to claude-plugin/0.2.13, and so does the plugin.version the eval fixtures' get_org_context answers carry. AdCopilot reads any version from 0.2.7 on, and v2.43.1 is live, so nothing on the server waits for this version.
0.2.12 (2026-10-17, aligned with AdCopilot v2.41.0 on 2026-10-18): the release checklist below says its steps must be run before this version is tagged, and 0.2.12 was tagged on 2026-10-19 by the founder's choice ("release Plugin 0.2.12") without its real-account steps 1 to 6, as 0.2.10 was. Step 7: both claude plugin validate --strict runs pass, and the suite (Claude Code 2.1.303, judge sonnet, against AdCopilot v2.42.0, 2026-10-19, evals/results/rerun-v10-2026-10-19T14-27-25Z, gitignored) passes every case at 0.8 except the three habit-offer cases. Those split by run-to-run variance: across the v8 and v10 runs the same unchanged case passed and then failed (habit-offer-chat 0.956 then 0.778, habit-offer-untold 1.000 then 0.690), so the founder chose to ship the v8 wording rather than iterate it on two or three runs per case. The tools-only ablation is green: review-follows-related-reads holds as a guard (tools-only 0.932) and meta-connector-absent, review-question-first-roomy and daily-scheduled-run are gated with deltas of +0.333, +0.292 and +0.238. Changes to what the plugin tells the model, and nothing to the manifest but the version. They come from one conversation on a workspace whose plan has no daily limit, on 2026-10-14, and every sentence is written to be true against AdCopilot v2.41.0, which must be live before this version is published: the dates, the coverage count and the list of costly searches below are that release's. First, the read ceiling. cross_reads.ads_reads_max is how many Google Ads reads the server sized the situation for: the size of one answer, not a workspace, plan or daily limit. Every surface that names it now says so, says the day itself is short only when cross_reads.reads_left_today is present, and offers a read left over as the next read, in a statement rather than a second question ("The next read would show how each campaign bids; say the word and I will run it."), rather than announcing it as a limit; the review in that conversation stopped at its five reads and said it had hit "today's read limit". adcopilot-review also gains a rule under What breaks: a daily limit, a quota or today's look-ups is mentioned only when reads_left_today is present or a call returned the daily-cap refusal. /adcopilot:daily and adcopilot-launch stop calling the number a cap. Related reads still count toward the answer's reads. From v2.41.0 AdCopilot sizes an account that spends with nothing counted at eight reads instead of five on a day with no daily limit, or a limit of at least 6,000, outside a scheduled run: the five it names, then up to three related reads. Every other day and situation keeps its number, and a scheduled run, which skips related reads, keeps five. Second, dates and counts. adcopilot-review and the auditor end their own search reads on the date_range.end that full_audit and the other lenses carry, and say the dates once. From v2.41.0 AdCopilot ends the audit's window and the other analysis windows on the account's yesterday, the last full day in its own time zone; before, it ended them on UTC's, and in that conversation the audit's seven days ended on 12 Oct while the account's only result came on 13 Oct. This month's pacing (budget_pacing) and the change log (change_history) run through the account's today, the day in progress, so the review and the auditor give their dates on their own and never end a read on them. They give a finding's shorter window its dates with any later result in the same sentence, and give coverage as the count the result gives, "N of 73 checks gave a result", with reasons for the rest only in the result's own words; /adcopilot:weekly dates its two weeks and any other window the same way. Third, the costliest searches. Before giving a count of searches, the review and the auditor look for their names in every result they hold. That means analyze_search_terms' "Costliest search terms with no conversion", which v2.41.0 adds (the costliest first, with their clicks and cost, and how many there are), and the wasted-spend check's list in full_audit or analyze_waste, which names each search whenever the account counted a result in the window. When one lists them, the top ones are named with their cost. Where the list says nothing was counted anywhere in the account, they are called the costliest searches, not proven waste. When what they hold only counts them, they say so, and they offer another read for them only when a related read's own reason says that read shows them. /adcopilot:daily and /adcopilot:negatives name them from the same list. Fourth, adcopilot-docs answers a question about AdCopilot from the one get_org_context call. It makes no account read, starts no other skill, and follows none of next_step, the playbook, cross_reads or question_map on that turn, not even their offer of a skill's steps (a limit next_step names is kept). It says what AdCopilot can do as one list, says what each routine reads and proposes while leaving approval to the one line on what AdCopilot may change, and ends on exactly one question offering one read, tied to one fact from the 30-day summary with the dates last_30d names. From v2.41.0 the server itself has a question answered first and the installed steps offered after. Fifth, approval. No sentence in the skills, the commands, the agent, the manifests or this README promises a yes before every change without saying what makes that true, and the condition is now the same for every AI client: AdCopilot proposes each change first, and the hard stop is your AI client set to ask before AdCopilot's write tools, Needs approval in Claude and, in Cursor, the Allowlist run mode with the write tools kept off the allowlist (Cursor's cloud agents never ask). The earlier 0.2.12 text gave Claude's setting alone as the condition, and Cursor has no such setting. That covers the plugin's description in the plugin directory, /adcopilot:negatives' description in the command picker, which now promises nothing, and the line the auditor's report carries, which now says each change is a proposal for the customer's own conversation. A related read's own sentence says a change is still only proposed. tests/test_structure.py fails on such a promise without "as long as" or "while" in the same sentence, in the second person on every surface and in the third person in this README, the manifests, every description and the auditor's report, and it fails on the Claude-only form of the condition anywhere. A rule the model is given in a skill's body, to apply a change on the customer's yes and not before, keeps no condition, because the model keeps to it whatever the client's approval setting. adcopilot-docs and this README also name the one exception v2.41.0 brings, and how far it goes: Claude Code (version 2.1.214 or later) asks before every use of set_campaign_status, the tool that pauses a campaign or switches one on, whatever its settings, and a run with nobody there to answer cannot use it; the Claude desktop app may also ask every time. Every skill and command that pauses a campaign or switches one on now does it with that tool, never with a status in update_campaign, which v2.41.0 also accepts and does not hold. Sixth, adcopilot-measure's description names both routes, through Analytics or directly in Google Ads, and says what the direct route needs as the skill walks it: no Analytics, and its conversion tag on the site through Tag Manager. /adcopilot:audit now says to call get_org_context first, as every other surface does: v2.41.0 can add a note to a first call that skipped it, once that switch is on. Seventh, Cursor. AdCopilot's connector is listed on cursor.directory, and Cursor reads skills from .cursor/skills or .claude/skills, so the plugin's text now checks for Cursor first wherever it works out which client it is in: /adcopilot:setup, adcopilot-connect, the daily-habit block, adcopilot-review's own paragraph on it and adcopilot-daily's set-up. In Cursor it never runs claude mcp (Cursor's agent can reach a shell where Claude Code is installed, and that command lists Claude Code's connectors), looks for the connector in Cursor's MCP settings, where one added from cursor.directory may be named server, and gives Cursor's sign-in step in place of the plugin's Connectors tab; /adcopilot:audit's fix for an out-of-date tool list gives Cursor's step first (turn AdCopilot off and on again in Cursor's MCP settings) in place of /mcp. The daily-habit offer now follows AdCopilot's own decision for the client: it is made only when get_org_context hands over routine.schedule, which AdCopilot sends only when its overlays.schedule_offer is set, for Claude and Claude Code and never for Cursor, and it is worded by routine.schedule.offer; as on the server, a scheduled run holds the offer back once it has checked in on two different days. In Cursor the plugin makes no schedule offer of its own, but when next_step asks for an offer of a check-in or a summary (v2.41.0 still closes a Cursor turn with one while the routine is pending), it makes that offer as next_step says, with the playbook's close for the steps. A no is recorded in routine.own_schedule only from Claude, because AdCopilot keeps that answer for the whole workspace and a record made in Cursor would stop the offer in Claude too. Every command now knows what to do when the skills are not installed, because cursor.directory installs commands one by one: /adcopilot:setup, /adcopilot:launch and /adcopilot:measure follow get_org_context's next_step and playbook, and /adcopilot:audit, /adcopilot:daily, /adcopilot:negatives and /adcopilot:weekly send source "claude-plugin" with no version there, because on a versioned marker v2.41.0 hands the turn to the skill that owns the account's situation and leaves that skill's steps out of next_step and the playbook, while on the bare one it names no skill and gives the steps itself. cursor.directory's copies of the commands are 0.2.11's until they are pasted there again, so none of this reaches them before then. The auditor agent carries readonly: true for Cursor, whose subagents have no tool list and inherit every tool; its list of read tools is enforced only in Claude Code, and Cursor does not say readonly holds back an MCP write. This README gains a Cursor section under Install and Google's disclosure, word for word as AdCopilot's site gives it. Eighth, four changes after the eval runs of 2026-10-18. /adcopilot:negatives carries disable-model-invocation: true: the model can no longer start it on its own, as it did for a question about which searches waste money in place of adcopilot-review, and it runs as before when it is typed. adcopilot-review neither offers nor mentions a related read it skipped because it does not bear on the question, and the figure in that read's why stays out of the answer. The daily-habit offer, the same in all seven files that carry it, is a paragraph of its own, right after the paragraph or list that gives the finding and never added to the end of one of its sentences, and two sentences in fixed words, with no semicolon and no set-up step before a yes: a read-only check that runs by itself every weekday at 8:07 would have caught the finding on its first morning, opening with "All clear" or "Needs a look" plus one yes/no fix; then, if they say "schedule it", the offer to set it up for where they are. At the close, the first sentence says the same check can look over the account each morning. On those runs every judge vote failed the earlier wording on the check that the offer is one or two sentences with no set-up step, and on a later run that check failed again only where the offer was added to the end of the paragraph about the searches. /adcopilot:daily's opening line names every finding that needs a look. Five new cases are written; three of them ran once, on 2026-10-17, against an earlier 0.2.12 text, and two never have (see the Evals section). Three are owned by adcopilot-review: review-uncapped-no-limit-words, review-question-first-roomy (a question answered first on a plan with no daily limit, with eight reads) and review-account-day-named-searches (one set of dates ending on the account's own yesterday, and the costliest searches named). Two are owned by adcopilot-docs: docs-capabilities-untracked and docs-capabilities-routed. Every get_org_context answer in them carries v2.41.0's own overlays, hints, next_step, playbook and cross_reads for its state, worked out and rendered by that release's code, and the account-day case's wasted-spend and search-terms findings are that release's evaluators' own, with this month's pacing and the change log running to the account's today. The first of the two docs cases is the state of that conversation's server answer, an account spending with nothing counted handed to adcopilot-measure; the second is a counted account handed to adcopilot-review. Each item of their rubric is its own llm grader, numbered as in the rubric, and the no-review item allows what the workspace answer itself says: the suite's judge answers one word, and given every item at once it failed replies it passed on each item alone. The fourteen older cases with a get_org_context answer of their own are re-rendered the same way, from their own data, by v2.41.0's code, with two values in that data changed rather than carried over, as each fixture's header says: routine.weekly.status is skipped in five of them (it was off), so that next_step carries no close where the case says it asks for none, and habit-recorded-stays-quiet gains routine.scheduled_first_seen_at a day before its last sighting, because v2.41.0 believes a schedule only once a scheduled run has checked in on two different days; setup-signed-out's answer is the client's sign-in error, not a server answer, and the suite-wide answer in evals/mocks, which eight cases share, is left as it is. Their call-log graders now look for a call by its own tool, because v2.41.0's playbook and question_map name tools inside the answer itself, and the account-day case's date graders also read a < bound on the day after, a single day, a date in double quotes, a DURING range that takes in the account's today, an open range (a > or >= bound with no upper bound, which runs to the account's today) and an IN list holding a wrong day. Neither the suite nor the tools-only ablation has been run for this text, so none of those cases has run on its new answer. The marker in every file moves to claude-plugin/0.2.12. AdCopilot reads any version from 0.2.7 on, but this text describes v2.41.0, so that release goes out first.
0.2.11 (2026-10-13) is untagged, and the release checklist below has not been run for it. Two changes to what the plugin tells the model, and nothing to the manifest but the version. First, the daily-habit offer moves from the end of the reply to right after the first finding the morning check also reads (the searches that spent with no sale, a campaign out of budget or off its budget pace, a disapproved ad, a drop against the 7-day average, a change nobody asked for), tied to it, with the value line: a check that runs by itself every weekday at 8:07 would have caught it on its first morning, it only reads the account, and it opens with "All clear" or "Needs a look" plus one yes/no fix. For any other finding, even one that costs money (a tracking fault, a Quality Score problem, a targeting problem), or none, the offer closes the reply, before any question the reply ends on, and does not say the check would have caught it, because the morning check does not read those. The seven files that carry the block and adcopilot-review's own paragraph change together. A no now ends the offer for the conversation, asks once whether the customer's Claude can create scheduled tasks, and records routine.own_schedule as unavailable if it cannot and declined if it can, which AdCopilot holds for a week and then lets the offer come back; get_org_context shows declined only for that week, so the plugin does no date arithmetic. The offer stays a statement ("say "schedule it""), not a question, so a reply never ends on two. Second, the cost of a run: about six or seven reads, not five, each counted toward the workspace's daily operations, and a Monday that also runs the weekly summary can use the whole day's, which on Free is 10. Release order: this version is written against the AdCopilot build that accepts declined; on an older server that call is rejected by name, so the server is released first. The eval graders for the offer no longer look for it "at the end", and the three habit cases have not been re-run for 0.2.11. The marker in every file moves to claude-plugin/0.2.11.
0.2.10 (2026-10-12) was tagged on 2026-10-13 without the release checklist below being run for it; the founder chose to tag it unrun, and its release notes say so. Three changes to what the plugin tells the model, and nothing to the manifest but the version. First, a finding whose own text carries a condition. Every surface that relays a lens's findings to the customer now carries one sentence: a finding that says "if X" is not a step until X has been checked, so either make the read it names and say what you found, or carry the condition into your own sentence, and never turn it into an instruction. Nine surfaces carry it: the auditor agent, /adcopilot:audit, /adcopilot:daily, /adcopilot:negatives, /adcopilot:weekly, and the adcopilot-review, adcopilot-daily, adcopilot-launch and adcopilot-measure skills. adcopilot-seo's own steps name two lenses only to say what they do not cover and run none, so it carries nothing; it can still reach analyze_waste or analyze_search_terms when a raw search it makes ends with related_reads, and carries no sentence for that case, and tests/test_structure.py fails on any file that names a lens and is neither covered nor excused. The names it looks for are the twelve lenses the auditor enumerates, which a test holds the list to include, and explain_conversion_gap, which the auditor is also tooled for. In the unattended run the sentence sits among the run's rules with its limit stated: the run makes only its own reads, so a condition whose read is not one of them is carried into the sentence. The auditor's read-only tool list gains ga4_list_conversion_events, the read a conversion finding can name; the list is still enumerated reads only. Second, the depth of full_audit. AdCopilot v2.35.2, the release this was checked against, runs a call that names no depth deep and falls back to a quick audit, saying so, only if that run cannot finish; an explicit deep never falls back. adcopilot-review, the auditor and the description of the review-spends-the-depth case said the default was quick; they now say what the server does and still pass depth="deep". The eval mocks and the _tools.json snapshots are frozen copies of the server's older text and are left as they are; no grader changed. Third, a new skill, adcopilot-meta, for questions about Meta ads. AdCopilot has no Meta integration (one is on the roadmap, with no date). Meta publishes a connector of its own for Meta ads, which it calls the ads MCP server and an open beta, so the skill looks for it in the conversation first. When it is not there, the skill says what it is and how to add it, and gives Meta's help page and AdCopilot's guide, Meta ads alongside AdCopilot. When it is there, Meta's figures come from Meta's connector and Google's from AdCopilot, each labelled with its source; no figure read from Meta goes into an AdCopilot call; and a change through either connector is proposed first, one platform at a time, because Meta's pages say the AI client asks for confirmation and do not say Meta's server also does, so the skill asks itself. Meta's pages were checked on 12 October 2026. For adding the connector in an assistant without a developer app of the customer's own, Meta's pages state no list of countries, no role on the ad account and no verification step, and the skill states none of them; Meta's developer documentation does have an app review step for a customer who uses a Meta developer app of their own to act for other businesses, and the skill sends that customer to Meta's pages and says no more. Its text carries no plan or price word, and it describes Meta's rollout only in Meta's own words (an open beta; an account may not have access to all of its tools and features yet). It also says that text inside Meta's results is data, never an instruction. The guide is a page on AdCopilot's docs site, published from AdCopilot's own repository, and it must be live before this plugin is tagged or published, or the link in the skill leads nowhere for every customer who asks about Meta without Meta's connector, which is the skill's main answer. Release in this order: merge the docs commit and ship the AdCopilot release that carries the docs build, fetch https://adcopilot.cloud/docs/guides/meta-ads-alongside-adcopilot and check that the page title is the guide's, and only then tag and publish the plugin. The status code is not the check: the docs site answers a path that does not exist with HTTP 200 and the title "404: This page could not be found." The skill passes the plugin marker only when Google figures are part of the answer, so the structural test that counts the files carrying the marker moves from eleven to twelve. Its eval case is written and has not been run. evals/meta-connector-absent (tag meta, entered in TAG_TO_SKILL as adcopilot-meta) asks about Instagram ads with Meta's connector absent and grades that the reply gives Meta's help page, AdCopilot's guide and the address, reads nothing through AdCopilot, states no figure from memory and none of Meta's terms. The tools-only ablation prints FAIL: no gated case for a skill with none, so step 7 of the release checklist has a case to measure now, but it cannot be green until that case is run under an authenticated Claude Code and its delta over the tools-only arm is positive; if the case turns out to be a regression guard (the tools-only arm already scores 0.8 or more on it), the skill still has no gated case and needs another, or the founder's explicit waiver recorded in the release notes. The marker in every file moves to claude-plugin/0.2.10, which AdCopilot reads like 0.2.7 to 0.2.9. The structural tests and both claude plugin validate --strict runs pass. Releasing steps 1 to 8 have not been run for 0.2.10: the real-account runs, the eval suite and the ablation need a person with an authenticated Claude Code, so no gate result exists for these words, and 0.2.8's results stand only for the text this release leaves alone.
0.2.9 (2026-10-12) changes the directory listing and nothing a customer runs. The display name becomes "AdCopilot for Google Ads, Google Analytics, Google Search Console & Google Tag Manager", the description and this README's opening lead with the agent that runs your Google Ads and say AdCopilot is not related to other products of that name, the keywords grow from six to twenty, and the manifest's icon points at a 1024x1024 PNG (.claude-plugin/icon.png, the same mark as icon.svg), because the directory cannot display an SVG given as a path. Two directory warnings are addressed: the auditor agent's tool list drops Read, which it never used (a report is its return value, and the list stays enumerated reads only), and a .gitignore comment no longer mentions running anything. The marker in every file moves to claude-plugin/0.2.9, which AdCopilot v2.34.10 and later read like 0.2.7 and 0.2.8. No skill, command or agent body changes beyond the marker, so 0.2.8's gate results stand for the text; the suite and the ablation have not been re-run for 0.2.9. It was tagged on 2026-10-13 on that basis.
0.2.8 is written against AdCopilot v2.34.19 (deployed 2026-10-09 21:04 UTC), the release whose lens results end with related_reads and whose get_org_context answer carries question_map; it was published on 2026-10-10 and tagged on 2026-10-12, and its gate is recorded in the 0.2.8 note below. 0.2.7 was released against AdCopilot v2.34.10 (deployed 2026-10-08 08:00 UTC; connector tools revision 7b2eee34, read live from get_org_context on 2026-10-08), the first release that reads the version on the plugin marker. The components were written against AdCopilot v2.34.0 (connector tools revision 7016a0aa, read live on 2026-10-06). The review, docs, Search Console and auditor components were written and checked against that server; the earlier flows were written against v2.30.2 and re-checked against v2.34.0 for the claims they make. The tool snapshot the eval suite mocks against (evals/mocks/adcopilot/_tools.json) is frozen at connector tools revision 0066a60e, taken 2026-09-23 under v2.14.1, except its get_org_context entry, which is the live schema at revision 782ec74d (read 2026-10-03) so that the eval model is offered source as a real client is — every case that reads the server fails its marks-plugin-use grader on a get_org_context call without source: "claude-plugin/<version>", the bare "claude-plugin" included, because the version is what the server routes on (the tools-only ablation's stub carries the same versioned marker, so that grader adds nothing to any skill's delta); the live revision moves independently of this plugin, and a difference between the two is not a fault. /adcopilot:audit relies on the tools_revision that get_org_context reports from v2.14.1 on, to refresh a stale tool description in-conversation. The skills, the four commands that read the account and the auditor call get_org_context with source: "claude-plugin/<version>": the plugin marker followed by this plugin's own version from .claude-plugin/plugin.json. It is written out in every file, because a skill cannot read the manifest at run time, and tests/test_structure.py holds every copy equal to the manifest, so a version bump moves them all or fails. AdCopilot reads the bare "claude-plugin" from v2.29.0 on to record plugin use per workspace. The version after the slash is read from the AdCopilot release that routes on it (v2.34.10, live from 2026-10-08): on a call marked by plugin 0.2.7 or later, the first version whose version it reads, that server hands the turn to the installed skill that owns the situation instead of repeating its steps in its own playbook — adcopilot-connect, adcopilot-launch, adcopilot-measure, or adcopilot-review for a running account whose results are counted. The bare marker, a version below 0.2.7 or one it cannot read counts as no version: the call is recorded and answered with the server's own playbook, as before, because no release before 0.2.7 carries the sentence below. That server routes by the account's situation, not by which command or skill made the call, so each of them, when the answer's plugin.skill names a different skill, does the request it was given, keeps any limit next_step adds, and offers that skill's steps afterwards: /adcopilot:weekly on an account whose results are not counted still writes the week, and offers the measurement steps after it. A server before that release does not recognise the suffixed value: it records no plugin use, and the reminder to install the plugin comes back for those workspaces, so that release is live before any plugin version carrying the suffix is published. Calls without the marker behave as before. adcopilot-daily's unattended run passes source: "claude-scheduled" instead, and the daily-habit offer stops when get_org_context reports routine.daily.status user_set, a date in routine.scheduled_last_seen_at, or routine.own_schedule saved or unavailable, the three things that stop the server's own schedule offer; the set-up records routine.own_schedule itself (saved, which AdCopilot accepts even while its emailed check-in holds the routine, or unavailable on a Claude with no scheduled tasks), because from 0.2.7 the server leaves its own schedule section to this plugin on a turn a skill owns. AdCopilot records that marker, and reports routine.scheduled_last_seen_at, routine.daily.customer_id (the account its own check-in reads) and latest_check_in, from v2.30.2 on; on an earlier server the value is ignored and those fields are absent, so the offer stops only for a schedule recorded with save_org_context, which AdCopilot refuses while its own emailed check-in (in_app) holds the routine. The instructions the set-up hands over are AdCopilot's recipe (SCHEDULED_CHECKIN, from v2.30.2 on) word for word, and tests/test_structure.py holds the skill's copy to it. The new cases for /adcopilot:daily and the scheduled run add case-level _tools.json entries for analyze_search_terms, add_negative_keywords, update_campaign and save_org_context, read live at revision 782ec74d on 2026-10-04, and their get_org_context mocks carry the new fields; the suite-wide snapshot is unchanged. The cases for the review and Search Console skills do the same: review-spends-the-depth pins analyze_waste and analyze_search_terms at revision 7016a0aa and full_audit at revision 6145d0b5, and seo-two-numbers-that-lie pins the five Search Console tools at 7016a0aa, all read live on 2026-10-06. full_audit is pinned deliberately: the suite-wide snapshot's older copy of its description says findings come back “ranked by impact”, and the live one says the opposite — failures first, then by severity, not impact — which is the fact the review case is built on, so a case left on the snapshot would have measured the skill against a description that contradicts the server. Each tool's Tools revision is a hash of that tool's own text, so tools read in the same listing can report different ones; only the value get_org_context returns is the connector's.
Evals, and how a skill earns its place
The suite in evals/ runs with claude plugin eval . --trust-plugin --ablation none --threshold 0.8 --judge-model sonnet --concurrency 2 --max-cost-usd 20 --no-publish, which the release checklist below runs before a version tag — by hand, because it drives a model and no workflow here holds a credential to sign Claude Code in (see What CI runs, and what it does not at the end) (the judge model is not incidental: the same case scored 0.81 under the default judge and 0.905 under sonnet). The harness's own with/without comparison removes the whole plugin — connector included — so it cannot say what a skill adds; evals/ablation/tools-only.sh runs the same cases against a copy of the plugin whose skills are cut to their frontmatter and prints each skill's delta over that tools-only arm. A skill whose cases show no delta there is deleted, not kept; the gate reads on each skill's weakest case as well as its mean, so one strong case cannot carry a dead one — and a case the tools-only arm already passes at 0.8 is a regression guard on the connector, not evidence about the skill, so it is held at 0.8 and left out of the delta. Fixtures under evals/**/mocks/ are a fictional tenant: the field names follow the live connector's answers and every value is invented. Each of the nine skills owns at least one case — thirty cases across the suite, which is what evals/ablation/delta.py's TAG_TO_SKILL records — and a skill with no case of its own fails the gate outright, because nothing measures it. 0.2.14's fix-checks-three-checks, for adcopilot-fix-checks, and review-scheduled-unmarked, for adcopilot-review, had not run when this section was written. The six newest cases (meta-connector-absent, for adcopilot-meta; review-uncapped-no-limit-words, review-question-first-roomy and review-account-day-named-searches, for adcopilot-review; docs-capabilities-untracked and docs-capabilities-routed, for adcopilot-docs) have not run on this text, so they have not yet earned their place: four of them (meta-connector-absent, review-uncapped-no-limit-words, docs-capabilities-untracked and docs-capabilities-routed) ran on 2026-10-17 against an earlier 0.2.12 text, where meta-connector-absent scored 0.778, under the 0.8 line on all three of its runs, and the other two have never run. The four cases added in 0.2.5 (docs-safe-to-let-loose, review-spends-the-depth, seo-discovered-is-not-a-verdict and seo-two-numbers-that-lie) are built the other way round from the rest: each one grades only what its skill's BODY adds, after subtracting what the tool descriptions and the server's own playbook already say. Since the Cursor round their answers are v2.41.0's rendering for a call carrying the versioned marker, which the tools-only stub sends too, so the baseline arm is the plugin present with its skill bodies empty: it gets the [PLUGIN] hand-off to the installed skill, not the full branch a connection without the plugin would get, and a delta can include text the server gives such a connection. For the review skill that meant dropping “biggest money first” as the thing measured — the live playbook for a tracked account says it in those words — and measuring the deep-depth call, the coverage number, the micros rule and the two-sided recommendation instead.
0.2.5's ablation is also the first run where the gate said no, and what it caught is worth keeping: adcopilot-seo's only case scored 0.852 on the tools-only arm, above the 0.8 line, so it was classed a regression guard, left out of the delta, and the skill was left with no gated case — FAIL, “no case the baseline does not carry”. The skill was not the problem. The case was: six of its seven graders passed on the baseline. Three traps written as the skill's knowledge turned out to be things a capable model already does — it passes row_limit explicitly without being told to, it does not reach for a write tool unasked, and it knows an average position is an average. Only the sitemap's dead indexed field survived (1/3 on the baseline, 3/3 with the skill). The remedy the rule prescribes for a skill with no delta is deletion, and it would have been the wrong one here; the right one was a case aimed at what that arm got wrong, which is seo-discovered-is-not-a-verdict. So: a red ablation names a skill, but read which graders moved before believing it is the skill that is dead. Two negative deltas in the same run — audit-stale-tools -0.095 and habit-offer-code -0.167 — are each one llm rubric that both arms fail some of the time, with every deterministic grader passing 3/3 in both; they are variance, not regressions, and the per-grader breakdown is how you tell.
0.2.5's green table, all seven skills ok on their gated cases: review +0.500, docs +0.370, launch +0.295 (min +0.292), daily +0.294, seo +0.250, measure +0.238 (min +0.143), connect +0.189 (min +0.078). It was composed from two runs rather than one, which is worth saying plainly: the 20-case run above, plus a --case 'seo-*' re-run for the case written in answer to it, spliced and re-scored through delta.py. Same judge, same claudeVersion 2.1.289, same day, and no commit touched skills/, agents/, commands/ or the manifest between them, so the twenty older cases were measured against byte-identical content. A single whole-suite run is the cleaner artefact and costs about $35; splicing is honest only while that last condition holds, so check it with git log -- skills/ agents/ commands/ .claude-plugin/ before relying on one.
0.2.6 (2026-10-07) was measured PARTIALLY, and here is exactly how much. Three cases were re-run — seo-discovered-is-not-a-verdict 1.000, seo-two-numbers-that-lie 0.926, audit-stale-tools 0.905 — 3/3 at the 0.8 threshold, overall 0.944, judge sonnet, claudeVersion 2.1.292, $2.87, 236s. Those are the only three cases whose inputs changed: the release touched skills/adcopilot-seo/SKILL.md (body only), commands/audit.md and agents/adcopilot-auditor.md, and nothing else under skills/, commands/ or agents/. The other eighteen cases are carried over from the 2.1.289 run against byte-identical text for their skills. No whole-suite run under 2.1.292 exists. That is a weaker artefact than 0.2.5's and is recorded as such: the carried-over scores are honest about the TEXT and silent about the BUILD, which is the one comparison this file keeps warning about.
The seo ablation was run fresh on both arms under 2.1.292, because a body-only change cannot move the tools-only arm but a build change can move everything: seo-discovered-is-not-a-verdict 1.000 vs 0.750 (+0.250), seo-two-numbers-that-lie 0.926 vs 0.778 (+0.148), so adcopilot-seo is ok on a mean of +0.199 and a minimum of +0.148. Only seo needed it — commands/audit.md is not stripped by tools-only.sh (it cuts skills/*/SKILL.md only, line 51) so it moves both arms equally, audit-stale-tools is tagged audit which is not in TAG_TO_SKILL and so owns no skill's gate, and agents/ is outside the suite entirely (see the eval-coverage issue).
And a reclassification worth more than the scores. seo-two-numbers-that-lie scored 0.852 on the tools-only arm under 2.1.289 — above the line, a regression guard, excluded from the delta, which is what left adcopilot-seo with no gated case and threw 0.2.5's first FAIL. On 2.1.292 the same case text with the same judge scores 0.778 on that arm, so it is now a gated case and seo has two. Nothing in the case or the skill changed between those numbers. So 0.2.5's "the skill was not the problem, the case was" diagnosis was half the story: the classification is a property of the BUILD as much as of the case, and a skill can pass or fail the gate on a client upgrade alone. Re-read the class column before acting on a FAIL, and never carry a Class verdict across builds.
0.2.7 (2026-10-08) is the release the server's routing waits for. The marker says its version — every interactive get_org_context call, fourteen sites in eleven files, passes source: "claude-plugin/<version>" with the manifest's version — and from AdCopilot v2.34.10 the server reads it: on a call from 0.2.7 or later it hands the turn to the installed skill that owns the account's situation instead of repeating that skill's steps in its own playbook. The rest of the release is what makes that hand-over safe. Every yes closes a loop: adcopilot-review applies the fix, reads back only what the result does not show, says how to undo it, records it and proposes the next fix the same way; /adcopilot:audit ends on its first fix as a proposal; a yes to a finished scheduled run re-reads what the fix touches before the change is made; and adcopilot-review carries the two rules the server's counted-account branch used to state (no bidding change in the weeks after something went live, and on a day with few look-ups left, the sized reads first, then the proposal). A routed turn still does the request it was given: one sentence in every marked file, keyed on the answer's plugin.skill. The scheduled run's closing question asks about the change itself, never a change that hangs on a check. And the daily-habit offer also stops on routine.own_schedule saved or unavailable, which the set-up now records, because from 0.2.7 the server leaves its own schedule offer to the plugin on a turn a skill owns.
0.2.7's gate on 2026-10-08 is GREEN on its second run; the first run was red. The second run is the whole suite, once and not spliced, on commit 3bd07bd (the fix round below; nothing under skills/, commands/, agents/ or the manifest has changed since), under claudeVersion 2.1.293, judge sonnet: 21 of 21 cases passed at 0.8, overall 0.942, $18.88, 1,406s. The tools-only ablation is green too, all seven skills ok: review +0.500, docs +0.444, seo +0.347 (min +0.111), daily +0.275, measure +0.274 (min +0.190), launch +0.267 (min +0.167), connect +0.189 (min +0.078). Its with-skills arm is that suite run, passed in as ABLATION_WITH_JSON. Its tools-only arm was run fresh on the same commit, build and judge. The first run's arm could not be reused, because three skill bodies and commands/audit.md had changed since, and the copy keeps commands/ whole, so splicing it in would not be the honest splice described above. ABLATION_MAX_COST_USD=20 let the arm finish all 21 cases in one run, unspliced: $16.98, 793s. No case is red. Five sit within 0.05 of the line, and each is named here so that a drop on the next build is read against it. measure-teaches scored 0.810, with teaches-two-steps failing on two runs of three where the first run had one. setup-duplicate-server scored 0.825: setups-job-and-one-next-step failed on all five runs, against one of five in the first run. The tools-only arm fails that rubric on all five runs in both ablations, and the only change that reaches the case is adcopilot-connect's step 2; this is a lead, not yet diagnosed. setup-signed-out scored 0.829, with sign-in-once-and-stop failing on three runs of five. It is a guard, and its with-skills score sits below its tools-only 1.000 (-0.171), as it did in the first run (0.886 against 0.943). launch-unserved-campaign scored 0.833 and connect-unknown-product 0.843; the latter's failing runs are the address item described in the judge replay below. audit-stale-tools scored 1.000 on both arms, so it is now a guard; /adcopilot:audit is in both arms. 0.2.6 scored 0.714 on it under this build, and that result stands, so the fix ships with this release. seo-two-numbers-that-lie fell from 1.000 to 0.889 with its skill unchanged: one run left the marker off a call and missed one rubric. That is the size of the run-to-run noise at three runs.
The first run, at a2e4fd8 under the same build and judge, passed 17 of 21 cases, overall 0.927, $19.20, 1,482s. Four cases failed their llm rubric on all three runs: audit-stale-tools 0.714, connect-unknown-product 0.765, measure-import-trap 0.786 and launch-unserved-campaign 0.792. Its ablation failed adcopilot-connect and adcopilot-measure on their minimum (+0.000 each), because both arms failed the same rubric there. Its tools-only arm hit the default $15 ceiling after 18 cases, and the remaining three, all unowned, were spliced in at $17.72 in all. Re-run on main (0.2.6) under 2.1.293 for $3.05, audit-stale-tools scored 0.714 there too. So the branch did not cause that one, and the replay files keyed on the bare marker did not either. The other three passed on main: connect-unknown-product 0.843, launch-unserved-campaign 0.917 and measure-import-trap 0.929. That pointed at the branch, which the fix round below confirmed case by case.
The fix round, the same day, build and judge. Each red case was diagnosed from its saved replies before any text changed, then re-run alone with the suite's command and --case (3 runs): measure-import-trap 1.000 (0.833 on the first pass), connect-unknown-product 0.922, launch-unserved-campaign 0.917, audit-stale-tools 0.905, $3.83 for the five runs. Every fix is skill or command text, one commit each, and no rubric changed. adcopilot-measure: the replies pasted the audit's rows as a table, which the rubric fails as the server's rows; step 6 now names the Primary actions in a sentence, and step 1 reads the container back when the customer says a tag is published (the first pass skipped it once, as a 2.1.288 run had). adcopilot-connect: a table, or a suggested order naming Tag Manager but not Lantern, failed; step 2 now writes sentences and names every product in a state alike. adcopilot-launch: the fixture's September dates against an October clock, argued before the finding; step 8 now says the finding first and the date gap once, after the rule-outs. /adcopilot:audit: see the judge replay below. Against the tools-only arm, which a body-only change cannot move, measure-import-trap is +0.214 and connect-unknown-product +0.157. Read those as indicative, not as the gate: the connect, measure and launch changes also reach the other cases those skills own, which were not re-run, so splicing these five runs into the gate's arm is not the honest splice described above. The whole-suite run and fresh ablation that settle it are recorded above.
Replaying the judge, rather than guessing at it. The judge answers one word and stores no reasoning, so which rubric item failed can only be inferred, or measured. A throwaway probe plugin whose every case asks the agent (--model haiku) to repeat one saved reply verbatim, graded by the red case's own criterion copied unchanged, puts that reply in front of the suite's judge: same build, same sonnet alias, same prompt, and a regex grader confirms the echo is exact. Removing one passage at a time and re-judging shows which one tips the verdict; the four cases took about $7.45. What it showed. audit-stale-tools is not the judge or the build's grading: the two 2026-10-07 replies that passed under 2.1.292 pass 9 of 9 votes under 2.1.293, and the failing 2026-10-08 replies fail 9 of 9, so the replies changed. On this branch the trigger is the reconnect written as a proposal with cost lines, which commit 4a9d67e asks for: dropping only those two bullets takes a branch reply from 0 of 9 votes to 6 of 6. On both arms it is newness argued without the audit's findings: dropping only "September was the campaign's first full month (it was switched on 23 August)." takes a main reply from 0 of 9 to 6 of 6. The judge sits near its line on these replies, so /adcopilot:audit now moves them away from it rather than chasing one sentence: a missing tool halves the audit, the pacing is reported for its dates, newness waits for the run with findings, and the reconnect is a step, not a proposal. connect-unknown-product's item 4 grades an address the judge cannot check: it asks whether a connect address is the server's, and the judge is never shown the server's answer. With only the Search Console address removed, one failing branch reply goes from 0 of 6 votes to 6 of 6 and another from 1 of 3 to 6 of 6; with the criterion stating the fixture's https://app.example.invalid/connect/<slug> addresses, those two score 3 of 6 and 6 of 6, while the same reply carrying a remembered Google address still fails 6 of 6. The no-remembered-addresses grader already checks addresses against the fixture's domain. The rubric is left as it is, because the case passes on the skill text; if it drops again, that sentence is the change to make, with this evidence.
Reading a --tag- or --case-filtered ablation: every skill with no case in the run prints FAIL: no gated case and the script exits non-zero on them. That is the filter, not a regression. Only the rows for skills whose cases actually ran mean anything.
0.2.8 (written 2026-10-10) follows the server's related_reads. It is written against AdCopilot v2.34.19, deployed 2026-10-09 21:04 UTC, which is live, so the release order holds: the server that emits related_reads is out before the plugin that follows it. What changed: every skill, command and agent that reads the account now does what the server's own instruction says about a result that ends with related_reads — run the ones that bear on the question, at most three, then answer from all you hold; never repeat a read you hold; offer, do not chase, the related_reads those reads bring back; reads only, so a change is still only proposed — and adcopilot-daily's unattended run skips them. Where a flow wrote a search by hand for what a lens already returns, it now uses the lens: adcopilot-review names the lens for each kind of narrow question and reads change_history when the numbers moved; the audit command and the auditor agent are told the same; adcopilot-launch reads the first week's search terms with analyze_search_terms. The raw search stays where no lens covers it: the two weeks by day, spend and results per keyword, disapproved ads, a campaign's impression share, and the daily run's change-event read, which the existing daily cases measure. /adcopilot:weekly now reads change_history for who changed what, and the ad and campaign status for what Google switched off itself, because the change log does not hold a disapproval. It also follows related_reads on those reads, on purpose, although the server's own weekly summary carries no related_reads rail because its reads are sized in advance: the command is run by the customer, not on a schedule, and the read cap it already states still holds. adcopilot-review and the auditor mention question_map, the table get_org_context returns for an account with campaigns to read, and fall back to their own list when it is absent. The auditor's read-only tool list gains explain_conversion_gap, a read that conversion_setup_audit can name. One new case, review-follows-related-reads (owned by adcopilot-review), has the waste lens name two reads, one that bears on the question and one that does not, and grades that the first is run, the second is not, the answer uses both results and nothing is applied without a yes; its day_of_week schema is the live description and signature from the server code, without a Tools revision line, which could not be read without a signed-in connection. 0.2.8's gate on 2026-10-10 is GREEN, from a run that is spliced, and here is exactly how. The suite, under claudeVersion 2.1.296, judge sonnet, on commit a20a389, stopped at its --max-cost-usd 20 ceiling after 20 of the 22 cases ($20.30, 1,020s; partialReason cost_ceiling): twenty-two cases now cost a little more than twenty did. The two it did not reach, setup-signed-out and unrelated-request-stays-quiet, were run on their own on the same commit, build and judge, and scored 1.000 each on every run ($0.77 and $0.19). An earlier attempt the same morning is void: the account's spend limit cut runs off mid-case (the runs read "You've hit your individual spend limit"), which scored skills that had worked as if they had not, and none of its numbers are used here. All 22 cases pass at 0.8, overall 0.946. The five lowest are measure-teaches 0.810, setup-duplicate-server 0.812, review-follows-related-reads 0.852 (the new case), habit-offer-untold 0.857 and connect-teaches 0.867; each is named so that a drop on the next build is read against it. The tools-only ablation is green, all seven skills ok, its with-skills arm being that suite run (passed in as ABLATION_WITH_JSON) and its tools-only arm run fresh on the same commit, build and judge ($19.20): seo +0.324 (min +0.148), launch +0.318 (min +0.292), review +0.282 (min +0.148), connect +0.241 (min +0.222), measure +0.226 (min +0.214), daily +0.216, docs +0.444. audit-stale-tools and connect-no-analytics-account are guards on this build (the tools-only arm passes them at 0.8). The two cases run on their own have no tools-only arm in that ablation; neither is owned by a skill's gate.
Releasing
First, the order. The AdCopilot server customers reach must already read the versioned marker, source: "claude-plugin/<version>", before a version that sends it is published; an older server ignores it and stops recording plugin use. The bump moves .claude-plugin/plugin.json and every copy of the marker together — the structural tests in tests/ fail on a copy left behind. The server hands the counted account's review to adcopilot-review only from 0.2.7 on, so that skill's text is what a customer on such a version gets in place of the server's own steps for that situation.
The profile to run them in. Use a Claude Code profile with no hand-added adcopilot server; the default profile usually qualifies, and /mcp lists what a profile has.
- Start outside this repository. Inside it, the repo's own
.mcp.jsonis read a second time as a project-scoped server, which is the duplicateevals/setup-duplicate-serveris about. - Never point
CLAUDE_CONFIG_DIRat a temporary directory, such as$(mktemp -d). Claude Code writes the lock for each MCP token refresh under its config directory. A session that rotates AdCopilot's refresh token and cannot save the new one sends the spent one again. AdCopilot treats a reused refresh token as stolen and signs that connection out.- On 2026-10-07, a test session started with a temporary profile lost its sign-in. Its client log shows
Token refresh error: ENOENT: no such file or directory, mkdirandToken persist failed: ENOENT. The log does not record which directory failed, so the temporary profile is the likely cause, not a proven one.
- On 2026-10-07, a test session started with a temporary profile lost its sign-in. Its client log shows
- If you need a separate profile, give it a directory that persists:
mkdir -p "$HOME/.claude-adcopilot-release" && CLAUDE_CONFIG_DIR="$HOME/.claude-adcopilot-release" claude.
Before a version tag is pushed, in that profile:
/plugin marketplace add AtromxIntelligence/adcopilot-claude-plugin/plugin install adcopilot@adcopilot-claude-plugin/mcp— chooseadcopilotand sign in with the Google account that owns the ad account./adcopilot:setupon a real account: it reports what is connected, in prose, and one next step./adcopilot:auditon the same account: the full audit and this month's pacing, with the brand-new-account findings kept separate.The scheduled run, on the same account: say "schedule it" after the offer
/adcopilot:dailymakes, follow the steps it gives for the surface you are on, and Run now once — the run finishes without stopping to ask, calls no tool that changes anything in Google Ads, Analytics, Tag Manager or Search Console, and nosave_org_contexteither (a schedule set up before 0.2.14 keeps that tool asking first, so a note would stop it), and the next/adcopilot:dailymakes no offer once AdCopilot reports the run. If a scheduled run of the plugin's own skill does not finish unattended on a surface, the pasted instructions alone are what the steps give there.claude plugin validate . --strictandclaude plugin validate .claude-plugin/plugin.json --strictboth pass; the suite passes at 0.8; the tools-only ablation is green — every skill's delta over the connector plus its own frontmatter description (the tools-only arm keeps the frontmatter so the skill still fires), on its mean and on its weakest case among the cases that baseline does not already carry, is at least 0.05 (ABLATION_FAIL_BELOW=0.05 evals/ablation/tools-only.sh). A case the tools-only arm passes at 0.8 is a regression guard on the connector's own behaviour: it must still pass at 0.8 with the skill, and it is left out of the skill's delta. A red ablation blocks the tag: the skill it names is fixed or deleted first.Record the AdCopilot version and
tools_revisionthe steps above ran against, in the release notes and in the section above — and theclaudeVersionthe suite ran under, whichevals/results/<run>/aggregate-result.jsonalready stores. A score is only comparable to another score from the same build: on 2026-09-23 under2.1.280these cases scoredaudit-stale-tools1.00,connect-teaches1.00,measure-teaches1.00 andsetup-duplicate-server1.00; on 2026-10-02 under2.1.288, with the plugin byte-identical (plugins[0].version0.1.0 in both) and the same judge alias, they scored 0.67, 0.83, 0.67 and 0.77. Nothing in the plugin had changed. So a case that drops is a question — plugin, harness, or thesonnetalias resolving to a new snapshot — and not yet an answer: diff the two runs'claudeVersionbefore editing a skill, or you will fit the prompts to one build and refit them on the next.0.2.5's own run, for the next comparison: under
2.1.289on 2026-10-06, judgesonnet, all 20 cases passed at the 0.8 threshold, overall 0.939, $18.18, 1,191s. The three cases new at that point scored 1.00 on every run; the fourth,seo-discovered-is-not-a-verdict, was written after the ablation and is recorded with it. The four lowest wereaudit-stale-tools0.81,habit-offer-code0.83,connect-unknown-product0.84 andconnect-teaches0.87 — the same cases that moved between2.1.280and2.1.288, andaudit-stale-toolsis still the weakest of them on a build three releases later.0.2.7's own run, for the next comparison: under
2.1.293on 2026-10-08, judgesonnet, all 21 cases passed at the 0.8 threshold, overall 0.942, $18.88, 1,406s (evals/results/2026-10-08T09-57-47-060Z, gitignored). The five lowest weremeasure-teaches0.81,setup-duplicate-server0.83,setup-signed-out0.83,launch-unserved-campaign0.83 andconnect-unknown-product0.84.audit-stale-toolsis not among them for the first time: it scored 1.00.
Licence
MIT. Copyright Atromx Intelligence Private Limited. See LICENSE.
What CI runs, and what it does not
Two workflows run on every push to main and every pull request, and neither
reads a secret.
.github/workflows/checks.yml is the structural half of the release gates. It
installs Claude Code from npm at a pinned version (2.1.288) and runs both
--strict validations, claude plugin validate . --strict and
claude plugin validate .claude-plugin/plugin.json --strict, which read local
files and need no sign-in. It also checks that the three manifests parse, that
every skill and command carries a name: and a description:, that the
structural tests in tests/ pass (the daily-habit block identical in every
flow that carries it, the unattended run's instructions read-only and its
first call marked claude-scheduled, the instructions it hands over equal to
AdCopilot's recipe, the plugin marker carrying the manifest's version in every
file, and the eval graders able to see the offer and the marker they grade),
that no download-and-execute pattern is back anywhere in the repository, and
that the manifest version is not behind the latest tag.
.github/workflows/leak-check.yml runs gitleaks over every commit. It runs
gitleaks from its published container image, pinned by digest, and fails when
the scan finds a possible credential or reads no commits at all. GitHub's own
secret scanning and push protection are also switched on for this repository,
so a push carrying a secret type GitHub recognises is blocked unless the pusher
deliberately bypasses the block.
What no workflow runs is the eval suite and the tools-only ablation. Both drive
a model, which needs a signed-in Claude Code, and these jobs hold no
credential. The person cutting the release runs them, which means they can be
skipped, so the checklist above, not a green tick, is what stands between a
broken skill and a tag: no version tag without both --strict validations,
the suite at 0.8, and a green tools-only ablation.
The first workflow here ran all of it, the eval gate and the ablation included.
It was removed on 2026-09-26 for the plugin directory submission: its only way
to install Claude Code on a runner was a piped shell installer, and the
directory's scanner holds a download-and-execute pattern anywhere in the
repository for review, whoever published the script being fetched. Installing
from npm at a pinned version is not that pattern, which is why checks.yml
could come back.