@koda-sl/baker-cli 0.480.0 → 0.481.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +3 -3
  2. package/dist/{canvas-BPACZHNU.js → canvas-4T64K6PQ.js} +12 -12
  3. package/dist/{catalog-RMMECI4Z.js → catalog-UPF4SOBW.js} +3 -3
  4. package/dist/{chunk-65PUXMWE.js → chunk-4VWTIT5C.js} +2 -2
  5. package/dist/{chunk-7HJR5M4R.js → chunk-5RCCESDX.js} +23 -6
  6. package/dist/chunk-5RCCESDX.js.map +1 -0
  7. package/dist/{chunk-D4I3KULJ.js → chunk-ELL2IQMF.js} +3 -3
  8. package/dist/chunk-ELL2IQMF.js.map +1 -0
  9. package/dist/{chunk-7LU7KARN.js → chunk-K2Z3KGOH.js} +2 -2
  10. package/dist/{chunk-KCKC637S.js → chunk-KQDAFZKR.js} +2 -2
  11. package/dist/{chunk-HJQ7N7FC.js → chunk-LJNYZLOH.js} +2 -2
  12. package/dist/{chunk-AIJBZSZV.js → chunk-LULQRFGD.js} +6 -6
  13. package/dist/{chunk-GMSKPGLI.js → chunk-O6OCL3LH.js} +9 -9
  14. package/dist/{chunk-TF4PGUO5.js → chunk-PJSIVGHU.js} +5 -5
  15. package/dist/{chunk-JD4SABZC.js → chunk-U4W4LULB.js} +5 -5
  16. package/dist/{chunk-C3TTE7GS.js → chunk-U6IWRZQF.js} +3 -3
  17. package/dist/{chunk-LS3A753J.js → chunk-V6PXHQSI.js} +2 -2
  18. package/dist/{chunk-QVNSVFSD.js → chunk-WD5BQBVY.js} +2 -2
  19. package/dist/{chunk-V7N666PJ.js → chunk-WMVTIKET.js} +2 -2
  20. package/dist/{chunk-NBNLGJH5.js → chunk-XSGO44OV.js} +2 -2
  21. package/dist/cli.js +2 -2
  22. package/dist/{creatives-F3TMRTIR.js → creatives-U2M4F3D5.js} +5 -5
  23. package/dist/{critique-A2OEMGZS.js → critique-43OVWKN7.js} +3 -3
  24. package/dist/engine/index.js +2 -2
  25. package/dist/{feedback-CCU6ECMQ.js → feedback-3GU3PAEI.js} +6 -6
  26. package/dist/{feedback-record-JCE3JWG6.js → feedback-record-PIJDOJW3.js} +6 -6
  27. package/dist/{make-X6IG6KAZ.js → make-IHJROEKG.js} +20 -15
  28. package/dist/make-IHJROEKG.js.map +1 -0
  29. package/dist/{rerun-NR54OG3M.js → rerun-GFMBT6NJ.js} +10 -10
  30. package/dist/{run-IIU2UVQU.js → run-AIBAPD4R.js} +10 -10
  31. package/dist/{scaffold-ad-3VMAP5BF.js → scaffold-ad-7L6D6PKO.js} +8 -8
  32. package/dist/{scaffold-static-ad-Z7LO7PKI.js → scaffold-static-ad-V2H7TOG6.js} +13 -13
  33. package/dist/{scaffold-video-SBSSR7TZ.js → scaffold-video-A3KAT6QO.js} +7 -7
  34. package/dist/{select-ON3KZEEK.js → select-Q4S4CPKV.js} +5 -5
  35. package/dist/{set-prompt-RHMZ7SFC.js → set-prompt-VLGJG5WO.js} +4 -4
  36. package/dist/{stale-OIOOAP2S.js → stale-NT2JRQHY.js} +4 -4
  37. package/dist/{sync-definition-5JO7QXX3.js → sync-definition-VSR72E7E.js} +7 -7
  38. package/dist/{translate-K4D7KDYV.js → translate-27DIFMBU.js} +7 -7
  39. package/dist/{validate-PEWGXGCR.js → validate-2NRSRRMM.js} +7 -7
  40. package/package.json +1 -1
  41. package/dist/chunk-7HJR5M4R.js.map +0 -1
  42. package/dist/chunk-D4I3KULJ.js.map +0 -1
  43. package/dist/make-X6IG6KAZ.js.map +0 -1
  44. /package/dist/{canvas-BPACZHNU.js.map → canvas-4T64K6PQ.js.map} +0 -0
  45. /package/dist/{catalog-RMMECI4Z.js.map → catalog-UPF4SOBW.js.map} +0 -0
  46. /package/dist/{chunk-65PUXMWE.js.map → chunk-4VWTIT5C.js.map} +0 -0
  47. /package/dist/{chunk-7LU7KARN.js.map → chunk-K2Z3KGOH.js.map} +0 -0
  48. /package/dist/{chunk-KCKC637S.js.map → chunk-KQDAFZKR.js.map} +0 -0
  49. /package/dist/{chunk-HJQ7N7FC.js.map → chunk-LJNYZLOH.js.map} +0 -0
  50. /package/dist/{chunk-AIJBZSZV.js.map → chunk-LULQRFGD.js.map} +0 -0
  51. /package/dist/{chunk-GMSKPGLI.js.map → chunk-O6OCL3LH.js.map} +0 -0
  52. /package/dist/{chunk-TF4PGUO5.js.map → chunk-PJSIVGHU.js.map} +0 -0
  53. /package/dist/{chunk-JD4SABZC.js.map → chunk-U4W4LULB.js.map} +0 -0
  54. /package/dist/{chunk-C3TTE7GS.js.map → chunk-U6IWRZQF.js.map} +0 -0
  55. /package/dist/{chunk-LS3A753J.js.map → chunk-V6PXHQSI.js.map} +0 -0
  56. /package/dist/{chunk-QVNSVFSD.js.map → chunk-WD5BQBVY.js.map} +0 -0
  57. /package/dist/{chunk-V7N666PJ.js.map → chunk-WMVTIKET.js.map} +0 -0
  58. /package/dist/{chunk-NBNLGJH5.js.map → chunk-XSGO44OV.js.map} +0 -0
  59. /package/dist/{creatives-F3TMRTIR.js.map → creatives-U2M4F3D5.js.map} +0 -0
  60. /package/dist/{critique-A2OEMGZS.js.map → critique-43OVWKN7.js.map} +0 -0
  61. /package/dist/{feedback-CCU6ECMQ.js.map → feedback-3GU3PAEI.js.map} +0 -0
  62. /package/dist/{feedback-record-JCE3JWG6.js.map → feedback-record-PIJDOJW3.js.map} +0 -0
  63. /package/dist/{rerun-NR54OG3M.js.map → rerun-GFMBT6NJ.js.map} +0 -0
  64. /package/dist/{run-IIU2UVQU.js.map → run-AIBAPD4R.js.map} +0 -0
  65. /package/dist/{scaffold-ad-3VMAP5BF.js.map → scaffold-ad-7L6D6PKO.js.map} +0 -0
  66. /package/dist/{scaffold-static-ad-Z7LO7PKI.js.map → scaffold-static-ad-V2H7TOG6.js.map} +0 -0
  67. /package/dist/{scaffold-video-SBSSR7TZ.js.map → scaffold-video-A3KAT6QO.js.map} +0 -0
  68. /package/dist/{select-ON3KZEEK.js.map → select-Q4S4CPKV.js.map} +0 -0
  69. /package/dist/{set-prompt-RHMZ7SFC.js.map → set-prompt-VLGJG5WO.js.map} +0 -0
  70. /package/dist/{stale-OIOOAP2S.js.map → stale-NT2JRQHY.js.map} +0 -0
  71. /package/dist/{sync-definition-5JO7QXX3.js.map → sync-definition-VSR72E7E.js.map} +0 -0
  72. /package/dist/{translate-K4D7KDYV.js.map → translate-27DIFMBU.js.map} +0 -0
  73. /package/dist/{validate-PEWGXGCR.js.map → validate-2NRSRRMM.js.map} +0 -0
package/README.md CHANGED
@@ -2007,7 +2007,7 @@ baker creatives check [--slug <s>] # which ads alrea
2007
2007
  - `brief` returns a verdict, not raw numbers: `account.bottleneck` is `hook`, `after_click`, `none` or `too_little_data`. A part that cannot be read says `unavailable`; the market searches (the followed brands, and the library for what COMPANY.md's first line says the company sells, in a few words) run at once and get 12 seconds, so a slow ad library leaves them out rather than holding the brief. With few customer quotes on file it runs `research customers` itself alongside the reads (about 30 s) and returns what it wrote; `--no-research` skips that. With no ideas on file, its hint lists the values `awareness`, `angle` and `format` take. `market` says what competitors' ads already say (the followed brands, else the library), how sophisticated the market is, and which awareness stages nobody speaks to.
2008
2008
  - `ideas` is local and nearly free: two readings of the words go to the decision model (a fraction of a cent, billed with the workspace's next run, answers kept in `canvas/.cache/`). An insight filed as `observed` reads as `interpreted` when its source cannot be opened or its quote is a statistic about people rather than someone's words ("39% de los propietarios…"), and a new idea is warned when its message says what a failed idea said, however reworded or translated. Without an answer, an insight keeps its status and only the same words count as the same message. An idea's `evidence` resolves to `src/content/insights/` entries, or it is an `explore` bet. Numbers must be in the Knowledge or our own customers' quotes (a Reddit, forum or web quote never sources a claim), no two ideas may share an angle and format, and a review or testimonial format needs a quote from our own customer.
2009
2009
  - `learn` pools each built idea's creatives, judges them against the rest of the account's cost per result and writes `status` (`winner`, `failed`, `inconclusive`, `testing`) and a frozen `result` onto the idea. `ideas` then warns when a new idea repeats a failed one.
2010
- - `make` builds one idea, on a proven winning ad whenever one fits. A static first reads a reference brief: one small model (`google/gemini-3.5-flash-lite`, about a second, cached per company notes and idea under `canvas/.cache/reference-brief/`) reads COMPANY.md and the idea and says what this ad sells, its industry and its people in the plain English the library files ads under, the market it runs in, who buys (consumer, small business, enterprise), whether the brand jokes (these and its own brand names from the company's reading, read once per COMPANY.md), what its rules forbid (its never-rules), and up to four wider searches, closest first (an adjacent product the same buyer weighs, the same buyer buying something else, the same kind of decision: for a padel-court builder, tennis courts, sports club owners, turnkey business investment). Without an answer it falls back to COMPANY.md's first line, its market line (English, Spanish, French, German or Italian) and the persona. It then searches for ads of what this ad sells, in its market and then anywhere, first in the idea's container for our viewer and then in any static container; then ads made for the same people whatever they sell; then the wider searches; then ads of the broader industry; then the idea's message; then the container's strongest winners from any category (no query, by winner score), in the market and then anywhere (a search that fails is asked once more; when every search for this ad's topic fails, the library's browse of any category's winners is never built on: the static starts from the idea, `REFERENCE_SEARCH_UNAVAILABLE`, and make can be run again later). It pools the candidates (one ad per brand, the better search first, ads whose run barely proves them after the proven ones, the company's own ads left out), reads up to 24 of them against the ad being made with the decision model (made for our kind of buyer? would our viewer see themselves in it? could our brand run something like it?) and checks the best six in that order, checks up to six with `winning-ads fit`, three at a time, each judged for the ad being made (the idea's persona from the Knowledge, its message, awareness, angle and the customers' words it cites; a reference the fit says our viewer would scroll past does not fit, nor one whose scroll-stopper or proof cannot be ours, and a fit made for one idea is never reused for another) (the next only once one does not fit; the first hint names each passed over and why), and, when more than one fits, looks at all of them side by side for the viewer (`~google/gemini-pro-latest`, one call) and keeps the one it prefers, saying why (`reference.chosen_because`, also in the first hint and the thinking's first read). The same look says, as a media buyer would, which element our ad takes from which of them (the hook, the visual, who appears, the proof, the layout, the call to action) and what it must not copy (a fake news page, dated urgency, another brand's marks); every picture is saved under `canvas/references/` and the thinking lays them out as a question to settle; measured on 14 production companies, judged blind by another model, the reference kept went from 5.0 to 6.9 out of 10. The fit says what made that ad a winner, so what the creative carries of it (`reference.carry`: `whole_ad`, `layout`, `hook` or `angle`); make seeds it into `prompt.json` as `concept`, the canvas keeps the ad as its `original` node, and the renders see it for its structure only at `whole_ad` or `layout`. The first hint names the ad and what it carries. A `--reference` chosen by hand skips the search but is checked like one make found (the same fit, for the ad being made; free when made before), so its thinking names what stops the scroll and the proof and asks what of ours carries each, and the first hint calls it the one chosen; only when no check can be made is it built on unchecked, the hint naming only the ad; if the concept could not be written into `prompt.json`, a hint says so and how to write it from the fit already made. For a new creative it starts scaffolding the best-ranked one while the fits are asked (kept when it fits, stopped and removed whole when another or none is kept). Finding, checking and describing a reference gets 150 seconds; past that the build is stopped and the static starts from the idea (`REFERENCE_SLOW`). A creative whose ad is already written is never built over (`ALREADY_MADE`): edit its `prompt.json`, or pass `--slug` for another ad of the same idea. A video idea takes its beats spec. Either way it records the idea on `_definition.md`, saves a proven ad's picture under `canvas/references/`, writes a `## Thinking` outline into its body (open `[TODO: …]` questions, the first sending the agent to that picture, seeded with the fit's read: what the reference is and why it won, or why the bet should work; whether it fits the viewer; our hook; the angle and the call to action; who appears and what we show, listing what the company has: its ready Avatars (real people and ones made for a role), up to three customers' own words for this message from the testimonials, how to make the founder's Avatar, where to get product pictures, and its logos, and closing with three decision lines, `- Cast:`, `- Product shown:`, `- Logo:`; what survives, one decision line per element the proven ad won on, what stops the scroll and the proof first (`- Stops the scroll: <element> — keep`, `— replace with <what of ours does the same job>` or `— impossible: "<rule>"`, the check's first read in each question); the plan, which settles the format when the proven ad's container differs from the idea's), sets `prompt.json`'s `message` and `viewer.who` (the idea's persona, title and segment, never a placeholder: the image prompt, the casting check and the viewer check read it) and syncs the definition. `canvas run` and `canvas validate` refuse to render while a thinking question is open (`definition_not_thought`), so the definition is thought before the ad is written; a creative made before the outline existed is never asked. They also refuse a cast that is not ours (`cast_not_decided`): the cast must be an Avatar (`@handle`), a real customer, real footage or nobody, the product a library picture, a capture, a Hyperframe, footage or none, the logo a brand file or none; and on a static, an Avatar cast must be one `prompt.json` grounds a person on. `--avatar <handle>` (`@eduard` or `eduard,lucia`) casts the ad's people as those Avatars: run again with it once the thinking decides, before `prompt.json` is written; it rebuilds the canvas and keeps the thinking. A static started from the idea has no people to find, so each Avatar is a person of its own, grounded on its sheet (`PERSON_n` in `prompt.json` `references`); a quote signed by that Avatar's person names that reference instead of casting them twice. Drafted copy lines claiming a figure the company never wrote come back first in `hints`. Only when no proven ad fits (or with `--from-idea`) does a static start from the idea itself, and the first hint says why (none in the container, what the ones checked won on that the company lacks, the time ran out, or the proven ad it picked could not be read; a `--reference` chosen by hand that cannot be read fails instead): `prompt.json` holds the message and placeholders, and `canvas run` refuses to render until the scene and copy are written.
2010
+ - `make` builds one idea, on a proven winning ad whenever one fits. A static first reads a reference brief: one small model (`google/gemini-3.5-flash-lite`, about a second, cached per company notes and idea under `canvas/.cache/reference-brief/`) reads COMPANY.md and the idea and says what this ad sells, its industry and its people in the plain English the library files ads under, the market it runs in, who buys (consumer, small business, enterprise), whether the brand jokes (these and its own brand names from the company's reading, read once per COMPANY.md), what its rules forbid (its never-rules), and up to four wider searches, closest first (an adjacent product the same buyer weighs, the same buyer buying something else, the same kind of decision: for a padel-court builder, tennis courts, sports club owners, turnkey business investment). Without an answer it falls back to COMPANY.md's first line, its market line (English, Spanish, French, German or Italian) and the persona. It then searches for ads of what this ad sells, in its market and then anywhere, first in the idea's container for our viewer and then in any static container; then ads made for the same people whatever they sell; then the wider searches; then ads of the broader industry; then the idea's message; then the container's strongest winners from any category (no query, by winner score), in the market and then anywhere (a search that fails is asked once more; when every search for this ad's topic fails, the library's browse of any category's winners is never built on: make fails with `REFERENCE_SEARCH_UNAVAILABLE` and is run again). It pools the candidates (one ad per brand, the better search first, ads whose run barely proves them after the proven ones, the company's own ads left out), reads up to 24 of them against the ad being made with the decision model (made for our kind of buyer? would our viewer see themselves in it? could our brand run something like it?) and checks the best six in that order, checks up to six with `winning-ads fit`, three at a time, each judged for the ad being made (the idea's persona from the Knowledge, its message, awareness, angle and the customers' words it cites; a reference the fit says our viewer would scroll past does not fit, nor one whose scroll-stopper or proof cannot be ours, and a fit made for one idea is never reused for another) (the next only once one does not fit; the first hint names each passed over and why), and, when more than one fits, looks at all of them side by side for the viewer (`~google/gemini-pro-latest`, one call) and keeps the one it prefers, saying why (`reference.chosen_because`, also in the first hint and the thinking's first read). The same look says, as a media buyer would, which element our ad takes from which of them (the hook, the visual, who appears, the proof, the layout, the call to action) and what it must not copy (a fake news page, dated urgency, another brand's marks); every picture is saved under `canvas/references/` and the thinking lays them out as a question to settle; measured on 14 production companies, judged blind by another model, the reference kept went from 5.0 to 6.9 out of 10. The fit says what made that ad a winner, so what the creative carries of it (`reference.carry`: `whole_ad`, `layout`, `hook` or `angle`); make seeds it into `prompt.json` as `concept`, the canvas keeps the ad as its `original` node, and the renders see it for its structure only at `whole_ad` or `layout`. The first hint names the ad and what it carries. A `--reference` chosen by hand skips the search but is checked like one make found (the same fit, for the ad being made; free when made before), so its thinking names what stops the scroll and the proof and asks what of ours carries each, and the first hint calls it the one chosen; only when no check can be made is it built on unchecked, the hint naming only the ad; if the concept could not be written into `prompt.json`, a hint says so and how to write it from the fit already made. For a new creative it starts scaffolding the best-ranked one while the fits are asked (kept when it fits, stopped and removed whole when another or none is kept). Finding, checking and describing a reference gets 150 seconds; past that the build is stopped and make fails (`REFERENCE_SLOW`): run it again, the searches and checks already made are kept, so it goes further each time. A creative whose ad is already written is never built over (`ALREADY_MADE`): edit its `prompt.json`, or pass `--slug` for another ad of the same idea. A video idea takes its beats spec. Either way it records the idea on `_definition.md`, saves a proven ad's picture under `canvas/references/`, writes a `## Thinking` outline into its body (open `[TODO: …]` questions, the first sending the agent to that picture, seeded with the fit's read: what the reference is and why it won, or why the bet should work; whether it fits the viewer; our hook; the angle and the call to action; who appears and what we show, listing what the company has: its ready Avatars (real people and ones made for a role), up to three customers' own words for this message from the testimonials, how to make the founder's Avatar, where to get product pictures, and its logos, and closing with three decision lines, `- Cast:`, `- Product shown:`, `- Logo:`; what survives, one decision line per element the proven ad won on, what stops the scroll and the proof first (`- Stops the scroll: <element> — keep`, `— replace with <what of ours does the same job>` or `— impossible: "<rule>"`, the check's first read in each question); the plan, which settles the format when the proven ad's container differs from the idea's), sets `prompt.json`'s `message` and `viewer.who` (the idea's persona, title and segment, never a placeholder: the image prompt, the casting check and the viewer check read it) and syncs the definition. `canvas run` and `canvas validate` refuse to render while a thinking question is open (`definition_not_thought`), so the definition is thought before the ad is written; a creative made before the outline existed is never asked. They also refuse a cast that is not ours (`cast_not_decided`): the cast must be an Avatar (`@handle`), a real customer, real footage or nobody, the product a library picture, a capture, a Hyperframe, footage or none, the logo a brand file or none; and on a static, an Avatar cast must be one `prompt.json` grounds a person on. `--avatar <handle>` (`@eduard` or `eduard,lucia`) casts the ad's people as those Avatars: run again with it once the thinking decides, before `prompt.json` is written; it rebuilds the canvas and keeps the thinking. A static started from the idea has no people to find, so each Avatar is a person of its own, grounded on its sheet (`PERSON_n` in `prompt.json` `references`); a quote signed by that Avatar's person names that reference instead of casting them twice. Drafted copy lines claiming a figure the company never wrote come back first in `hints`. Only when no proven ad fits (or with `--from-idea`) does a static start from the idea itself, and the first hint says why (none in the container, what the ones checked won on that the company lacks, or the proven ad it picked could not be read; a slow or unavailable search never starts an ad from the idea, and a `--reference` chosen by hand that cannot be read fails instead): `prompt.json` holds the message and placeholders, and `canvas run` refuses to render until the scene and copy are written.
2011
2011
  - **The logo on a static.** A standard ad shows the brand's logo whatever the reference showed (only a native disguise follows the reference, its logo small as the sender). With no logo file in `src/brand/logos/`, `scaffold-static-ad` (and so `make`) looks for the company's own: Brandfetch's when it confirms the brand, else the logo in its website's header (an image or inline SVG from its own domain only), saved as `logo-light`/`logo-dark` and registered in `tokens.json`; with none found a hint says where to add it on the Brand page, and the cast's `- Logo: none` is accepted only then, for a native ad, or when its reason quotes a company rule on the logo. `static_logo` places the file where it reads (each pixel against the ground under it, at least 90%), never on words, a button or a face; with no such spot it sets it on a small plate in a brand colour, and a size with no room is blocked (`--regenerate format_<size>`, keeping the corner clear). An ad with `logo: false` tells the words render to draw no logo or wordmark.
2012
2012
  - **Where an ad runs decides its sizes.** `make --platforms`, the creative's `_definition.md` (whose recorded `formats` a rebuild keeps), an idea's `platforms` (main first), the one platform the idea's words name, or a Meta `--adset` say where it runs; with none, `make` and `canvas scaffold-static-ad` refuse before anything is searched or billed (`platform_required`, its fix naming the connected ad accounts), never defaulting to Meta. The sizes follow the placement catalog (`@baker/canvas-contract` `deliveryPlan`): Meta 4:5 + 9:16, LinkedIn 1:1, TikTok 9:16, Pinterest 2:3, Google 1:1, YouTube and X 1.91:1, the first platform's size designed first; `--formats` delivers exactly the sizes asked (a size the platform never shows is refused, one it needs left out is a warning naming the placements that show a cropped picture), and a placement id names one placement alone (`meta_stories`). The definition records `platforms`, `platform` and `formats`; editing them re-plans the canvas on the next `canvas validate` or `canvas run` (`[sizes]`), before anything bills, keeping the three options when only a size is added. `make --platform` is retired (it named the reference's library: `--reference-platform`); `scaffold-static-ad --platform`, `--placements` and `--aspect` are mapped onto `--platforms` with a hint, and `--placements none` is refused.
2013
2013
  - `make --series <lead>` makes a static idea as another ad of a series: several messages in one style. Nothing is searched or rendered; the ad's `_definition.md` names its lead (`series: <lead>`), its `prompt.json` starts as the lead's ready version with the idea's message, and only its `copy` is written. Its canvas is written from the lead's on every run: the lead's finished picture with this ad's words, every other size the lead's own size with the same words. `baker canvas run --series <lead>` renders every ad of the series at once, one reviewed and corrected run each, skipping an ad already ready from the lead's current version.
@@ -6293,8 +6293,8 @@ It then scaffolds a canvas on which **the image model designs the finished ad**,
6293
6293
 
6294
6294
  - Up to **three variants** (`--takes`, default 3) render on the model the router picks for the concept (today `openai/gpt-image-2.5-sunburst` at quality `xhigh`, about 45 s a picture against 80 s at `max`; `--gen-model` overrides), each a finished ad: the picture and every word of the copy. Each variant is **its own request** (tagged `static_variant_call`), run in parallel on its own direction (the scene as written; another picture of the same message, by the scene's look; another line leading, named by number so no word changes), and a `collect` tagged `static_variants` gathers them, on every model: one request with `n` costs the same (`n` is billed per picture) but gave fewer distinct options, so the graph never uses it. The brief quotes every line of `copy` verbatim and asks for each once; it is compiled again from `prompt.json` on every `validate` and `run`, so an edit re-keys exactly the renders it touches. The brand's **typeface** is shown to the model as a `font_specimen` set on the `designed` lines: a file in `src/brand/fonts/` (via `src/brand/tokens.json` when present), else the heading family `tokens.json` or `BRAND.md` names, fetched as a web font file; an `in_scene` line keeps its own lettering only on the real packaging, signage or screen its `on` names (handwriting on post-its, whiteboards or chalkboards, and a line naming no surface, are set as designed type). Each line's `role` sets its size: a price or stat (else the headline) leads, the `cta` is a large solid button, fine print is smallest. **The logo is optional**: when `prompt.json` has `"logo": true` (an older blueprint: an `advertiser_brand` entry in `brands_logos`) and the real file is in `src/brand/logos/`, that file is placed on every size pixel for pixel, never drawn by a model (`static_logo`, below): every render is told to draw no logo and keep its slot plain, the slot planned inside the size's safe area at the concept's `logo_position` (top-left when it names none). A creative scaffolded while the model drew the logo is moved onto the file on its next run (`[logo]` note). A file that does not open as a picture is handed to the model to draw from instead, and the run says so; no logo is never an error, never a slot to fill and never a stand-in. Setting it false drops the logo on the next run; adding one to a canvas scaffolded without the file needs the file in `src/brand/logos/` and a re-scaffold. `prompt.json` also carries a **`brand`** block (ground, ink, accent, CTA and palette, from `src/brand/tokens.json` roles or `BRAND.md`); a colour outside it never reaches the model. The brief describes a complete ad at the master's shape and asks only for its edge margin (see below). `baker canvas set-prompt` refuses these nodes and names `prompt.json`.
6295
6295
  - A **`static_prefilter`** node (tagged `variant_prefilter`) drops variants that fail the deterministic checks before the judge sees them, never all of them; its **judge** (`judge_model`, `judge_prompt`) compares the rest pairwise against the compiled brief when the checks tie (one variant passes through unjudged). A canvas scaffolded with a separate `image_select` judge (tagged `pick`) has it folded into the prefilter on its next run.
6296
- - **Every placement format is made from the chosen picture, and no size is a crop or a paste of another.** On an ad laid out again for each size (a designed or interface ad, or `--master-shape relayout`), the pick goes to exact pixels as the master (an `imagemagick` node tagged `static_master`) at the lead size: the 4:5 when a feed size is delivered, else the square, else the first portrait size, rendered at the model's nearest ratio (3:4 on GPT Image) and filled to Meta's pixels. It is delivered as it is. Every other size (9:16, 1:1, 16:9, …) is **re-laid out** from the master by one request (`image_generate` node `adapt_<size>`, tagged `static_adapt_format`, on the router's `relayout` model, GPT Image 2.5 at quality `xhigh`): the master, the variants' own product and people pictures and the logo file as references and a brief compiled from `prompt.json` for that frame on every run (`static-adapt.ts`): the exact lines and their hierarchy, the same people (same face, same clothes), product and label, logo and none the chosen ad does not show, the words and the button re-scaled for the frame, the frame stated as zones inside its safe area, never the master shrunk onto a background, set as a square or cut. A `size_<size>` node (`static_size`) brings it to Meta's exact pixels (1:1 at 1080×1080; a landscape size the same way). Every 9:16 Reels or Stories covers then passes a free `static_safe_fit` node (`safe_<size>`), which shrinks and shifts the picture just enough (to 85% at most) to bring a word or the logo a few points past the band back inside it; a layout further out passes unchanged and the run lays it out again. The 9:16 is never extended instead (the 4:5 set whole in the clear band, its scene continued to the frame) nor set over its own blurred copy: rated in October 2026, the first showed the ad small inside a continued scene ("text super tiny") and the second "looks bad". The master shape is one decision in the code (`DEFAULT_MASTER_SHAPE`: `relayout`; the `9x16_band` and `4x5_adapt` shapes, kept for the bench, make the sizes by cutting or reframing the master, and the person rating them rejected every such way; a canvas from the retired band master, which extended a square master into the 9:16, is upgraded to the re-layout graph on its next run). Every size then gets the brand's logo file placed on it last (`logo_<size>`, a `static_logo` tagged `logo_composite`); on one picture, in the slot that size's words left empty (`format_<size>`'s `logo_slot`).
6297
- - **One picture, every size (the default for a photo-led ad, ADR 0014).** A designed or interface ad (one keeping the whole or the layout of a proven chat, screenshot, table, grid or text-card ad, or lettering words in chat bubbles or on a screen) is laid out again for each size instead; the canvas records why (`metadata.master_shape_reason`) and the scaffold and `baker creatives make` print it (`master_shape`). The first `baker canvas run` of a creative never rendered routes it once more on the prompt.json written since (`[shape]` note), since make routed it on the proven ad's drafted words. Each variant is a picture drawn without the ad's designed words (`plate_<i>`, `static_variant_call`, at the widest ratio the model draws that every tall size can be cut from: 9:16 for Meta's 4:5 and 9:16), its brief stating where the people and products stay and where the words go, the scene's own words (a chat on a phone, a label) kept. Each picture gets its lead size's words (`lead_<i>`, a `static_format` tagged `static_lead`), so the options shown are finished ads; the prefilter chooses among them and passes on the chosen one's picture (`plate`), dropping a variant whose picture cannot give every size without cutting a face or the product before any words are paid for. Every size, the lead included (`format_<size>`, `static_format`), is then cut from that picture with its words set in the lead's look; the lead's replays its option's paid words, free. A size's words call is told each line's `look` whole at every trim level (a tag, a pill, the ✗ or ✓ beside it), and the marks a look names are no stray icon, so a before/after's ✗/✓ proof is set with its lines. A wide size beside tall ones is cut from the chosen picture extended sideways (`plate_wide`, `static_plate`). A change of the words or colours in `prompt.json` keeps the pictures and the choice and sets only the words again; `--fix` corrects the picture itself (`edit_<n>`) and every size's words are set on it again; `--regenerate format_<size>` sets one size's words again on the same picture; a run correcting itself sets a size's words again for a misread line or words under the interface, and corrects the picture only for a defect of the picture. An ad of a series led by an ad on one picture draws no picture: every size is cut from the lead's picture with its own words. An existing creative stays on the shape it was made on through every run, fix, edit, ad of its series and translation. Scaffolded again with `--master-shape plate`, an approved creative made before plates moves onto one picture: its approved size is kept byte for byte, every other size cut from its picture with the words painted out (`static_plate` `from_ad`), refused when more than a quarter of its frame would be painted. `baker canvas validate` refuses a size's words set on a finished ad (`STATIC_FORMAT_PLATE_SOURCE`) and a canvas mixing these sizes with the re-layout, safe-fit or placing steps (`STATIC_MIXED_SIZE_PIPELINE`).
6296
+ - **Every placement format is made from the chosen picture, and no size is a crop or a paste of another.** On an ad laid out again for each size (every new ad; `--master-shape plate` asks for one picture instead), the pick goes to exact pixels as the master (an `imagemagick` node tagged `static_master`) at the lead size: the 4:5 when a feed size is delivered, else the square, else the first portrait size, rendered at the model's nearest ratio (3:4 on GPT Image) and filled to Meta's pixels. It is delivered as it is. Every other size (9:16, 1:1, 16:9, …) is **re-laid out** from the master by one request (`image_generate` node `adapt_<size>`, tagged `static_adapt_format`, on the router's `relayout` model, GPT Image 2.5 at quality `xhigh`): the master, the variants' own product and people pictures and the logo file as references and a brief compiled from `prompt.json` for that frame on every run (`static-adapt.ts`): the exact lines and their hierarchy, the same people (same face, same clothes), product and label, logo and none the chosen ad does not show, the words and the button re-scaled for the frame, the frame stated as zones inside its safe area, never the master shrunk onto a background, set as a square or cut. A `size_<size>` node (`static_size`) brings it to Meta's exact pixels (1:1 at 1080×1080; a landscape size the same way). Every 9:16 Reels or Stories covers then passes a free `static_safe_fit` node (`safe_<size>`), which shrinks and shifts the picture just enough (to 85% at most) to bring a word or the logo a few points past the band back inside it; a layout further out passes unchanged and the run lays it out again. The 9:16 is never extended instead (the 4:5 set whole in the clear band, its scene continued to the frame) nor set over its own blurred copy: rated in October 2026, the first showed the ad small inside a continued scene ("text super tiny") and the second "looks bad". The master shape is one decision in the code (`DEFAULT_MASTER_SHAPE`: `relayout`; the `9x16_band` and `4x5_adapt` shapes, kept for the bench, make the sizes by cutting or reframing the master, and the person rating them rejected every such way; a canvas from the retired band master, which extended a square master into the 9:16, is upgraded to the re-layout graph on its next run). The model draws the brand's logo from its file, handed to every render beside the reference, so the ad is designed around it; the review holds the drawn logo to its file. A canvas made with the logo placed from its file keeps it placed (`logo_<size>`, a `static_logo` tagged `logo_composite`); on one picture, in the slot that size's words left empty (`format_<size>`'s `logo_slot`).
6297
+ - **One picture, every size (`--master-shape plate`, ADR 0014).** It was the default for a photo-led ad until its ads came back stamped (words set by a second call, the logo pasted); every new ad is now drawn whole and laid out again for each size. The canvas records which kind of ad it read (photo-led, designed or interface: one keeping the whole or the layout of a proven chat, screenshot, table, grid or text-card ad, or lettering words in chat bubbles or on a screen) and why (`metadata.master_shape_reason`) and the scaffold and `baker creatives make` print it (`master_shape`). The first `baker canvas run` of a creative never rendered routes it once more on the prompt.json written since (`[shape]` note), which also moves one made on one picture without asking for it back to the re-laid-out shape. Each variant is a picture drawn without the ad's designed words (`plate_<i>`, `static_variant_call`, at the widest ratio the model draws that every tall size can be cut from: 9:16 for Meta's 4:5 and 9:16), its brief stating where the people and products stay and where the words go, the scene's own words (a chat on a phone, a label) kept. Each picture gets its lead size's words (`lead_<i>`, a `static_format` tagged `static_lead`), so the options shown are finished ads; the prefilter chooses among them and passes on the chosen one's picture (`plate`), dropping a variant whose picture cannot give every size without cutting a face or the product before any words are paid for. Every size, the lead included (`format_<size>`, `static_format`), is then cut from that picture with its words set in the lead's look; the lead's replays its option's paid words, free. A size's words call is told each line's `look` whole at every trim level (a tag, a pill, the ✗ or ✓ beside it), and the marks a look names are no stray icon, so a before/after's ✗/✓ proof is set with its lines. A wide size beside tall ones is cut from the chosen picture extended sideways (`plate_wide`, `static_plate`). A change of the words or colours in `prompt.json` keeps the pictures and the choice and sets only the words again; `--fix` corrects the picture itself (`edit_<n>`) and every size's words are set on it again; `--regenerate format_<size>` sets one size's words again on the same picture; a run correcting itself sets a size's words again for a misread line or words under the interface, and corrects the picture only for a defect of the picture. An ad of a series led by an ad on one picture draws no picture: every size is cut from the lead's picture with its own words. An existing creative stays on the shape it was made on through every run, fix, edit, ad of its series and translation. Scaffolded again with `--master-shape plate`, an approved creative made before plates moves onto one picture: its approved size is kept byte for byte, every other size cut from its picture with the words painted out (`static_plate` `from_ad`), refused when more than a quarter of its frame would be painted. `baker canvas validate` refuses a size's words set on a finished ad (`STATIC_FORMAT_PLATE_SOURCE`) and a canvas mixing these sizes with the re-layout, safe-fit or placing steps (`STATIC_MIXED_SIZE_PIPELINE`).
6298
6298
  - Readers go by these roles, never by node ids: the run review, `--fix`, `--use-variant`, `--regenerate static_variants`, and the dashboard. A static ad scaffolded before this graph (`ad_a`/`ad_b`/`ad_c`, `pick`, re-lays `ad_4x5`/`ad_9x16`, `fit_4x5`) is **upgraded in place** on its next `validate` or `run`: prompt.json, every asset slot, the model and the formats are kept, the render part becomes this graph, and the creative is not reported as changed by the upgrade. Its old runs stay readable.
6299
6299
  - Detected elements are **never slots to fill**: the source's people, products, animals and badges are its world. The original image stays on the canvas as the record of the source; when `prompt.json` keeps its concept (`concept.verdict` `use` or `adapt`) the first renders are shown it **for its structure only**, and otherwise it never reaches a render. Only the people cast from a company Avatar and the brand's logo become pictures on the canvas; everything else is described in words (a picture of ours is added through a reference's `image`). A **person** with no avatar needs no slot and no casting: the render draws an invented, realistic person from the element's description. With `--avatar <handle>` (for an actor who recurs across ads) the slot ingests the Avatar's identity sheet and `prompt.json` describes that person in the Avatar's own words. A reference's `avatar` names who was cast and draws no face on its own: its face reaches the render only with a picture behind it (the `--avatar` slot, or the identity sheet URL from `baker avatars get <handle>` as the reference's `image`).
6300
6300
 
@@ -25,22 +25,22 @@ Subcommands:
25
25
  Full guide: __tooling__/docs/tools/baker/canvas.md`
26
26
  },
27
27
  subCommands: {
28
- run: () => import("./run-IIU2UVQU.js").then((m) => m.runCommand),
29
- rerun: () => import("./rerun-NR54OG3M.js").then((m) => m.rerunCommand),
30
- validate: () => import("./validate-PEWGXGCR.js").then((m) => m.validateCommand),
31
- critique: () => import("./critique-A2OEMGZS.js").then((m) => m.critiqueCommand),
28
+ run: () => import("./run-AIBAPD4R.js").then((m) => m.runCommand),
29
+ rerun: () => import("./rerun-GFMBT6NJ.js").then((m) => m.rerunCommand),
30
+ validate: () => import("./validate-2NRSRRMM.js").then((m) => m.validateCommand),
31
+ critique: () => import("./critique-43OVWKN7.js").then((m) => m.critiqueCommand),
32
32
  "viewer-check": () => import("./viewer-check-TSH3SAYJ.js").then((m) => m.viewerCheckCommand),
33
- catalog: () => import("./catalog-RMMECI4Z.js").then((m) => m.catalogCommand),
33
+ catalog: () => import("./catalog-UPF4SOBW.js").then((m) => m.catalogCommand),
34
34
  inspect: () => import("./inspect-BMQTTKMW.js").then((m) => m.inspectCommand),
35
- "scaffold-ad": () => import("./scaffold-ad-3VMAP5BF.js").then((m) => m.scaffoldAdCommand),
36
- "scaffold-video": () => import("./scaffold-video-SBSSR7TZ.js").then((m) => m.scaffoldVideoCommand),
37
- "scaffold-static-ad": () => import("./scaffold-static-ad-Z7LO7PKI.js").then((m) => m.scaffoldStaticAdCommand),
38
- "set-prompt": () => import("./set-prompt-RHMZ7SFC.js").then((m) => m.setPromptCommand),
39
- "sync-definition": () => import("./sync-definition-5JO7QXX3.js").then((m) => m.syncDefinitionCommand),
40
- stale: () => import("./stale-OIOOAP2S.js").then((m) => m.staleCommand)
35
+ "scaffold-ad": () => import("./scaffold-ad-7L6D6PKO.js").then((m) => m.scaffoldAdCommand),
36
+ "scaffold-video": () => import("./scaffold-video-A3KAT6QO.js").then((m) => m.scaffoldVideoCommand),
37
+ "scaffold-static-ad": () => import("./scaffold-static-ad-V2H7TOG6.js").then((m) => m.scaffoldStaticAdCommand),
38
+ "set-prompt": () => import("./set-prompt-VLGJG5WO.js").then((m) => m.setPromptCommand),
39
+ "sync-definition": () => import("./sync-definition-VSR72E7E.js").then((m) => m.syncDefinitionCommand),
40
+ stale: () => import("./stale-NT2JRQHY.js").then((m) => m.staleCommand)
41
41
  }
42
42
  });
43
43
  export {
44
44
  canvasCommand
45
45
  };
46
- //# sourceMappingURL=canvas-BPACZHNU.js.map
46
+ //# sourceMappingURL=canvas-4T64K6PQ.js.map
@@ -2,8 +2,8 @@ import {
2
2
  catalogView,
3
3
  defaultRegistry,
4
4
  generateCatalog
5
- } from "./chunk-HJQ7N7FC.js";
6
- import "./chunk-D4I3KULJ.js";
5
+ } from "./chunk-LJNYZLOH.js";
6
+ import "./chunk-ELL2IQMF.js";
7
7
  import "./chunk-5NS4CNF6.js";
8
8
  import "./chunk-DGCUPJZD.js";
9
9
  import "./chunk-6KK26F2T.js";
@@ -62,4 +62,4 @@ var catalogCommand = defineCommand({
62
62
  export {
63
63
  catalogCommand
64
64
  };
65
- //# sourceMappingURL=catalog-RMMECI4Z.js.map
65
+ //# sourceMappingURL=catalog-UPF4SOBW.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseRefExpr
3
- } from "./chunk-D4I3KULJ.js";
3
+ } from "./chunk-ELL2IQMF.js";
4
4
  import {
5
5
  collectAssetRefLikes,
6
6
  isPersistedAssetRef
@@ -164,4 +164,4 @@ export {
164
164
  buildInitialRunRecord,
165
165
  buildFailedRunRecord
166
166
  };
167
- //# sourceMappingURL=chunk-65PUXMWE.js.map
167
+ //# sourceMappingURL=chunk-4VWTIT5C.js.map
@@ -98,7 +98,7 @@ import {
98
98
  staticRenderNodes,
99
99
  trimmedRenderBand,
100
100
  wordCount
101
- } from "./chunk-D4I3KULJ.js";
101
+ } from "./chunk-ELL2IQMF.js";
102
102
  import {
103
103
  blockingArea
104
104
  } from "./chunk-5NS4CNF6.js";
@@ -1230,6 +1230,7 @@ function plateFixPrompt(instruction, opts = {}) {
1230
1230
  }
1231
1231
 
1232
1232
  // src/engine/scaffold/static-shape-route.ts
1233
+ var PHOTO_LED_SHAPE = "relayout";
1233
1234
  var DESIGNED_PATTERNS = /* @__PURE__ */ new Set([
1234
1235
  "headline_card",
1235
1236
  "infographic",
@@ -1291,7 +1292,10 @@ function staticShapeFor(spec) {
1291
1292
  reason: `interface: the scene is an interface ("${named2}"), so every size is laid out again`
1292
1293
  };
1293
1294
  }
1294
- return { shape: "plate", reason: "photo-led: every size is cut from one picture, its words set for each size" };
1295
+ return {
1296
+ shape: PHOTO_LED_SHAPE,
1297
+ reason: "photo-led: the ad is drawn whole, logo and words included, and laid out again for each size"
1298
+ };
1295
1299
  }
1296
1300
  function recordedShape(canvas) {
1297
1301
  const holder = canvas ?? {};
@@ -3130,6 +3134,7 @@ async function syncLogoPlacement(canvas, log) {
3130
3134
  const source = blueprintPath(nodes);
3131
3135
  if (!source || !isStaticBriefCanvas(canvas)) return "not_applicable";
3132
3136
  if (!readAdSpec(JSON.parse(await readFile3(source, "utf8"))).logo) return "up_to_date";
3137
+ if (!nodes.some((n) => n.tags?.role === LOGO_COMPOSITE_ROLE)) return "up_to_date";
3133
3138
  const logo = nodes.find((n) => n.type === "ingest" && n.tags?.role === BRAND_LOGO_ROLE);
3134
3139
  const file = logo?.params?.source === "path" && typeof logo.params.path === "string" ? logo.params.path : null;
3135
3140
  if (!logo || !file) return "up_to_date";
@@ -7205,12 +7210,12 @@ var LABEL_JOB = new Map(
7205
7210
  Object.entries(JOB_LABEL).map(([job, label]) => [label.toLowerCase(), job])
7206
7211
  );
7207
7212
  var DECISION_LINE = /^-\s*(Stops the scroll|Proof|Also carries it)\s*:\s*(.+)$/i;
7208
- var DECIDED = /^(.+?)\s+(?:—|–|--?)\s+((?:\[TODO|keep\b|replace|impossible\b).*)$/i;
7213
+ var DECIDED = /^(.+?)(?:\s+(?:—|–|--?)|\s*[—–:.])\s+((?:\[TODO|(?:keep|keeps|kept|impossible)\b|replace).*)$/i;
7209
7214
  var DASHED = /^(.+?)\s+(?:—|–|--?)\s+(.*)$/;
7210
7215
  function readDecision(said) {
7211
7216
  const text3 = said.trim();
7212
7217
  if (text3.startsWith(OPEN)) return { decision: "open" };
7213
- if (/^keep\b/i.test(text3)) return { decision: "keep" };
7218
+ if (/^(?:keep|keeps|kept)\b/i.test(text3)) return { decision: "keep" };
7214
7219
  const replaced = /^replace[ds]?\b(?:\s+(?:it|them))?(?:\s+(?:with|by)\s+|\s*:\s*)(.+)$/i.exec(text3)?.[1]?.trim().replace(/\.$/, "");
7215
7220
  if (replaced) return { decision: "replace", ours: replaced };
7216
7221
  if (/^impossible\b/i.test(text3)) {
@@ -8141,6 +8146,7 @@ var ROLE = {
8141
8146
  };
8142
8147
  var SURVIVES2 = /^###\s+What survives and what changes\s*$/im;
8143
8148
  var norm2 = (text3) => text3.trim().toLowerCase().replace(/\s+/g, " ").replace(/^["'“”«»]+|["'“”«».]+$/g, "").trim();
8149
+ var LABEL = { stop: "Stops the scroll", proof: "Proof" };
8144
8150
  var because = (rule) => rule?.trim() ? ` (a company rule: "${rule.trim()}")` : "";
8145
8151
  function specProblems(l, spec) {
8146
8152
  const said = `"${l.element}", ${ROLE[l.job]}`;
@@ -8196,6 +8202,17 @@ function droppedProblems(lines, concept) {
8196
8202
  return lines.flatMap((d) => {
8197
8203
  if (d.job === "other") return [];
8198
8204
  const held = concept.load_bearing.find((l) => norm2(l.element) === norm2(d.element));
8205
+ if (!held && d.decision === "unclear") {
8206
+ const job = d.job;
8207
+ const shapes = concept.load_bearing.filter((l) => l.job === job).map((l) => `"- ${LABEL[job]}: ${l.element} \u2014 replace with <what of ours>" (or "\u2014 keep", or "\u2014 impossible: "<rule>"")`);
8208
+ return [
8209
+ {
8210
+ element: d.element,
8211
+ unreadable: true,
8212
+ text: `"${d.element}" under "What survives and what changes" is no decision line: write ${shapes.join(" and ") || "the element's words, a dash, then keep, replace with \u2026 or impossible"}, the element's words exactly as prompt.json's concept has them.`
8213
+ }
8214
+ ];
8215
+ }
8199
8216
  if (!held) {
8200
8217
  return [
8201
8218
  {
@@ -8225,7 +8242,7 @@ function uncastReplacements(definition, vital) {
8225
8242
  }
8226
8243
  function keepFix(problems, idea) {
8227
8244
  const lost = new Set(problems.filter((p) => p.another).map((p) => p.element));
8228
- const kept = new Set(problems.filter((p) => !lost.has(p.element)).map((p) => p.element));
8245
+ const kept = new Set(problems.filter((p) => !lost.has(p.element) && !p.unreadable).map((p) => p.element));
8229
8246
  const quoted = (elements) => [...elements].map((e) => `"${e}"`).join(", ");
8230
8247
  const another = quoted(lost);
8231
8248
  const named2 = quoted(kept);
@@ -8840,4 +8857,4 @@ export {
8840
8857
  prepareCanvas,
8841
8858
  exitOnPrepareError
8842
8859
  };
8843
- //# sourceMappingURL=chunk-7HJR5M4R.js.map
8860
+ //# sourceMappingURL=chunk-5RCCESDX.js.map