From 6942d18d53b11bb4e71a06576fd079bd30f0a09d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 08:10:05 +0000 Subject: [PATCH] fix(skill): repair the agent-facing structure of SKILL.md, add a doc gate + CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The doctrine was fine; the wrapper around it was not. Fixed - SKILL.md cited design-rules.md §10.5, §12 and §9 for the production modes, the QA rule list and the slop check. That file stops at §9, and §9 is the platform-dimensions table — so the mandatory QA step in the workflow pointed at nothing. Now §4, §8 and §6, each quoted by title so a renumber is visible. - The QA step ran `python scripts/qa.py`, which resolves against the user's project once the skill is installed to ~/.claude/skills/. It now resolves the skill's own directory and names the dependency install. - `visual-advertising-engine.md §25` is a rule ID: cited as `R25`. Changed - "Load first (in order)" pulled the engine, the charter and the chat inject (~730 lines) before the brief was taken. Replaced with a load-when table: SKILL.md runs the brief, everything else opens at the step that needs it. core.md is marked as the paste-in for loader-less chat hosts and taken off the agent's path. - The table routes to all seven references; five of them previously appeared only inside the repo-structure tree, with no cue for when to open them. - Frontmatter is a valid Agent Skills header — version/author/url moved under `metadata`; the description leads with what the skill does and its trigger. - The 17 quick rules carry their canonical R-IDs, so summary and standard diff. Added - scripts/check_docs.py: dead links, §-pointers to missing sections, undefined rule IDs, invalid frontmatter, version drift. Against the previous commit it reports 8 problems, including every pointer fixed here. - .github/workflows/ci.yml running check_docs.py + test_qa.py. test_qa.py has shipped since 5.4.0 with nothing running it. - requirements.txt (pillow, numpy) and .claude-plugin/plugin.json. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011vYy2C92BdEun9DxDbWQ63 --- .claude-plugin/plugin.json | 9 ++ .github/workflows/ci.yml | 21 +++++ CHANGELOG.md | 23 +++++ CONTRIBUTING.md | 5 +- INSTALL.md | 7 +- README.en.md | 8 +- README.md | 8 +- SKILL.md | 81 ++++++++++-------- requirements.txt | 2 + scripts/check_docs.py | 167 +++++++++++++++++++++++++++++++++++++ 10 files changed, 290 insertions(+), 41 deletions(-) create mode 100644 .claude-plugin/plugin.json create mode 100644 .github/workflows/ci.yml create mode 100644 requirements.txt create mode 100644 scripts/check_docs.py diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..9e62e7e --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,9 @@ +{ + "name": "meta-ads-designer", + "description": "Design and generate posters, flyers, Meta/social ads and promo graphics that look art-directed instead of AI-generated.", + "version": "5.5.0", + "author": { "name": "AI Evolution Labs", "url": "https://github.com/aievolutionpl" }, + "homepage": "https://github.com/aievolutionpl/meta-ads-designer", + "license": "MIT", + "keywords": ["design", "advertising", "meta-ads", "image-generation", "marketing"] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e5c2460 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + +jobs: + checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install dependencies + run: pip install -r requirements.txt + - name: Docs — links, section pointers, rule IDs, frontmatter, version + run: python scripts/check_docs.py + - name: QA gate self-test + run: python scripts/test_qa.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9691a7f..a1aad03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ All notable changes to Meta Ads Designer. Versions follow [SemVer](https://semve --- +## [5.5.0] — 2026-08-19 + +A structure pass over the skill itself. The rules were fine; the wrapper around them sent the agent to sections that don't exist, to a script path that doesn't resolve, and through ~730 lines of doctrine before it had heard the brief. + +### Fixed +- **`SKILL.md` pointed at three sections that were never there.** `design-rules.md §10.5` (two production modes) and `§12` (the QA rule list) do not exist — that file stops at §9 — and the slop check was cited as `§9`, which is the platform-dimensions table. An agent following step 3.6 or the mandatory QA step in 5 found nothing. They now resolve to §4, §8 and §6, each quoted by title so a renumber is visible rather than silent. +- **The QA step told the agent to run `python scripts/qa.py`.** Installed to `~/.claude/skills/meta-ads-designer`, that path resolves against the user's project, where it does not exist. The step now resolves the skill's own directory first and names the dependency install. +- **`visual-advertising-engine.md §25` is a rule ID, not a section number** — cited as `R25` now, like everywhere else. + +### Changed +- **`SKILL.md` no longer front-loads the doctrine.** "Load first (in order)" asked for the engine, the charter and the inject — about 730 lines — before the brief was even taken. It is now a load-when table: this file runs the brief, everything else opens at the step that needs it. `core.md` is marked as what it is, the paste-in for chat hosts with no skill loader, and taken off the agent's path. +- **The load table routes to all seven references.** `layout-system.md`, `headline-system.md`, `qa-gate.md`, `anti-slop-registry.md` and `niche-playbooks.md` previously appeared only inside the repo-structure tree, with no cue for when to open them. +- **Frontmatter is a valid Agent Skills header.** `version`, `author` and `url` are not spec keys and a strict loader rejects them; they moved under `metadata`. The description now leads with what the skill does and states its trigger, instead of opening on "Universal plugin that teaches agents…". +- **The 17 quick rules carry their canonical IDs** (`R02`, `R03`, …). The summary and the standard can now be diffed instead of trusted. + +### Added +- **`scripts/check_docs.py`** — dead relative links, `§`-pointers to sections that don't exist, rule IDs no rule defines, an invalid `SKILL.md` frontmatter, and version drift between the frontmatter, the README badges and this file. Run against the previous commit it reports 8 problems, including every pointer fixed above. +- **`.github/workflows/ci.yml`** — `check_docs.py` and `test_qa.py` on every push and PR. `test_qa.py` has been in the repo since 5.4.0 with nothing running it. +- **`requirements.txt`** — `pillow`, `numpy`. `INSTALL.md` asked for a bare `pip install pillow numpy` with no pinned floor. +- **`.claude-plugin/plugin.json`** — installable through a Claude Code marketplace, not only by `cp -r`. + +--- + ## [5.4.0] — 2026-08-19 An audit pass over the whole skill. The gate was making ship/no-ship calls it could not actually support, and the docs had drifted from the code and from each other. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 863586d..d6f5c3f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,8 +28,9 @@ A rule is useful if it would **prevent a real rejection**. Anchor it in a concre 2. Edit the canonical file (`visual-advertising-engine.md`). Give a new rule the next free ID; never renumber an existing one — deprecate it and add a new ID. 3. Update the summary in `core.md` / `design-rules.md` only if the rule is headline-grade. 4. Keep README (PL + EN) in sync if it lists rules. -5. If you touched `scripts/`, run `python scripts/test_qa.py` and add a case for the behaviour you changed. -6. Open a PR with a one-line "why": the real rejection this rule would have caught. +5. Run `python scripts/check_docs.py` — it fails on dead links, `§`-pointers to sections that don't exist, cited rule IDs the engine never defines, an invalid `SKILL.md` frontmatter, and a version that drifted between the frontmatter, the README badges and this changelog. +6. If you touched `scripts/`, run `python scripts/test_qa.py` and add a case for the behaviour you changed. +7. Open a PR with a one-line "why": the real rejection this rule would have caught. ## Style - **English is canonical for the rules.** `visual-advertising-engine.md`, `design-rules.md`, `core.md` and `references/` are English; `README.md` is the Polish manual and `README.en.md` the English one. There is no EN mirror of the engine — the duplicate was removed in 5.0.0 because the two copies had drifted. diff --git a/INSTALL.md b/INSTALL.md index 69e8f6e..38f5ebc 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -71,13 +71,16 @@ Inject **`core.md`** into your system prompt (fully self-contained), or load `SK The QA gate's deterministic layer and the wordmark extractor need two common packages: ```bash -pip install pillow numpy +pip install -r requirements.txt python scripts/qa.py out/*.png --format 4:5 --text-box 86,900,994,1264 python scripts/extract_wordmark.py refs/logo.png build/logo_white.png python scripts/test_qa.py # verifies the gate itself, 13 synthetic cases +python scripts/check_docs.py # verifies the docs: links, section pointers, rule IDs, versions ``` +Run these from the skill's own directory (`~/.claude/skills/meta-ads-designer`, or wherever you cloned it) — the paths above are relative to it, not to the project you are designing for. + Always pass `--text-box` on a creative that carries copy (and `--logo-box` when you place a logo). Without them the safe-area, contrast, thumbnail and scrim checks have nothing to measure, report `n/a`, and the PASS is only partial — the script warns you when this happens. `qa.py` exits non-zero when any image fails, so it drops into CI or a pre-delivery hook. Everything else in the repo is plain Markdown with no dependencies. @@ -108,5 +111,5 @@ Ask the agent: *"What is the specificity test, and what's the default margin on | `references/niche-playbooks.md` | 15 per-industry playbooks (What works / Avoid / Headline / CTA) | | `references/prompt-library.md` | Prompt skeletons | | `references/anti-slop-registry.md` | Full banned-pattern list + grep gate | -| `scripts/` | `qa.py` (QA gate), `test_qa.py` (its self-test), `extract_wordmark.py` | +| `scripts/` | `qa.py` (QA gate), `test_qa.py` (its self-test), `check_docs.py` (doc integrity), `extract_wordmark.py` | | `CONTRIBUTING.md` | How to add a rule without forking the doctrine | diff --git a/README.en.md b/README.en.md index 70f7b68..531256e 100644 --- a/README.en.md +++ b/README.en.md @@ -8,7 +8,7 @@ [🇵🇱 Polski](README.md) · [🇬🇧 English](README.en.md) -![Version](https://img.shields.io/badge/version-5.4.0-6a5acd) +![Version](https://img.shields.io/badge/version-5.5.0-6a5acd) ![License](https://img.shields.io/badge/license-MIT-brightgreen) ![Format](https://img.shields.io/badge/default_format-4:5%20(1080×1350)-informational) ![Hosts](https://img.shields.io/badge/runs_on-ChatGPT%20%7C%20Codex%20%7C%20Hermes%20%7C%20Claude%20%7C%20Cursor-blue) @@ -206,9 +206,13 @@ meta-ads-designer/ ├── README.en.md # This manual (EN, extra) ├── CHANGELOG.md # Version history ├── LICENSE # MIT +├── CONTRIBUTING.md # How to add a rule (rule-ID policy, no-duplication rule) +├── requirements.txt # pillow + numpy — dependencies for scripts/ +├── .claude-plugin/plugin.json # Plugin manifest (install via a Claude Code marketplace) +├── .github/workflows/ci.yml # CI: check_docs.py + test_qa.py on every push ├── assets/meta-ads-designer-banner.png ├── examples/ # Worked ad examples (anti, restaurant, hotel, services, retail) -├── scripts/ # qa.py, test_qa.py, extract_wordmark.py +├── scripts/ # qa.py, test_qa.py, check_docs.py, extract_wordmark.py └── references/ ├── hospitality-food-services-playbook.md # Depth: food / hotel / services ├── layout-system.md # Layout + panel heights + gradient values diff --git a/README.md b/README.md index 4fb7e5e..9097095 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [🇬🇧 English](README.en.md) · [🇵🇱 Polski](README.md) -![Version](https://img.shields.io/badge/version-5.4.0-6a5acd) +![Version](https://img.shields.io/badge/version-5.5.0-6a5acd) ![License](https://img.shields.io/badge/license-MIT-brightgreen) ![Format](https://img.shields.io/badge/default_format-4:5%20(1080×1350)-informational) ![Hosts](https://img.shields.io/badge/runs_on-ChatGPT%20%7C%20Codex%20%7C%20Hermes%20%7C%20Claude%20%7C%20Cursor-blue) @@ -206,9 +206,13 @@ meta-ads-designer/ ├── README.en.md # Ten manual (EN, extra) ├── CHANGELOG.md # Historia wersji ├── LICENSE # MIT +├── CONTRIBUTING.md # Jak dodać zasadę (polityka ID, zasada braku duplikacji) +├── requirements.txt # pillow + numpy — zależności scripts/ +├── .claude-plugin/plugin.json # Manifest pluginu (instalacja przez marketplace Claude Code) +├── .github/workflows/ci.yml # CI: check_docs.py + test_qa.py przy każdym pushu ├── assets/meta-ads-designer-banner.png ├── examples/ # Gotowe przykłady adów (anti, restauracja, hotel, serwisy, retail) -├── scripts/ # qa.py, test_qa.py, extract_wordmark.py +├── scripts/ # qa.py, test_qa.py, check_docs.py, extract_wordmark.py └── references/ ├── hospitality-food-services-playbook.md # Głębia: food / hotel / serwisy ├── layout-system.md # Layout + panel-heights + gradient values diff --git a/SKILL.md b/SKILL.md index 20c67d9..034ca8f 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,10 +1,11 @@ --- name: meta-ads-designer -description: Universal plugin that teaches agents how to design beautiful posters, flyers, meta ads and promo graphics — and generate them without AI-slop. Framework-agnostic: works on Hermes, Claude Code, Codex, Cursor, ChatGPT and any agent. Load when the user asks for promotional images for a business/restaurant/hotel/local brand, especially when they upload logo, service or food reference photos. -version: 5.4.0 +description: Designs and generates posters, flyers, Meta/social ads and promo graphics that look art-directed instead of AI-generated - hierarchy, real typography, real light, one message per creative. Use when the user asks for an ad, poster, flyer, promo or social graphic for a business, restaurant, hotel or local brand, especially when a logo, venue, product or food photo is attached; also for product photography, lifestyle and e-commerce visuals, and image-editing prompts. Framework-agnostic - runs on Hermes, Claude Code, Codex, Cursor and ChatGPT. license: MIT -author: AI Evolution Labs -url: https://github.com/aievolutionpl/meta-ads-designer +metadata: + version: 5.5.0 + author: AI Evolution Labs + url: https://github.com/aievolutionpl/meta-ads-designer --- # 🎨 Meta Ads Designer @@ -17,36 +18,47 @@ This is a **universal plugin** that runs on any AI agent. It teaches **what beau --- -## ⚡ Load first (in order) +## ⚡ How to load this skill -1. **`visual-advertising-engine.md`** — the 34-rule operating standard (Product First, Reference = Source of Truth, Prompt Architecture, Hard Fails, QA). **Read this before any commercial visual.** -2. **`design-rules.md`** — the canonical charter of beautiful advertising (the readable summary of the engine). -3. **`core.md`** — only when you need one self-contained page to hand to a chat host or paste into another agent's system prompt. It restates the doctrine; the engine outranks it. -4. Then follow the workflow below. -5. For host setup (Hermes / Claude / Codex / Cursor / ChatGPT): `INSTALL.md`. -6. For prompts and niche depth: `references/`. +**Read this file first.** It carries the intake, the routing, the workflow and the QA gate — enough to run a brief end to end. Pull anything else in **only at the step that needs it**. All paths are relative to this skill's own directory (call it `SKILL_DIR`), not to the user's working directory. + +| Open | At which point | +|------|----------------| +| `visual-advertising-engine.md` | **Before writing any prompt for a commercial visual.** The 34-rule operating standard, `R01`–`R34` — every other file cites these IDs. | +| `design-rules.md` | You want the doctrine in prose, or the index of which file answers which question. | +| `references/layout-system.md` | Placing anything: grid, margins, panel heights, type scale, palettes. | +| `references/headline-system.md` | Writing the copy inside the ad: archetypes, character budgets, diacritics, CTAs. | +| `references/prompt-library.md` | Filling the 5-slot prompt, or choosing a model. | +| `references/hospitality-food-services-playbook.md` | The brief is food, restaurant, hotel, venue or a local service. | +| `references/niche-playbooks.md` | Any other industry — 15 playbooks. | +| `references/anti-slop-registry.md` | An output looks generic and you need the named pattern and the grep gate. | +| `references/qa-gate.md` | Step 5 — the scored rubric behind the script. | +| `examples/` | You want a finished brief → prompt → verdict before writing your own. | +| `INSTALL.md` | Host setup, or the user asks how to install this. | + +**Skip `core.md`.** It is the self-contained inject for chat hosts that have no skill loader (paste into ChatGPT/Gemini custom instructions). If you are reading `SKILL.md` you can reach the engine directly, and the engine outranks it. --- ## 🎯 Core rules (non-negotiable) -1. **Product First** — the product is the main character: visible, large, lit, sharper than surroundings, attractive angle. Never hide it in a big set. -2. **Reference = Source of Truth** — a supplied product photo is a technical document. NEVER change shape/proportions/color/construction/material/logo/lettering/mechanism. Only environment, light, frame, perspective, styling. Respect the product's physics. -3. **Commercial realism** — professional commercial photography, not "obvious AI ad". Correct perspective, scale, gravity, shadows, real materials. -4. **One creative = one idea** — one message, one focal point. Don't cram product + 7 benefits + promo + reviews. -5. **Hierarchy** — PRIMARY (product) → SECONDARY (context) → TERTIARY (subtle atmosphere). -6. **Negative space** — don't fill the frame. Space = premium + room for the headline. -7. **Lighting is part of the product** — say exactly what the light does (clean commercial / premium dramatic / natural lifestyle / food commercial). -8. **Think like a photographer** — decide camera position, angle, lens, depth of field, foreground/midground/background. -9. **Build depth** — foreground → subject → background. No flat images. -10. **Show product in use** — packshot alone isn't enough; a hand/gesture/POV gives context. -11. **Typography after the image** — strong photo first, then headline → support → CTA. Not a dashboard. -12. **Don't generate important text in-image** — if the model is weak at text, generate a clean visual and add real typography + the real logo later. -13. **Mobile-first composition — DEFAULT is 4:5 (1080×1350)**, the Instagram/Facebook feed default; 9:16 for Reels/Stories, 1:1 marketplace, 16:9 — only when the user asks. Compose for the format; don't rely on cropping. -14. **Series consistency** — product identical across 5–10 images; only context/frame/mood/light change. Like one shoot. -15. **Variation, not randomness** — hero · lifestyle · feature · close-up · problem · result · premium · UGC · unexpected angle. -16. **Food builds appetite** — texture, steam, gloss, juiciness, layers; Frozen-Time/Bullet-Time for dynamic scenes. Physically credible. -17. **Anti-slop** — no random neon, HUD, icons, gradients, arrows, fake logos, excessive bokeh, plastic surfaces. Every element has a function. +1. **Product First** — the product is the main character: visible, large, lit, sharper than surroundings, attractive angle. Never hide it in a big set. `R02` +2. **Reference = Source of Truth** — a supplied product photo is a technical document. NEVER change shape/proportions/color/construction/material/logo/lettering/mechanism. Only environment, light, frame, perspective, styling. Respect the product's physics. `R03` +3. **Commercial realism** — professional commercial photography, not "obvious AI ad". Correct perspective, scale, gravity, shadows, real materials. `R04` +4. **One creative = one idea** — one message, one focal point. Don't cram product + 7 benefits + promo + reviews. `R06` +5. **Hierarchy** — PRIMARY (product) → SECONDARY (context) → TERTIARY (subtle atmosphere). `R07` +6. **Negative space** — don't fill the frame. Space = premium + room for the headline. `R08` +7. **Lighting is part of the product** — say exactly what the light does (clean commercial / premium dramatic / natural lifestyle / food commercial). `R09` +8. **Think like a photographer** — decide camera position, angle, lens, depth of field, foreground/midground/background. `R10` +9. **Build depth** — foreground → subject → background. No flat images. `R11` +10. **Show product in use** — packshot alone isn't enough; a hand/gesture/POV gives context. `R12` +11. **Typography after the image** — strong photo first, then headline → support → CTA. Not a dashboard. `R17` +12. **Don't generate important text in-image** — if the model is weak at text, generate a clean visual and add real typography + the real logo later. `R18` +13. **Mobile-first composition — DEFAULT is 4:5 (1080×1350)**, the Instagram/Facebook feed default; 9:16 for Reels/Stories, 1:1 marketplace, 16:9 — only when the user asks. Compose for the format; don't rely on cropping. `R19` +14. **Series consistency** — product identical across 5–10 images; only context/frame/mood/light change. Like one shoot. `R20` +15. **Variation, not randomness** — hero · lifestyle · feature · close-up · problem · result · premium · UGC · unexpected angle. `R21` +16. **Food builds appetite** — texture, steam, gloss, juiciness, layers; Frozen-Time/Bullet-Time for dynamic scenes. Physically credible. `R15` +17. **Anti-slop** — no random neon, HUD, icons, gradients, arrows, fake logos, excessive bokeh, plastic surfaces. Every element has a function. `R05` **Final principle: DON'T DECORATE. DIRECT.** One product. One idea. One strong visual. @@ -68,10 +80,10 @@ Define **5–10 distinct promises and layouts**, not 10 color swaps. Examples: h ### 3.5 · Creative generation (before writing any prompt) Do **not** jump to the prompt. Run the engine's creative workflow first: -1. **Identify the product.** 2. **Identify the most important benefit.** 3. **Define the target.** 4. **Choose the marketing angle** (Problem / Effect / Lifestyle). 5. **Invent a simple visual metaphor or situation.** 6. **Choose the creative type** (from the library: hero, packshot, lifestyle, product-in-use, macro, problem/solution, result, UGC, editorial, scroll-stopper). 7. **Design the composition.** 8. **Define light and camera.** 9. **Add constraints.** 10. **Only then write the final prompt** using the 11-part architecture in `visual-advertising-engine.md` §25. +1. **Identify the product.** 2. **Identify the most important benefit.** 3. **Define the target.** 4. **Choose the marketing angle** (Problem / Effect / Lifestyle). 5. **Invent a simple visual metaphor or situation.** 6. **Choose the creative type** (from the library: hero, packshot, lifestyle, product-in-use, macro, problem/solution, result, UGC, editorial, scroll-stopper). 7. **Design the composition.** 8. **Define light and camera.** 9. **Add constraints.** 10. **Only then write the final prompt** using the 11-part architecture in `visual-advertising-engine.md` `R25`. ### 3.6 · Route by brief type -- **Food / restaurant:** two modes (see `design-rules.md` §10.5). If the client has real dish photos → **real-food hero** (photo top ~60–65% + solid panel bottom ~35–40%, zero text on food). If not → **dark studio editorial**. **Native AI text in-scene is the default** (keep strings SHORT: brand + headline + 1 location line; append `CRITICAL: every word spelled PERFECTLY`). Depth: `references/hospitality-food-services-playbook.md`. +- **Food / restaurant:** two modes (see `design-rules.md` §4 "The two production modes"). If the client has real dish photos → **real-food hero** (photo top ~60–65% + solid panel bottom ~35–40%, zero text on food). If not → **dark studio editorial**. **Native AI text in-scene is the default** (keep strings SHORT: brand + headline + 1 location line; append `CRITICAL: every word spelled PERFECTLY`). Depth: `references/hospitality-food-services-playbook.md`. - **Hotel / venue:** prefer **real-photo + deterministic typography/logo** over AI re-generation of the building. Design system: serif headline + clean sans body, coastal palette (navy/teal/cream/white/gold), real photo hero + content card. Produce structurally different styles (heritage poster · travel cover · swiss grid · terrace · dining · direct-booking · events · seaside · offer · brand story). - **Services / local biz:** real product/install photos as refs → generate NEW premium scenes (never overlay on the client's raw photo). Angles: Problem→Effect · package tiers · deadline offers · transformation · benefit-led headline ≤40 chars. Use **deterministic composition** when text/logo fidelity matters. @@ -83,9 +95,9 @@ Do **not** jump to the prompt. Run the engine's creative workflow first: - Model choice is host-specific — see `references/prompt-library.md` and `INSTALL.md`. Rule of thumb: a model that renders text well for in-scene headlines; a clean-photo pipeline for everything else. ### 5 · QA gate (mandatory) -1. Run `python scripts/qa.py --format 4:5 --text-box x0,y0,x1,y1` (add `--logo-box` when a logo is placed). **Declare the boxes** — without them the safe-area, contrast, thumbnail and scrim checks report `n/a` and the PASS means only "right dimensions, no collage". Fix edge intrusions with **scale+pad**, never a crop. +1. Run `python "$SKILL_DIR/scripts/qa.py" --format 4:5 --text-box x0,y0,x1,y1` (add `--logo-box` when a logo is placed). `SKILL_DIR` is the directory this file sits in — resolve it before you call the script; a bare `scripts/qa.py` resolves against the user's project, where it does not exist. Needs `pillow` and `numpy` (`pip install -r "$SKILL_DIR/requirements.txt"`). **Declare the boxes** — without them the safe-area, contrast, thumbnail and scrim checks report `n/a` and the PASS means only "right dimensions, no collage". Fix edge intrusions with **scale+pad**, never a crop. 2. Build a **contact sheet** (exclude prior contact sheets from the glob). -3. Inspect each ad against **every rule in `design-rules.md` §12** — thumbnail readability, spelling (incl. Polish diacritics), hierarchy, accent ≤3, logo fidelity, no fake footers, no text-on-photo slop, no AI-invented food, ad spine present, contrast. +3. Inspect each ad against **every rule in `design-rules.md` §8 "The QA gate"** — thumbnail readability, spelling (incl. Polish diacritics), hierarchy, accent ≤3, logo fidelity, no fake footers, no text-on-photo slop, no AI-invented food, ad spine present, contrast. 4. Fix minor issues deterministically (clean typography pass); regenerate when the visual is fundamentally wrong. ### 6 · Delivery @@ -96,7 +108,7 @@ Do **not** jump to the prompt. Run the engine's creative workflow first: ## 🚫 Quick slop check (before ANY output) -From `design-rules.md` §9 — if any of these is present, fix it: +From `design-rules.md` §6 "Quick slop check" — if any of these is present, fix it: purple/blue default gradient · glassmorphism · neon glow · gradient text · tiny clip-art icons · text slapped on a photo · cream/sand bg · over-round cards · cards-in-cards · icons > content · gray-on-tinted text · isometric default · AI-invented food · AI-redrawn logo · a pretty photo with no ad structure. --- @@ -130,6 +142,9 @@ meta-ads-designer/ ├── CHANGELOG.md # Version history ├── LICENSE # MIT ├── CONTRIBUTING.md # How to add a rule (rule-ID policy, no-duplication rule) +├── requirements.txt # pillow + numpy, for scripts/ +├── .claude-plugin/ # plugin.json — install via a Claude Code marketplace +├── .github/workflows/ci.yml # runs check_docs.py + test_qa.py on every push ├── examples/ # Worked ad examples (food, hotel, services, retail) ├── assets/ # Banner and generated hero images for the README ├── scripts/ # qa.py, test_qa.py (its self-test), extract_wordmark.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..540c684 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pillow>=10.0 +numpy>=1.24 diff --git a/scripts/check_docs.py b/scripts/check_docs.py new file mode 100644 index 0000000..b437855 --- /dev/null +++ b/scripts/check_docs.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Structural check for the skill's own docs. + +Every failure this catches has already happened in this repo at least once: +a pointer to `design-rules.md` §12 when that file stops at §9, a file table +listing a reference that was renamed, a version badge that drifted from the +frontmatter. Markdown has no compiler, so this is it. + +Checks: + 1. relative links (.md/.py/.png/.html) resolve from the linking file + 2. `file.md` §N pointers resolve to a real `## N ·` heading in that file + 3. cited rule IDs (R01-R34) exist in visual-advertising-engine.md + 4. SKILL.md frontmatter is a valid Agent Skill header + 5. the version in SKILL.md, the README badges and CHANGELOG agree + +Usage: python scripts/check_docs.py [--root .] exits non-zero on any failure. +""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +LINK = re.compile(r"\[[^\]]*\]\(([A-Za-z0-9_./-]+\.(?:md|py|png|html))\)") +SECTION = re.compile(r"`([A-Za-z0-9_./-]+\.md)`\s*§([0-9]+(?:\.[0-9]+)?)") +HEADING = re.compile(r"^##\s+([0-9]+(?:\.[0-9]+)?)\s*·", re.M) +RULE_CITE = re.compile(r"`(R[0-9]{2})`") +RULE_DEF = re.compile(r"^##\s+(R[0-9]{2})\s*·", re.M) +NAME = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$") + +# Agent Skills frontmatter: anything outside this set belongs under `metadata`. +ALLOWED_KEYS = {"name", "description", "license", "allowed-tools", "metadata"} +MAX_DESCRIPTION = 1024 + + +def md_files(root: Path) -> list[Path]: + return sorted(p for p in root.rglob("*.md") if ".git" not in p.parts) + + +def check_links(root: Path, fails: list[str]) -> None: + for f in md_files(root): + for target in LINK.findall(f.read_text(encoding="utf-8")): + if not (f.parent / target).exists(): + fails.append(f"{f.relative_to(root)}: dead link -> {target}") + + +def check_sections(root: Path, fails: list[str]) -> None: + headings: dict[str, set[str]] = {} + for f in md_files(root): + headings[f.name] = set(HEADING.findall(f.read_text(encoding="utf-8"))) + for f in md_files(root): + for target, section in SECTION.findall(f.read_text(encoding="utf-8")): + name = Path(target).name + if name not in headings: + fails.append(f"{f.relative_to(root)}: §-pointer to unknown file {target}") + elif section not in headings[name]: + have = ", ".join(sorted(headings[name], key=float)) or "none" + fails.append( + f"{f.relative_to(root)}: {target} §{section} does not exist (has: {have})" + ) + + +def check_rules(root: Path, fails: list[str]) -> None: + engine = root / "visual-advertising-engine.md" + if not engine.exists(): + fails.append("visual-advertising-engine.md is missing") + return + defined = set(RULE_DEF.findall(engine.read_text(encoding="utf-8"))) + for f in md_files(root): + for rule in RULE_CITE.findall(f.read_text(encoding="utf-8")): + if rule not in defined: + fails.append(f"{f.relative_to(root)}: cites {rule}, not defined in the engine") + + +def frontmatter(skill: Path) -> dict[str, str]: + text = skill.read_text(encoding="utf-8") + if not text.startswith("---\n"): + return {} + block = text.split("\n---\n", 1)[0][4:] + out, indented = {}, False + for line in block.split("\n"): + if not line.strip(): + continue + if line.startswith((" ", "\t")): # nested under the previous key + indented = True + continue + indented = False + key, _, value = line.partition(":") + out[key.strip()] = value.strip() + del indented + return out + + +def check_frontmatter(root: Path, fails: list[str]) -> dict[str, str]: + skill = root / "SKILL.md" + fm = frontmatter(skill) + if not fm: + fails.append("SKILL.md: no YAML frontmatter") + return {} + for key in ("name", "description"): + if key not in fm: + fails.append(f"SKILL.md: frontmatter is missing required `{key}`") + for key in fm: + if key not in ALLOWED_KEYS: + fails.append( + f"SKILL.md: `{key}` is not an Agent Skills frontmatter key — move it under `metadata`" + ) + name = fm.get("name", "") + if name and not NAME.match(name): + fails.append(f"SKILL.md: name `{name}` must be lowercase letters, digits and hyphens") + if name and name != root.name and root.name != ".": + fails.append(f"SKILL.md: name `{name}` should match the skill directory `{root.name}`") + description = fm.get("description", "") + if len(description) > MAX_DESCRIPTION: + fails.append(f"SKILL.md: description is {len(description)} chars, max {MAX_DESCRIPTION}") + if description and " when " not in description.lower(): + fails.append("SKILL.md: description states no trigger — say when to use the skill") + return fm + + +def check_version(root: Path, fm: dict[str, str], fails: list[str]) -> None: + text = (root / "SKILL.md").read_text(encoding="utf-8") + match = re.search(r"^\s+version:\s*([0-9]+\.[0-9]+\.[0-9]+)\s*$", text, re.M) + if not match: + fails.append("SKILL.md: no `version:` under `metadata`") + return + version = match.group(1) + changelog = root / "CHANGELOG.md" + if changelog.exists(): + latest = re.search(r"^## \[([0-9]+\.[0-9]+\.[0-9]+)\]", changelog.read_text(encoding="utf-8"), re.M) + if latest and latest.group(1) != version: + fails.append(f"CHANGELOG.md tops out at {latest.group(1)}, SKILL.md says {version}") + for readme in ("README.md", "README.en.md"): + path = root / readme + if not path.exists(): + continue + badge = re.search(r"badge/version-([0-9]+\.[0-9]+\.[0-9]+)", path.read_text(encoding="utf-8")) + if badge and badge.group(1) != version: + fails.append(f"{readme} badge says {badge.group(1)}, SKILL.md says {version}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", default=".", type=Path) + args = parser.parse_args() + root = args.root.resolve() + + fails: list[str] = [] + check_links(root, fails) + check_sections(root, fails) + check_rules(root, fails) + fm = check_frontmatter(root, fails) + check_version(root, fm, fails) + + if fails: + print(f"FAIL — {len(fails)} problem(s):\n") + for line in fails: + print(f" · {line}") + return 1 + print(f"PASS — {len(md_files(root))} markdown files: links, §-pointers, rule IDs, frontmatter, version") + return 0 + + +if __name__ == "__main__": + sys.exit(main())