Ask a model for five hero image concepts for a supplement and you will get five hero image concepts. They will be plausible. They will be written confidently. And if you run the same prompt again tomorrow you will get five different ones, also plausible, also confident, with no way to say which set was better.
That is the actual problem with using AI for creative direction, and it took me a while to name it. The output isn't bad. The output is unreviewable. A generated idea has no provenance โ you can't ask where it came from, you can't ask what it's known to be bad for, and you can't check whether the model considered and rejected something better. You're not reviewing work. You're reacting to a draft that arrived from nowhere.
I run a 9-person creative team across multiple client accounts. Unreviewable is expensive at that size. Two strategists producing two different concept sets for the same ASIN isn't a debate you can settle, and "whichever one the person in the room liked" is how a quarter of creative roadmap gets decided.
So I built the opposite: a skill where the model isn't allowed to invent a concept at all. It has to pick one from a fixed list of 52 and tell me why.
The problem, priced
The manual version of this job is a strategist afternoon per SKU. Open the listing, look at the current hero, look at 3-6 real competitors in the actual search grid, work out what the image is failing to communicate, come up with a spread of concepts that aren't all the same idea in different clothes, write the brief, then write a test plan that will tell you which one won.
Call it three to four hours of senior time to go from a listing URL to something a client can approve and a designer can build. That's fine once. It's not fine across a catalog, which is why most brands do this properly for the hero SKU and never again for anything else.
The tempting fix is to hand the whole thing to a model. The reason that fails isn't quality โ models write decent creative rationale. It's that you've replaced a reviewable process with an unreviewable one, and on a hero image the output eventually becomes a live asset on a page that people buy things from.
The build
The skill is productpinion-main-image-lab, living in ~/.codex/skills/, with a slash command wrapper in my iCloud-synced ~/.claude/commands/ so it fires the same way from any machine.
The command file is 452 words and it does almost nothing interesting. It parses the arguments, and then โ this is the part that matters โ it names the three reference files by absolute path and tells the model to read them before it thinks:
2. Read the ProductPinion Main Image Lab references:
- references/main-image-tactics.json
- references/tactic-selection-rules.md
- references/prompt-schema.md
3. Select 3 to 5 tactics that genuinely fit.
Note the verb. Select. Not generate, not brainstorm, not suggest. The entire design decision is in that word, and everything underneath it exists to make selection possible.
Here's what's under it:
main-image-tactics.json โ 52 tactics, 21 fields each, about 211KB. This is the ProductPinion main-image tactic set normalized into structured data. Names like Megalabel It, Humanize It, Smear It, Dead Pixel Space It, Expand Explode It, Swatch It, X-Ray It. Each record carries tactic_category, best_for, avoid_for, core_strategy, visual_transformation, required_user_inputs, negative_prompt, a full standalone_json_prompt, a customer_facing_prompt, a test hypothesis, and what to measure.
Same data also sits as .jsonl (52 lines, one per tactic, for cheap line-oriented reads) and as a CSV for the Airtable version. Three formats, one source.
tactic-selection-rules.md โ how to choose. Strong matches by product format ("wearables/body-contact items: Humanize It, Clone It, Swatch It, Fold It"). Poor fits stated as prohibitions. And a portfolio rule, which is the sneaky-important part:
For a typical product, select 5 tactics:
1. One low-risk visual pattern interrupt.
2. One use-case clarifier.
3. One value/benefit callout.
4. One product-transparency tactic.
5. One bolder test variant.
Without that rule a model returns five tactics that are all the same bet. It finds a direction it likes and produces variations on it, because variations score well against "are these relevant." The portfolio rule forces a spread, which is what you actually need if the next step is a test rather than an approval.
prompt-schema.md โ the output shape. Every tactic emits the same API-agnostic JSON object: task, tactic, input_image, objective, required_user_inputs, edit_instructions, avoid. Plus a customer-facing wrapper so a client can paste it into whatever image tool they already use.
scripts/validate_tactics.py โ and this is the piece I'd steal if I were reading someone else's build log.
You can't unit-test a prompt. You can unit-test a library.
Every AI guardrail I've written before this one has been a sentence. "Do not invent certifications." "Leave it blank rather than guess." Sentences work, but you can't run them. You find out whether a sentence held by reading the output and forming an impression, and an impression is not a test.
A tactic library is different, because it's data. So it gets a validator:
REQUIRED_FIELDS = {
"tactic_id", "database_order", "guide_heading_number",
"tactic_name", "tactic_category", "best_for",
"core_strategy", "standalone_json_prompt", "customer_facing_prompt",
}
It walks all 52 records, checks the nine required fields are present, checks tactic_id is unique, and then does the thing I care most about: it takes the standalone_json_prompt โ which is stored as a string inside each record โ runs json.loads on it, and asserts that the parsed object has task == "edit_product_main_image" and contains an edit_instructions block.
That means a malformed prompt inside a tactic record fails on my machine, deterministically, before anything reaches a client deliverable. I ran it again this morning writing this post:
$ python3 scripts/validate_tactics.py
Validated 52 ProductPinion tactics.
Exit 0. That's not a vibe. That's a check I can run twice and get the same answer, which is the entire difference between a creative system and a creative mood.
The field that makes the library defensible
If I had to point at one design decision that made this work, it isn't best_for. It's avoid_for, populated on all 52 records โ I checked, it's 52 out of 52, no blanks.
best_for is easy to write and close to useless on its own. Every tactic sounds good when you only describe what it's good for. A model handed 52 tactics with only positive framing will find a way to justify any of them for any product, because the justification is always available.
avoid_for gives the model something to lose. Megalabel It says avoid for "single-item products or premium products where a large label would cheapen the image." The selection rules escalate the ones with real downside into flat prohibitions:
- Don't use
Feature It,America It,TikTok It, orLifetime Warranty Itunless the seller can substantiate the claim. - Don't use
Multiply It,Box It,Case It, orMegalabel Itif it could confuse pack quantity. - Don't use
Humanize Itif the product isn't used on or near a person.
That second one is not a design rule. It's a returns rule wearing a creative costume. An image that makes a 1-pack read as a 4-pack will win the click and then eat the return, and a return costs you the contribution plus fees in both directions plus frequently the unit โ and then it feeds a rate that hangs a badge on you. I'd rather the library refuse the tactic than have me catch it in review on a Thursday evening.
The part that makes it a client workflow instead of a prompt library
A concept with no decision rule attached is an opinion, and opinions get settled by seniority. So the skill doesn't stop at concepts. Every tactic record carries a productpinion_test_hypothesis and a what_to_measure field โ for Megalabel It, that's "CTR preference, perceived value, message recall, confusion about quantity," which is the tactic naming its own failure mode as something to watch for.
And the skill hard-codes the test sequence:
- Search Simulation diagnostic against 3-6 real competitors.
- Generate 3-5 meaningfully different hero iterations.
- Validate the strongest challenger against the original with an Image Split Test โ or a Ranked Test when there are three-plus real concepts and ordering matters.
- Re-run Search Simulation with the winner to confirm it improves click preference in the competitive shelf context.
Step four is the one people skip and it's the one that matters. A challenger can beat your original head-to-head and still lose in the grid, because head-to-head is a comparison the shopper never actually runs. The shelf is the exam.
What I'd flag honestly
I don't have a stack of run logs for this skill and I'm not going to write a failure narrative I can't date. What I can tell you is what's fossilized in the file, because guardrails are written after something goes wrong, and three of these are clearly scar tissue:
"Do not force five tactics if fewer truly apply." A model handed a quota fills the quota. Five is a default, not a requirement, and without that line you get three good tactics and two written to make the list look complete.
"Do not treat the most visually exciting concept as the automatic winner." The model optimizes for interesting. The search grid optimizes for legible at 280 pixels. Those are different objectives and they diverge more often than anyone wants to admit.
The fidelity rule. If the generator changes the logo, label hierarchy, pack count, or product shape, the skill requires the output to be called out and treated as concept art needing production cleanup. That's the line Aspi runs on generally โ AI generates direction, humans execute finals โ but it needed to be written into the file, because a generated image that quietly redrew a label looks completely fine until someone reads it at zoom.
And one honest flag about my own data: there is an expected_ctr_impact field in every record, and the values are High / Medium / Low. That is an authored prioritization field. It is not measured data. It's there so the selector can break ties, and it's the kind of field that turns into a fake statistic the moment someone quotes it in a deck. If you build something like this, label your authored fields as authored, out loud, in the file โ otherwise your own reference data eventually gets cited back to you as a result.
Cost, and the number I won't publish
The workflow was an evening. The corpus was the work โ normalizing 52 tactics into 21 consistent fields, writing avoid_for for every single one, and getting all 52 embedded JSON prompts to parse. That's a merchandising exercise typed into a JSON file, not a coding exercise.
Runtime cost is a few dollars per SKU. There's no cron, no daemon, no unattended write path to any listing. A human invokes it, a human approves the concepts, and humans build the production assets.
I'm not going to give you an hours-saved-per-month figure across a 9-person team, because I'd be making it up and it's exactly the kind of number I'd call out in someone else's post. The real compression is narrow and describable: listing context to a diagnosed, spread, testable concept brief with a measurement plan attached goes from an afternoon to something closer to a coffee. Everything downstream of that โ production, retouching, QA โ takes exactly as long as it always did.
The number that justifies it sits on the other side of the ledger anyway. One hero image rolled out across a catalog on the strength of a concept nobody could reproduce, defend, or test costs more than every hour this thing has ever saved.
What an operator could replicate this week
You don't need my tactic set. You need the shape.
- Write down the options before you ask the model to choose. If you can't enumerate the choices, you're asking for invention, and invention isn't reviewable. This is true for hero images, ad angles, A+ module layouts, and bullet structures.
- Populate the negative field. For every option, write what it's bad for. A library that only knows what things are good for will approve anything.
- Add a portfolio rule so the model returns a spread instead of five versions of its favorite idea.
- Make the output machine-checkable, then actually check it. JSON you can parse is a contract. Prose is a hope. Write the twelve-line validator and run it.
- Ship the decision rule with the concept. What you'll measure, against what, and what result would kill it. Concepts without decision rules become opinion contests, and opinion contests are won by whoever is most senior in the room.
- Label the fields you made up. Your own reference data will get quoted as evidence eventually. Decide now whether it deserves to be.
The pattern underneath all six: the model's job should be choosing and justifying, not producing from nothing. Choosing is reviewable. Justifying is checkable against a rule you wrote down. Producing from nothing is a slot machine with good prose.