name: recompose-creative
version: 1.4.0
type: producer
isolation_scope: brand_only
layer: production
recommended_model: opus
subagent_safe: true
mode: proposed
operator_facing: true
description: >
v1.4.0 (Brique 4 étape C · repath batch + split genome/creative/sidecar + mkdir-claim + variant_axis collapse) · recompose lit le creative SOURCE sous la forme batch creatives/{batch}/{CRT-NN}/creative.json (+ genome.json frère pour l'ADN), id source GLOBAL au namespace CRT scanné cross-batch, et PRODUIT une variante = un NOUVEAU CRT-NN dans le batch DU JOUR (split genome.json + creative.json + produced/{slug}.json sidecar + brief.md, dossier réservé par mkdir-claim atomique STEP 0). variant_axis collapse sur l'enum canonique creative.schema (5+null, single). variant_of = le CRT-NN source (namespace CRT, jamais RCV). _schema_version creative/1.4 + genome/1.2. Backward compat · forme cible imposée par le gate.
v1.2.2 (v2.61 doctrine consume) · consumes: enrichi avec refs docs/doctrine/ NEW v2.60 (angle-anatomy, hooks-method). Skill peut désormais consume ces doctrines canon copywriting/strategy pour informer production sans dépendre schemas exacts.
v1.0.0 (v2.34 ship production loop) : 3e skill P5 visual aux côtés de decompose-ad
(reverse) et compose-creative (forward). Adapte une créa interne testée (ou
hypothesis) en variante alignée sur UNE dimension changée : audience, plateforme,
format, hook, traitement visuel. Output : nouveau CRT-NN qui réutilise concept_id
parent + flag variant_of source + variant_axis canonique. Préserve concept core
(mécanique, ton, intent_mix) ; ne change qu'une dimension à la fois. Persiste
(Brique 4 étape C · split) sous brands/{slug}/creatives/{batch}/{CRT-NN}/ avec
meta.validation_status = hypothesis (tag derived_from_test_results si la source a
un score). Pipeline visuel mirror compose-creative (HR3) avec prompt adapté
preservation-first. Output operator-facing : vue compacte side-by-side source vs
variant + brief markdown explicit "WHAT CHANGED vs SOURCE".
FR: "adapte cette créa", "version pour autre audience", "déclinaison",
"variant créa", "variante TikTok", "version story", "décline cette créa".
EN: "adapt this creative", "version for another audience", "variant",
"tiktok version", "story version", "decline this creative".
disambiguates_against:
compose-creative: "compose-creative crée ex nihilo depuis brand intelligence + angle. recompose-creative ADAPTE un creative existant (1 dimension change, concept core préservé)."
decompose-ad: "decompose-ad analyse une ad EXTERNE (reverse-engineering benchmark concurrent). recompose-creative adapte une créa INTERNE (mode internal_production, source CRT-NN déjà persisté sous creatives/{batch}/{CRT-NN}/)."
produce-paid-angles: "produce-paid-angles produit un nouveau ANGLE (forward generation depuis brand intelligence). recompose-creative garde l'angle, change autre dimension (audience/platform/format/hook/visual)."
weave-hooks: "weave-hooks incarne des concepts NEUFS en scripts (amont) · recompose-creative décline une créa EXISTANTE en variantes (aval)"
consumes:
- path: brands/{slug}/creatives/{batch}/{CRT-NN}/creative.json min_version: 1.3.0 note: "creative source obligatoire (couche lignage). L'id CRT-NN est GLOBAL au namespace CRT (batch-indépendant) → scanner TOUS les dossiers {batch}/ pour le localiser. concept_id réutilisé pour la variante."
- path: brands/{slug}/creatives/{batch}/{CRT-NN}/genome.json min_version: 1.0.0 note: "ADN frère du creative source (mécanique/format/support/hook/frames/genome_tags). Lu pour préserver le concept core en regen. {CRT-NN} localisé par le même scan cross-batch."
- path: brands/{slug}/audiences/{audience_slug}/profile.json min_version: 1.0.0 note: requis si l'axe variante = persona_split (recalibrage audience cible)
- path: brands/{slug}/products/{product_slug}/spec.json#visual_identity min_version: 1.10.0 note: packshots clean URL + distinctive features pour préserver fidélité produit en regen
- path: resources/schemas/creative.schema.json min_version: 1.3.0 note: "couche lignage (Brique 4 étape C · creative/1.4 · variant_of CRT|RCV + lineage{} + variant_axis enum canonique SSOT)"
- path: resources/schemas/genome.schema.json min_version: 1.0.0 note: "NEW Brique 4 étape C · ADN par-créa (genome/1.2 · script_id GSC-NN, hook, frames, support, genome_tags) hérité/adapté de la source"
- path: resources/schemas/produced-asset.schema.json min_version: 1.0.0 note: "NEW Brique 4 étape C · sidecar manifest binaire (produced-asset/1.0 · 1 par .jpg produit, anti-orphelin)"
- path: resources/conventions/creative-storage.md min_version: 1.0.0 note: "forme batch creatives/{batch}/{CRT-NN}/ + allocation mkdir-claim (D#481)"
- path: resources/canon/copy/formats-livrables/*.json min_version: 1.0.0 note: requis si l'axe variante touche plateforme/format (lookup contraintes ratio/durée/ton, mappé sur background_swap pour l'axe canonique)
- path: docs/doctrine/angle-anatomy-doctrine.md
- path: docs/doctrine/hooks-method-doctrine.md produces_proposals_for:
- brands/{slug}/creatives/{batch}/{CRT-NN}/genome.json
- brands/{slug}/creatives/{batch}/{CRT-NN}/creative.json
- brands/{slug}/creatives/{batch}/{CRT-NN}/produced/{slug}.json
- brands/{slug}/creatives/{batch}/{CRT-NN}/produced/{slug}.jpg
- brands/{slug}/creatives/{batch}/{CRT-NN}/brief.md prerequisites:
- field: creatives/{batch}/{CRT-NN}/creative.json level: L1 auto_pull: read_creative_source freshness_ttl_days: 60 note: "source localisée par scan cross-batch (id CRT-NN global). genome.json frère lu pour l'ADN."
- field: variant_axis
level: L2
options:
- photo_swap
- promo_toggle
- hook_swap
- background_swap
- persona_split
- field: resources/canon/copy/formats-livrables
level: L1
auto_pull: read_canon_directory
freshness_ttl_days: 365
permissions:
reads: [brand, product, profile, learning, creative, canon_copy, registries]
writes: [creative]
emits_events: [creative_recomposed, variant_generated]
mode: proposed
subagent_safe: true
external_apis:
- provider: "fal.ai"
endpoint: "fal-ai/nano-banana-2/edit"
model_family: "gemini_3_pro_image_novembre_2025"
version_check_url: "https://fal.ai/models?keywords=banana"
version_canon_date: "2025-11"
replaced_legacy: "fal-ai/nano-banana-pro/edit (v1.0.x · Gemini 2.5 Flash Image)"
auto_upgrade: false
pipeline:
preconditions: |
creative source CRT-NN existe sous brands/{slug}/creatives/{batch}/{CRT-NN}/creative.json (namespace CRT, batch dirs).
L'id CRT-NN source est GLOBAL au namespace CRT → résolu par scan cross-batch de tous les dossiers {batch}/.
genome.json frère disponible pour l'ADN à préserver.
1 dimension change explicite (variant_axis ∈ enum canonique [photo_swap, promo_toggle, hook_swap, background_swap, persona_split]).
Credentials FAL_API_KEY présent dans credentials_shared.env pour pipeline visuel.
Si variant_axis = persona_split, audience cible profile.json existe.
Si l'axe touche plateforme/format (résolu vers background_swap), canon formats-livrables disponible.
{batch} du jour résolu (run date-stampé, ex 2026-06-07-NN) et
mkdir -p brands/{slug}/creatives/{batch}/exécuté AVANT le mkdir-claim, sinon STEP 0 n'a pas de dossier-batch parent. postconditions: | Nouvelle CRT-NN (réservée par mkdir-claim atomique STEP 0) persistée dans le batch DU JOUR sous brands/{slug}/creatives/{batch}/{CRT-NN}/ via le split : genome.json (genome/1.2 · ADN hérité/adapté de la source) + creative.json (creative/1.4 · variant_of=CRT-source, variant_axis canonique, lineage{}) + sidecar produced/{slug}.json (produced-asset/1.0) + brief.md. JPG écrit DIRECT sous {CRT-NN}/produced/{slug}.jpg AVEC son sidecar le traçant (anti-orphelin). brief.md écrit DIRECT. concept_id réutilisé de la source (SSOT du groupage de variantes). variant namespace reste CRT (jamais RCV). meta.validation_status = {status: hypothesis, confidence: 0.5, confidence_source: derived_from_test_results} si la source a un score, sinon derived_from_hypothesis. tags.source = "internal_production". rebuild snapshot via build-brand-snapshot.py {slug} si un core file est touché. validate-resources triggered silencieusement.
- provider: "fal.ai"
endpoint: "fal-ai/nano-banana-2/edit"
model_family: "gemini_3_pro_image_novembre_2025"
version_check_url: "https://fal.ai/models?keywords=banana"
version_canon_date: "2025-11"
replaced_legacy: "fal-ai/nano-banana-pro/edit (v1.0.x · Gemini 2.5 Flash Image)"
auto_upgrade: false
pipeline:
preconditions: |
creative source CRT-NN existe sous brands/{slug}/creatives/{batch}/{CRT-NN}/creative.json (namespace CRT, batch dirs).
L'id CRT-NN source est GLOBAL au namespace CRT → résolu par scan cross-batch de tous les dossiers {batch}/.
genome.json frère disponible pour l'ADN à préserver.
1 dimension change explicite (variant_axis ∈ enum canonique [photo_swap, promo_toggle, hook_swap, background_swap, persona_split]).
Credentials FAL_API_KEY présent dans credentials_shared.env pour pipeline visuel.
Si variant_axis = persona_split, audience cible profile.json existe.
Si l'axe touche plateforme/format (résolu vers background_swap), canon formats-livrables disponible.
{batch} du jour résolu (run date-stampé, ex 2026-06-07-NN) et
Skill: recompose-creative
Adapt an existing internal creative. v1.4.0 · Brique 4 étape C · split genome/1.2 + creative/1.4 + produced-asset/1.0 · forme batch
creatives/{batch}/{CRT-NN}/· variant_axis enum canonique · 3e skill P5 visual.
Adaptateur, not generator. Lit un creative interne déjà persisté sous la forme batch creatives/{batch}/{CRT-NN}/ (id CRT-NN source GLOBAL au namespace CRT, localisé par scan cross-batch), change UNE dimension explicitement déclarée par l'opérateur, persiste (Brique 4 étape C · split) un NOUVEAU CRT-NN dans le batch DU JOUR · genome.json (ADN) + creative.json (lignage · variant_of + variant_axis canonique) + sidecar produced/{slug}.json + brief.md. Le dossier {CRT-NN}/ est réservé par mkdir-claim atomique AVANT tout write. Concept core (mécanique, ton, intent_mix) toujours préservé. Rend une vue side-by-side source vs variant à l'opérateur en langage clair. Pour refonte 2+ dimensions ou nouveau concept, route vers compose-creative.
Tone
Posture collègue senior media buying qui décline une créa testée. Pas inventeur. La fiche est dense mais lisible : ce qui reste vs ce qui change, justification courte de l'adaptation, comparaison side-by-side. Aucun JSON brut, aucun field_path interne. Si une dimension n'est pas explicitement déclarée, AskUserQuestion pour la fixer avant tout pipeline visuel.
Step 0bis · Prerequisite check (DRGFP v2.38)
Avant adaptation (Step 1), scanner prerequisites :
- L1 silent ·
creatives/{batch}/{CRT-NN}/creative.jsonsource (required · id CRT-NN résolu par scan cross-batch de tous les dossiers{batch}/) +genome.jsonfrère (ADN) ·resources/canon/copy/formats-livrables - L2 gate ·
variant_axisnon spécifié → AskUserQuestion menu 5 options plain-language (HR1). Le choix opérateur résout déterministiquement vers UNE valeur de l'enum canonique creative.schema.
Output state map + confidence_chain[] init avec variant_axis selected.
Hard Rules
HR1 · Identify variant axis explicit (menu plain-language → enum canonique)
Skill exige opérateur précise QUELLE dimension change. Surface au démarrage en langage clair, zéro enum technique entre parenthèses ·
Tu veux changer quoi sur cette créa ? Une seule chose à la fois ·
(a) la photo / le packshot (même hook, même scène, autre visuel produit)
(b) la promo affichée (toggle promo on/off, autre offre commerciale)
(c) le hook copy (variant A/B même visuel)
(d) la scène / le décor (autre background, même produit, même hook · couvre aussi plateforme/format)
(e) le persona ciblé (autre audience, même concept)
Si l'opérateur reste ambigu ou tente de changer 2+ dimensions, AskUserQuestion mandatoire. Rappel · 2+ dimensions change = route compose-creative (nouveau concept), pas recompose-creative.
Table menu → enum canonique (NON surface operator · déterministe, 1 choix = 1 valeur) · le choix opérateur (a)-(e) résout vers EXACTEMENT une valeur de l'enum canonique creative.schema.json#variant_axis = [photo_swap, promo_toggle, hook_swap, background_swap, persona_split, null]. Cet enum reste backstage pour retrieval programmatique · operator ne le voit jamais.
| Choix opérateur (plain-language) | variant_axis canonique (persisté) |
|---|---|
| (a) la photo / le packshot | photo_swap |
| (b) la promo affichée | promo_toggle |
| (c) le hook copy | hook_swap |
| (d) la scène / le décor (inclut plateforme/format) | background_swap |
| (e) le persona ciblé (autre audience) | persona_split |
Single axis (contrat recompose) · variant_axis reste un SEUL string (ou null), jamais un array. recompose change EXACTEMENT 1 axe par invocation. C'est son contrat et la SSOT du schéma (creative.schema.json#variant_axis est single-enum). Pour multi-axes, c'est un nouveau concept → compose-creative.
NOTE plateforme/format (axe (d) →
background_swap) · un changement de plateforme (Meta → TikTok) ou de format (image → carrousel) modifie principalement la scène/le décor et les contraintes natives ; il se résout sur l'axe canoniquebackground_swap. Les contraintes ratio/durée/ton restent lookées dansresources/canon/copy/formats-livrables/(HR3) et adaptées dans le genome (support,aspect_ratio,format), mais l'axe de lignage persisté estbackground_swap(l'enum schéma ne porte niplatform_swapniformat_swap).
HR2 · Load creative source + identify what stays vs changes
Localiser le creative source par scan cross-batch (l'id CRT-NN est GLOBAL au namespace CRT, batch-indépendant) ·
ls -d brands/{slug}/creatives/*/{CRT_source} 2>/dev/null | head -1
Lire brands/{slug}/creatives/{batch}/{CRT_source}/creative.json ET son genome.json frère du même dossier :
- Depuis
creative.json(lignage) · capturerconcept_idsource (sera réutilisé pour le variant, pas régénéré),lineage{}(refs),intent_mix,execution,tags,meta.validation_status+meta.test_results[](pour décider le statut de la variante en HR6). - Depuis
genome.json(ADN) · capturersupport,hook(mechanic_id + hook_text),frames[],genome_tags,lineageADN. C'est le concept core (mécanique, ton, intent_mix) à préserver.
Selon variant_axis (valeur canonique), marquer rigoureusement ce qui reste vs ce qui change :
| variant_axis (canonique) | Reste inchangé | Change |
|---|---|---|
photo_swap |
hook (genome), mécanique, format, scène, intent_mix | visuel produit / packshot (genome frames[].visual_script, produced/) |
promo_toggle |
concept core, hook, mécanique, scène | offre commerciale (modifiers.offer, overlay promo) |
hook_swap |
visual_script, mécanique, intent_mix | hook.hook_text (genome) · overlay verbal |
background_swap |
hook, mécanique, ton, produit (couvre plateforme/format) | scène/décor (genome frames[].visual_script) + contraintes natives (support, aspect_ratio) si plateforme/format change |
persona_split |
mécanique, format, visuel, hook | lineage.audience_ref, context.persona, registre copy recalibré |
Anti-pattern : retain 0 dimension du concept core = c'est compose-creative déguisé, refuser et re-router.
HR3 · Apply contraintes platform/format spécifiques
Si l'axe variante touche la plateforme ou le format (choix opérateur (d), résolu vers variant_axis: background_swap), lookup resources/canon/copy/formats-livrables/{platform}.json :
- TikTok : ratio 9:16, attention < 3 sec, native UGC plus fort, music context
- Instagram Story : ratio 9:16, swipe up CTA, sticker tap
- Instagram Reel : ratio 9:16, hook < 2 sec, trending audio, scroll-stop visuel
- LinkedIn : ratio 1.91:1 ou 1:1, ton professionnel, B2B context
- YouTube Shorts : ratio 9:16, hook < 5 sec, story arc compact
- Meta Feed : ratio 4:5 ou 1:1, hook scroll-stop visuel + verbal layered
- Pinterest : ratio 2:3 vertical, search-driven keywords visibles
Adapter les fields concernés dans le genome (support, aspect_ratio, frames[].visual_script, ton register). Si canon silencieux sur la plateforme demandée, surfacer à l'opérateur ("pas de canon formats-livrables pour {platform}, je peux improviser sur les contraintes natives connues ou tu valides un override ?"). L'axe de lignage persisté reste background_swap (l'enum schéma ne porte pas de valeur platform/format dédiée).
HR4 · Génération visuelle adaptée (preservation-first)
Pipeline mirror compose-creative HR3 (FAL nano-banana-2/edit canon v2.46), prompt ajusté pour préserver le concept core :
- Référence visuel original : image_urls inclut le binaire parent persisté
brands/{slug}/creatives/{batch}/{CRT_source}/produced/{slug}.jpg(le{CRT_source}localisé par scan cross-batch HR2 ;/tmp/compose-creative/reste cache éphémère acceptable si présent). - Référence packshot : image_urls inclut
spec.visual_identity.packshots.primary_frontclean URL - Prompt structure :
Adapt this creative for {new_dimension_value}, preserving the core concept ({mecanique} + {hook}), changing only {what_changes_explicit}. CRITICAL preservation : {concept_id_signature} (mécanique, ton, intent_mix). CRITICAL change : {variant_axis_payload}. Product fidelity : {distinctive_features from spec.visual_identity}. - Retry : max 2 si label produit régresse (audit S55 v2.34 sur endpoint legacy nano-banana-pro/edit · sans visual_identity le model régressait wordmark with brackets en variantes sans brackets ou caractères corrompus · pattern préservé avec nano-banana-2/edit mais marges plus généreuses sur text fidelity natif). Au 3e échec, surfacer à l'opérateur.
- Staging + persist : download dans
/tmp/compose-creative/{brand}-{crt_id}.jpg(staging éphémère), puis move final versbrands/{slug}/creatives/{batch}/{CRT-NN}/produced/{slug}.jpg(binaire écrit DIRECT · le dossier{CRT-NN}/produced/existe déjà, réservé par le mkdir-claim HR-RC-CANON-1 STEP 0) PUIS émettre le sidecarproduced/{slug}.json(produced-asset/1.0 ·asset_role,file,tool: "fal",endpoint,params,source_asset_ref: {kind: "parent_frame", path: <binaire parent>},status: "generated",created_at,genome_ref: <script_id GSC-NN>) viawrite_to_contextmode=proposed (anti BUG-ASSET-ORPHAN). Double régime · binaire .jpg direct, sidecar .json par le gate. Détail HR6 + HR-RC-CANON-1.
HR5 · Generate variant brief markdown
Format opérateur-facing avec section explicite "WHAT CHANGED vs SOURCE". Écrit DIRECT (nom fixe brief.md) sous brands/{slug}/creatives/{batch}/{CRT-NN}/brief.md (outil Write · .md exempt de ALLOWED_PATH_PATTERNS ET de mutation-guard, ne PAS router via write_to_context) ·
# CRT-NN · Variant of CRT-source
## Concept préservé
- concept_id : {cpt_id}
- mécanique : {mechanic}
- ton : {ton}
- intent_mix : {primary, secondary, weights}
## What stays
- [list per HR2 table]
## What changes
- {variant_axis} : {old_value} → {new_value}
- Justification : {why_this_change}
## Side-by-side
| Field | Source (CRT-source) | Variant (CRT-NN) |
|---|---|---|
| audience | ... | ... |
| platform | ... | ... |
| format | ... | ... |
| ratio | ... | ... |
## Test recommendation
{a/b vs source · 2 autres axes à explorer si performant · score-matrix}
HR6 · Persist split genome.json + creative.json + sidecar + brief.md (Brique 4 étape C)
Le mkdir-claim atomique (réservation du
{CRT-NN}/dans le batch DU JOUR) est exécuté en STEP 0 de HR-RC-CANON-1 (section ci-dessous), AVANT cette persist. HR6 écrit dans le dossier déjà réservé.
L'unique creative.json historique se SPLIT en trois fichiers .json (+ le brief .md), chacun conforme à son schéma. La variante hérite/adapte l'ADN de la source. NEVER Edit/Write direct sur un .json, ALWAYS via write_to_context mode=proposed.
genome.json (ADN · conforme genome.schema.json _schema_version: "genome/1.2", strictement nu) ·
script_id: nouveauGSC-NN(Genome SCript · clé de join perf future · PAS CRT-NN · pattern^GSC-[0-9]{2,4}$).support,route,hook,frames,genome_tags(dontgenome_tags.mecanique_id= le CONCEPT ad-level ETgenome_tags.angle_id= l'ANGLE [unité stratégique de 1ère classe · porte objection+OTRB+payoff] +audience_slug· hérités TEL QUEL sauf sivariant_axisles modifie · D#491/492) : hérités de la source PUIS la dimension changée parvariant_axisest appliquée (exhook_swap→ nouveauhook.hook_text;background_swap→ nouveauframes[].visual_script+support/aspect_ratioadaptés).lineage(optionnel ADN abstrait) :{concept_id (réutilisé source), angle_id (ANG-NN), persona_label}.
creative.json (lignage · conforme creative.schema.json _schema_version: "creative/1.4") ·
creative_id: nouveauCRT-NN(réservé par mkdir-claim STEP 0 · pattern^CRT-[0-9]{2,4}$).mode: hérité source (concept | template | asset).audience_slug(nouveau si persona_split)._equation_refconst v3.1.concept_id= source.concept_id (réutilisé, PAS régénéré · SSOT du groupage de variantes).variant_of= source.creative_id (leCRT-NNsource · pattern^(CRT|RCV)-[0-9]{2,4}$· pour recompose c'est TOUJOURS un CRT, jamais RCV). Vit danslineage.variant_of(+ top-levelvariant_ofmiroir backward-compat).variant_axis∈ enum canonique creative.schema[photo_swap, promo_toggle, hook_swap, background_swap, persona_split, null](single string · valeur résolue par la table HR1).lineage{}:{angle_ref (ANG-NN), audience_ref, product_ref, mechanism_ref, concept_ref (miroir descriptif de concept_id), variant_of (CRT-source), brief_ref (BRF-NN), ad_id}.intent_mixhérité source. Champ legacyintentmirror backward-compat.execution.overlay_density+execution.brand_mark_presenthérités source (sauf si changés par l'axe). Champ legacycraft_modemirror dérivé.meta.validation_status={status: "hypothesis", confidence: 0.5, confidence_source: "derived_from_test_results"}si la source CRT-NN a un score testé (meta.test_results[]non vide), sinonconfidence_source: "derived_from_hypothesis".meta.created.meta.created_by_skill: "recompose-creative".performance.longevity_signalvide (variante pas encore en prod).tags.source = "internal_production".- Tous les autres fields hérités de la source sauf ceux explicitement changés par variant_axis.
produced/{slug}.json (sidecar manifest binaire · conforme produced-asset.schema.json _schema_version: "produced-asset/1.0") · UN sidecar par binaire .jpg produit (anti-orphelin) ·
asset_role(ex"composite-final","variant-front"),file(ex"{slug}.jpg"),status: "generated",created_atrequis.tool: "fal",endpoint: "fal-ai/nano-banana-2/edit",params(prompt/aspect_ratio/resolution),source_asset_ref: {kind: "parent_frame", path: <binaire parent CRT-source>},hashoptionnel,qcoptionnel.genome_ref:<script_id GSC-NN>(relie le binaire à l'ADN sans le dupliquer).created_by_skill: "recompose-creative".
Mutation gate (séquence) :
- Dossier déjà réservé par le mkdir-claim atomique STEP 0 (HR-RC-CANON-1) ·
creatives/{batch}/{CRT-NN}/+produced/existent dans le batch DU JOUR. NE PAS s'appuyer sur lemkdir(exist_ok=True)du gate (non-atomique · 2 runs concurrents écraseraient le mêmeCRT-NN). - Write
genome.json+creative.json+ sidecarproduced/{slug}.jsonviawrite_to_context(field_path, value, source, confidence, mode="proposed")(les trois.json). NEVER edit JSON directly via Edit/Write/NotebookEdit. - Persist JPG : move
/tmp/compose-creative/{brand}-{crt_id}.jpg→brands/{slug}/creatives/{batch}/{CRT-NN}/produced/{slug}.jpg. Le binaire.jpg/.mp4est écrit DIRECT (write_to_contextne gère que le.json), MAIS son sidecar.json(step 2) passe par le gate. Double régime non-négociable. - Write brief markdown :
brands/{slug}/creatives/{batch}/{CRT-NN}/brief.md(nom fixebrief.md, format HR5). Écrit DIRECT via l'outil Write (.mdn'est NI dansALLOWED_PATH_PATTERNSdu gate write-to-context, NI dans mutation-guard qui ne garde que.json) · ne PAS router le.mdviawrite_to_context(échouerait). - Rebuild snapshot via
python3 .skills/build-brand-snapshot.py {slug}si un core file est touché (mutation rule · la variante est un nouvel artefact créa du brand). - Trigger
validate-resourcessilencieusement post-write (valide genome + creative + sidecar contregenome.schema.json/creative.schema.json/produced-asset.schema.json, et la forme de path contreALLOWED_PATH_PATTERNS). Flag MAJOR/CRITICAL à l'opérateur si remonte. - Gate QC de sortie · invoquer
qc-creative(creative_dir= le{CRT-NN}/variante produit · Task tool · sonnet · subagent_safe) sur le binaire, écrire le verdict dansproduced/{slug}.json#qcviawrite_to_context, et NE PAS présenter la variante comme spend-ready sansdecision: PASS. Règle anti-défaut · logo/produit fidélité-critique = compositinglayered, jamais re-généré. Voirqc-creative/SKILL.md.
HR-RC-CANON-1 · Entry canonical batch + mkdir-claim (Brique 4 étape C · exécutable)
Cette règle était déclarée en frontmatter (
patch_notes_v2_87_4) mais JAMAIS exécutée. Elle est ici MATÉRIALISÉE en section réelle, miroir de compose-creative v1.9.0 (HR-CC-CANON-1). Toute variante recomposée DOIT créer son dossier canoniquebrands/{slug}/creatives/{batch}/{CRT-NN}/AVANT tout write, persister le split (genome + creative + sidecar + brief), et interdire tout asset orphelin hors structure. Cross-refresources/conventions/creative-storage.md(D#481).
Refus AVANT claim (ordre HR8)
Tous les refus HR8-style (variant_axis absent · 2+ dimensions · retain 0 concept core · FAL_API_KEY absent · source CRT-NN introuvable au scan cross-batch) s'exécutent AVANT STEP 0c. Un run refusé ne doit JAMAIS laisser un dossier CRT-NN/ réservé vide. La claim atomique ne se prend qu'une fois la génération garantie possible.
STEP 0 · mkdir-claim atomique (AVANT tout write_to_context)
Le mkdir-claim s'exécute en STEP 0, juste avant la génération visuelle persistée (HR4 produit le binaire en /tmp ; HR6 persiste dans le dossier réservé ici). La variante va dans le batch DU JOUR (run courant), PAS dans le batch de la source · le CRT source est trouvé par scan cross-batch (STEP 0b), mais le nouveau CRT-NN est écrit dans le {batch} du jour par cohérence-de-run.
STEP 0a · résoudre {batch} du jour. {batch} = run date-stampé du jour, lowercase + chiffres + tirets (regex gate [a-z0-9-]+). Default = $(date +%Y-%m-%d)-NN où NN = prochain run 2-digit du jour. Résoudre NN · lister brands/{slug}/creatives/ pour les dossiers matchant $(date +%Y-%m-%d)-*, prendre le max suffixe, sinon 01. Un run PEUT réutiliser le batch de session déjà ouvert. mkdir -p brands/{slug}/creatives/{batch}/ (idempotent · le dossier-batch n'est PAS la claim).
STEP 0b · candidat CRT-NN (scan max cross-batch). Scanner TOUT l'arbre du namespace CRT pour le max existant, PAS juste le batch courant (les id sont globalement uniques dans le namespace CRT, batch-indépendants) · c'est le MÊME scan cross-batch qui sert à localiser la source en HR2 ·
ls -d brands/{slug}/creatives/*/CRT-* 2>/dev/null | grep -oE 'CRT-[0-9]{2,4}' | sort -t- -k2 -n | tail -1
→ N. Candidat = CRT-(N+1), zero-paddé à ≥2 digits. CRT scanné SÉPARÉMENT de RCV (jamais fusionnés · RCV = namespace concurrent decompose-ad · recompose reste 100% CRT, jamais RCV).
STEP 0c · claim atomique. mkdir brands/{slug}/creatives/{batch}/CRT-{N+1}/ SANS -p sur le segment final (le parent {batch}/ existe déjà depuis 0a). mkdir sur un dossier existant échoue → c'est le verrou (la réservation). Sur succès · l'id est à toi, immédiatement mkdir -p brands/{slug}/creatives/{batch}/CRT-{N+1}/produced/. Sur EEXIST · N = N+1, retry (boucle bornée, ex 50 essais). C'est le SEUL mécanisme de réservation · pas de fichier-index, pas de lock-file, pas d'écrivain privilégié. Les trous d'id sont inoffensifs (unicité requise, pas absence-de-trou).
PIÈGE (vérifié source · write-to-context.py L453
target.parent.mkdir(parents=True, exist_ok=True)) · ce mkdir du gate n'est PAS une claim atomique. Si un skill saute STEP 0c et compte sur le mkdir du gate, deux runs concurrents passent tous les deux et écrivent dans le MÊMECRT-NN, clobbering silencieux. Lemkdiratomique (sansexist_ok) DOIT être fait par le skill en STEP 0c.
STEP 0d · write dans le dossier réservé. MAINTENANT (HR6) · write_to_context les trois .json (genome.json, creative.json, sidecar produced/{slug}.json) en mode=proposed. Binaires (.jpg/.mp4) déplacés DIRECT sous .../produced/ (write_to_context ne gère que le json). brief.md écrit DIRECT (outil Write · .md exempt de ALLOWED_PATH_PATTERNS ET de mutation-guard).
Lignage 5-refs + variant_of obligatoire
creative.json#lineage doit être populé · angle_ref (ANG-NN) + audience_ref + product_ref + mechanism_ref + concept_ref (miroir descriptif de concept_id, SSOT groupage = creative.concept_id top-level) + variant_of (le CRT-NN source · namespace CRT, jamais RCV). Cross-refs many-to-many activés. JAMAIS surcharger le dossier {CRT-source}/ parent · la variante est TOUJOURS un nouveau {CRT-NN}/ réservé par mkdir-claim, source append-only.
Interdit orphelin
JAMAIS sauver un asset JPG/PNG standalone hors structure (e.g. visual-identity/creative_ANG-01_*.jpg orphelin). TOUT binaire vit sous creatives/{batch}/{CRT-NN}/produced/ AVEC son sidecar produced/{slug}.json (manifest gated). Pas de binaire sans sidecar (anti BUG-ASSET-ORPHAN). Pas d'entry variante sans genome.json + creative.json.
HR7 · Output operator-facing
Vue compacte side-by-side source vs variant. Reco no-orphan-output finale :
- Suggest A/B test source vs variant si pas encore testé
- Suggest 2 autres axes de variation à explorer si concept performant en source
- Suggest
score-matrixpour prioriser variants à tester si déjà 3+ variants existent
HR8 · Anti-patterns
- NEVER recomposer sans variant_axis explicite (mode hypothesis trop large, pollue creative DB)
- NEVER changer 2+ dimensions simultanées (c'est un nouveau concept, route compose-creative)
- NEVER promouvoir
variant_axisen array (casse le contrat single-axis de recompose ET le schéma single-enum). 1 axe = 1 string canonique. - NEVER persister une valeur
variant_axishors enum canonique creative.schema[photo_swap, promo_toggle, hook_swap, background_swap, persona_split, null](les anciennesplatform_swap/format_swap/visual_treatment_swap/audience_swap/new_*sont SUPPRIMÉES · validate-resources rejette). - NEVER écraser le creative source ni son dossier
{CRT-source}/(toujours nouveau{CRT-NN}/réservé par mkdir-claim avec variant_of, source append-only) - NEVER skip retain de concept core (sinon c'est compose-creative déguisé, refuser et re-router)
- NEVER mettre la variante dans le batch de la source (la variante va dans le batch DU JOUR · le CRT source est trouvé par scan cross-batch, jamais hérité de batch)
- NEVER
variant_ofen namespace RCV pour recompose (recompose = 100% CRT · RCV est réservé aux concurrents decompose-ad) - NEVER confusion régime d'écriture sous
produced/· INTERDIT écrire un.json(genome/creative/sidecar) en direct (toujourswrite_to_contextmode=proposed) · ATTENDU écrire le binaire.jpg/.mp4DIRECT sousproduced/(write_to_context ne gère que le json) MAIS chaque binaire DOIT avoir son sidecarproduced/{slug}.jsongated (anti BUG-ASSET-ORPHAN) - NEVER exposer JSON brut, field_path, ou enum interne (
variant_axisvaleurs) à l'opérateur en surface output. Traduire en langage clair ("on garde le hook, on change juste la scène") - NEVER régénérer
concept_id(variant = même concept, dimension différente)
Operator output template
Réf canonique · resources/templates/operator-fiche-output.md. Header plain language, body en vocabulaire métier, footer soft offer 1 ligne max. Bloc concept_id / variant_of / variant_axis persiste en backstage (creative.json) · operator ne le voit jamais.
═══════════════════════════════════════════════════════════════
{BRAND_HUMAIN} · Variante de la pub n°{N_source}
═══════════════════════════════════════════════════════════════
{date YYYY-MM-DD} · {1 phrase plain language · ex "même concept, audience changée pour femmes 45-55 ménopause"}
───────────────────────────────────────────────────────────────
Ce qui reste
───────────────────────────────────────────────────────────────
Mécanique {nom métier · ex "before-after-bridge"}
Ton {plain · ex "direct response avec touche brand"}
Hook texte "{verbatim hook · si retenu}"
───────────────────────────────────────────────────────────────
Ce qui change
───────────────────────────────────────────────────────────────
Dimension {plain · ex "audience cible" ou "scène / décor" ou "photo produit" ou "hook texte" ou "promo affichée"}
Avant {plain · ex "femmes 30-45 post-grossesse"}
Après {plain · ex "femmes 45-55 ménopause"}
Pourquoi {1 phrase justif · ex "trigger émotionnel différent, registre copy à recalibrer"}
───────────────────────────────────────────────────────────────
Comparaison
───────────────────────────────────────────────────────────────
PUB n°{N_source} VARIANTE n°{N_variant}
Cible {old_audience} {new_audience}
Plateforme {old_platform} {new_platform}
Format {old_format} {new_format}
{autres si changent}
Photo générée ouvre dans Preview · open {path} (= creatives/{batch}/{CRT-NN}/produced/{slug}.jpg)
───────────────────────────────────────────────────────────────
{1 reco soft offer 1 ligne max · ex "Si tu veux, on peut tester A/B la source vs la variante."}
Backstage (creative.json, NON rendu operator) · concept_id (réutilisé source), variant_of: {creative_id_source} (CRT-NN), variant_axis enum canonique, meta.validation_status, tags.source: internal_production. Vivent dans le JSON pour retrieval programmatique · operator ne les voit jamais.
Validation pre-ship fiche ·
- Header ne contient pas
RECOMPOSE,CRT-N → CRT-X,variant_axis:,concept_id - Body ne contient pas
intent_mix: {...},variant_of, field paths JSON - Footer · 1 phrase soft offer max, jamais menu, jamais nommer skill
score-matrixen surface
Cross-refs
compose-creativev1.9.0 (forward generation ex nihilo, sibling P5 visual · même split genome/creative/sidecar + mkdir-claim + forme batch · Brique 4 étape B)decompose-ad(reverse-engineering ad externe, sibling P5 visual · namespace RCV concurrent)- Schemas cibles (Brique 4 étape C · split) :
resources/schemas/creative.schema.jsonv1.3 (lignage · variant_of CRT|RCV + lineage{} + variant_axis enum canonique SSOT 5+null),resources/schemas/genome.schema.jsonv1.0 (ADN par-créa · script_id GSC-NN, hook, frames, support, genome_tags),resources/schemas/produced-asset.schema.jsonv1.0 (sidecar manifest binaire · 1 par .jpg, anti-orphelin) - Convention stockage :
resources/conventions/creative-storage.md(D#481 · forme batchcreatives/{batch}/{CRT-NN}/+ allocation mkdir-claim atomique · CRT source trouvé cross-batch, variante écrite dans le batch du jour) resources/canon/copy/formats-livrables/*.json(contraintes platform/format · axe (d) → background_swap)docs/system/skill-authoring-doctrine.md(frontmatter triad, type producer baseline)docs/system/canonical-matrix-reasoning.md(préservation concept core = matrix-driven)validate-resources(post-write silencieux),build-brand-snapshot.py(rebuild snapshot post-mutation)