@koda-sl/baker-cli 0.495.0-dev.c2b64ccca → 0.498.0-dev.e2ca08ce6
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.
- package/dist/{brief-B7C3SYDP.js → brief-ECEIKJFK.js} +5 -5
- package/dist/{canvas-XCP2WPYO.js → canvas-MNZLQZ74.js} +12 -12
- package/dist/{check-NDPR32FH.js → check-R7IMU36G.js} +3 -3
- package/dist/{chunk-7XH4UQWS.js → chunk-5OAFKP6L.js} +5 -2
- package/dist/{chunk-7XH4UQWS.js.map → chunk-5OAFKP6L.js.map} +1 -1
- package/dist/{chunk-SQQ4VBNI.js → chunk-5OLCELV7.js} +2 -2
- package/dist/{chunk-IGOOLUE2.js → chunk-A7MTWTMM.js} +3 -3
- package/dist/{chunk-WZDKROPU.js → chunk-FHYUIYU5.js} +2 -2
- package/dist/{chunk-PPMSWLQC.js → chunk-GPWT42O3.js} +2 -2
- package/dist/{chunk-G2AZCDX4.js → chunk-HGTLGFKV.js} +3 -3
- package/dist/{chunk-QAOEVZYY.js → chunk-HY6DIWDV.js} +4 -4
- package/dist/{chunk-7FDDOOM4.js → chunk-IBJA2A4E.js} +2 -2
- package/dist/{chunk-OFTU5XBQ.js → chunk-MKL7CL4P.js} +2 -2
- package/dist/{chunk-3AOMKBDC.js → chunk-NDIBFOFB.js} +8 -8
- package/dist/{chunk-72QBQS63.js → chunk-OTLYELOW.js} +2 -2
- package/dist/{chunk-T5HB6R4I.js → chunk-T235G4C7.js} +2 -2
- package/dist/{chunk-BVMXTD4B.js → chunk-ZPMGYAYZ.js} +3 -3
- package/dist/{chunk-BVMXTD4B.js.map → chunk-ZPMGYAYZ.js.map} +1 -1
- package/dist/cli.js +3 -3
- package/dist/{creatives-NJ7SZ3S4.js → creatives-NHH4J2XW.js} +9 -9
- package/dist/{critique-LINKOKOE.js → critique-7OGWN5Q4.js} +4 -4
- package/dist/{feedback-HT2M63QW.js → feedback-XF2TOS4P.js} +2 -2
- package/dist/{feedback-record-V7UU6CG3.js → feedback-record-YPNGCJWS.js} +6 -6
- package/dist/{fit-S5ZGPNE3.js → fit-INDYA6KP.js} +3 -3
- package/dist/{ideas-HHTLPC6Y.js → ideas-ZFODHYVK.js} +3 -3
- package/dist/{make-4XCSN76S.js → make-GJPQRROG.js} +10 -10
- package/dist/{rerun-NWOGRYHU.js → rerun-77XP2DAH.js} +9 -9
- package/dist/{rules-DSLE6LVU.js → rules-HJGFA6OT.js} +4 -4
- package/dist/{rules-add-NBOMKFAI.js → rules-add-3YBX7UGL.js} +7 -5
- package/dist/rules-add-3YBX7UGL.js.map +1 -0
- package/dist/{run-IRJWKZ3K.js → run-WBFKGA42.js} +9 -9
- package/dist/{scaffold-ad-3BSA7BWT.js → scaffold-ad-6YO574PA.js} +6 -6
- package/dist/{scaffold-static-ad-Q26LLBWQ.js → scaffold-static-ad-WBC2U5U4.js} +11 -11
- package/dist/{scaffold-video-WE52C7YZ.js → scaffold-video-2ZJMQMEZ.js} +6 -6
- package/dist/{select-4IQQNKLN.js → select-ONK2GLKS.js} +6 -6
- package/dist/{set-prompt-DE4TD2V7.js → set-prompt-KQSUL77B.js} +4 -4
- package/dist/{stale-E34H24C3.js → stale-G25M2P7Q.js} +5 -5
- package/dist/{sync-definition-DTWEW6ZC.js → sync-definition-PGVMZTOX.js} +6 -6
- package/dist/{translate-V6XPDX4M.js → translate-SI6LSIY6.js} +8 -8
- package/dist/{validate-VTAKQGD5.js → validate-QNBI74BM.js} +4 -4
- package/dist/{viewer-check-3T2EZOSH.js → viewer-check-TEQ3WP2N.js} +4 -4
- package/dist/{winning-ads-F765TFLS.js → winning-ads-EVOFUO3N.js} +2 -2
- package/package.json +1 -1
- package/dist/rules-add-NBOMKFAI.js.map +0 -1
- /package/dist/{brief-B7C3SYDP.js.map → brief-ECEIKJFK.js.map} +0 -0
- /package/dist/{canvas-XCP2WPYO.js.map → canvas-MNZLQZ74.js.map} +0 -0
- /package/dist/{check-NDPR32FH.js.map → check-R7IMU36G.js.map} +0 -0
- /package/dist/{chunk-SQQ4VBNI.js.map → chunk-5OLCELV7.js.map} +0 -0
- /package/dist/{chunk-IGOOLUE2.js.map → chunk-A7MTWTMM.js.map} +0 -0
- /package/dist/{chunk-WZDKROPU.js.map → chunk-FHYUIYU5.js.map} +0 -0
- /package/dist/{chunk-PPMSWLQC.js.map → chunk-GPWT42O3.js.map} +0 -0
- /package/dist/{chunk-G2AZCDX4.js.map → chunk-HGTLGFKV.js.map} +0 -0
- /package/dist/{chunk-QAOEVZYY.js.map → chunk-HY6DIWDV.js.map} +0 -0
- /package/dist/{chunk-7FDDOOM4.js.map → chunk-IBJA2A4E.js.map} +0 -0
- /package/dist/{chunk-OFTU5XBQ.js.map → chunk-MKL7CL4P.js.map} +0 -0
- /package/dist/{chunk-3AOMKBDC.js.map → chunk-NDIBFOFB.js.map} +0 -0
- /package/dist/{chunk-72QBQS63.js.map → chunk-OTLYELOW.js.map} +0 -0
- /package/dist/{chunk-T5HB6R4I.js.map → chunk-T235G4C7.js.map} +0 -0
- /package/dist/{creatives-NJ7SZ3S4.js.map → creatives-NHH4J2XW.js.map} +0 -0
- /package/dist/{critique-LINKOKOE.js.map → critique-7OGWN5Q4.js.map} +0 -0
- /package/dist/{feedback-HT2M63QW.js.map → feedback-XF2TOS4P.js.map} +0 -0
- /package/dist/{feedback-record-V7UU6CG3.js.map → feedback-record-YPNGCJWS.js.map} +0 -0
- /package/dist/{fit-S5ZGPNE3.js.map → fit-INDYA6KP.js.map} +0 -0
- /package/dist/{ideas-HHTLPC6Y.js.map → ideas-ZFODHYVK.js.map} +0 -0
- /package/dist/{make-4XCSN76S.js.map → make-GJPQRROG.js.map} +0 -0
- /package/dist/{rerun-NWOGRYHU.js.map → rerun-77XP2DAH.js.map} +0 -0
- /package/dist/{rules-DSLE6LVU.js.map → rules-HJGFA6OT.js.map} +0 -0
- /package/dist/{run-IRJWKZ3K.js.map → run-WBFKGA42.js.map} +0 -0
- /package/dist/{scaffold-ad-3BSA7BWT.js.map → scaffold-ad-6YO574PA.js.map} +0 -0
- /package/dist/{scaffold-static-ad-Q26LLBWQ.js.map → scaffold-static-ad-WBC2U5U4.js.map} +0 -0
- /package/dist/{scaffold-video-WE52C7YZ.js.map → scaffold-video-2ZJMQMEZ.js.map} +0 -0
- /package/dist/{select-4IQQNKLN.js.map → select-ONK2GLKS.js.map} +0 -0
- /package/dist/{set-prompt-DE4TD2V7.js.map → set-prompt-KQSUL77B.js.map} +0 -0
- /package/dist/{stale-E34H24C3.js.map → stale-G25M2P7Q.js.map} +0 -0
- /package/dist/{sync-definition-DTWEW6ZC.js.map → sync-definition-PGVMZTOX.js.map} +0 -0
- /package/dist/{translate-V6XPDX4M.js.map → translate-SI6LSIY6.js.map} +0 -0
- /package/dist/{validate-VTAKQGD5.js.map → validate-QNBI74BM.js.map} +0 -0
- /package/dist/{viewer-check-3T2EZOSH.js.map → viewer-check-TEQ3WP2N.js.map} +0 -0
- /package/dist/{winning-ads-F765TFLS.js.map → winning-ads-EVOFUO3N.js.map} +0 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
buildNext,
|
|
9
9
|
ideasReport
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-GPWT42O3.js";
|
|
11
11
|
import {
|
|
12
12
|
buildSearchBody
|
|
13
13
|
} from "./chunk-RVXN2YTM.js";
|
|
@@ -21,13 +21,13 @@ import {
|
|
|
21
21
|
} from "./chunk-B2SAOVMU.js";
|
|
22
22
|
import {
|
|
23
23
|
liveViewerLookups
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import "./chunk-
|
|
24
|
+
} from "./chunk-A7MTWTMM.js";
|
|
25
|
+
import "./chunk-ZPMGYAYZ.js";
|
|
26
26
|
import "./chunk-AN7CMLYY.js";
|
|
27
27
|
import {
|
|
28
28
|
readAllRules,
|
|
29
29
|
readCompanyReading
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-5OAFKP6L.js";
|
|
31
31
|
import {
|
|
32
32
|
AWARENESS_STAGES,
|
|
33
33
|
IDEA_ANGLES,
|
|
@@ -534,4 +534,4 @@ export {
|
|
|
534
534
|
tastePart,
|
|
535
535
|
whatWorks
|
|
536
536
|
};
|
|
537
|
-
//# sourceMappingURL=brief-
|
|
537
|
+
//# sourceMappingURL=brief-ECEIKJFK.js.map
|
|
@@ -25,22 +25,22 @@ Subcommands:
|
|
|
25
25
|
Full guide: __tooling__/docs/tools/baker/canvas.md`
|
|
26
26
|
},
|
|
27
27
|
subCommands: {
|
|
28
|
-
run: () => import("./run-
|
|
29
|
-
rerun: () => import("./rerun-
|
|
30
|
-
validate: () => import("./validate-
|
|
31
|
-
critique: () => import("./critique-
|
|
32
|
-
"viewer-check": () => import("./viewer-check-
|
|
28
|
+
run: () => import("./run-WBFKGA42.js").then((m) => m.runCommand),
|
|
29
|
+
rerun: () => import("./rerun-77XP2DAH.js").then((m) => m.rerunCommand),
|
|
30
|
+
validate: () => import("./validate-QNBI74BM.js").then((m) => m.validateCommand),
|
|
31
|
+
critique: () => import("./critique-7OGWN5Q4.js").then((m) => m.critiqueCommand),
|
|
32
|
+
"viewer-check": () => import("./viewer-check-TEQ3WP2N.js").then((m) => m.viewerCheckCommand),
|
|
33
33
|
catalog: () => import("./catalog-LTH6SDYM.js").then((m) => m.catalogCommand),
|
|
34
34
|
inspect: () => import("./inspect-2IASA5YV.js").then((m) => m.inspectCommand),
|
|
35
|
-
"scaffold-ad": () => import("./scaffold-ad-
|
|
36
|
-
"scaffold-video": () => import("./scaffold-video-
|
|
37
|
-
"scaffold-static-ad": () => import("./scaffold-static-ad-
|
|
38
|
-
"set-prompt": () => import("./set-prompt-
|
|
39
|
-
"sync-definition": () => import("./sync-definition-
|
|
40
|
-
stale: () => import("./stale-
|
|
35
|
+
"scaffold-ad": () => import("./scaffold-ad-6YO574PA.js").then((m) => m.scaffoldAdCommand),
|
|
36
|
+
"scaffold-video": () => import("./scaffold-video-2ZJMQMEZ.js").then((m) => m.scaffoldVideoCommand),
|
|
37
|
+
"scaffold-static-ad": () => import("./scaffold-static-ad-WBC2U5U4.js").then((m) => m.scaffoldStaticAdCommand),
|
|
38
|
+
"set-prompt": () => import("./set-prompt-KQSUL77B.js").then((m) => m.setPromptCommand),
|
|
39
|
+
"sync-definition": () => import("./sync-definition-PGVMZTOX.js").then((m) => m.syncDefinitionCommand),
|
|
40
|
+
stale: () => import("./stale-G25M2P7Q.js").then((m) => m.staleCommand)
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
43
|
export {
|
|
44
44
|
canvasCommand
|
|
45
45
|
};
|
|
46
|
-
//# sourceMappingURL=canvas-
|
|
46
|
+
//# sourceMappingURL=canvas-MNZLQZ74.js.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readAdWords,
|
|
3
3
|
ruleRefusal
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZPMGYAYZ.js";
|
|
5
5
|
import "./chunk-AN7CMLYY.js";
|
|
6
6
|
import {
|
|
7
7
|
readAllRules
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5OAFKP6L.js";
|
|
9
9
|
import {
|
|
10
10
|
ruleTells
|
|
11
11
|
} from "./chunk-S4ROJTDY.js";
|
|
@@ -99,4 +99,4 @@ export {
|
|
|
99
99
|
checkCommand,
|
|
100
100
|
checkCreatives
|
|
101
101
|
};
|
|
102
|
-
//# sourceMappingURL=check-
|
|
102
|
+
//# sourceMappingURL=check-R7IMU36G.js.map
|
|
@@ -198,7 +198,10 @@ function ruleSections(brandMd) {
|
|
|
198
198
|
const fence = /```generation-style/.test(brandMd) ? ["the ```generation-style block"] : [];
|
|
199
199
|
return [.../* @__PURE__ */ new Set([...headings, ...fence])];
|
|
200
200
|
}
|
|
201
|
-
|
|
201
|
+
var PRIVATE_QUOTE_LINE = "Never publish a customer quote or an identifiable customer conversation.";
|
|
202
|
+
var PRIVATE_QUOTE_MEANT = "Never publish what a customer said in a private call, chat or note, nor a conversation that identifies them. A testimonial, review or case study the customer published or agreed to is not a private call: an ad may quote it.";
|
|
203
|
+
function brandRulesPrompt(document) {
|
|
204
|
+
const brandMd = document.replaceAll(PRIVATE_QUOTE_LINE, PRIVATE_QUOTE_MEANT);
|
|
202
205
|
const sections = ruleSections(brandMd);
|
|
203
206
|
return [
|
|
204
207
|
"You read a company's brand guidelines (BRAND.md), written in any language, for the people who make its ads. List every standing rule they set that an ad, its words, its pictures, its people or its logo must follow. Extract only what they state; never invent a rule.",
|
|
@@ -299,4 +302,4 @@ export {
|
|
|
299
302
|
workspaceOfCreative,
|
|
300
303
|
avoidOf
|
|
301
304
|
};
|
|
302
|
-
//# sourceMappingURL=chunk-
|
|
305
|
+
//# sourceMappingURL=chunk-5OAFKP6L.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/engine/creative-context/company.ts","../src/engine/creative-context/case-file.ts","../src/engine/creative-context/index.ts","../src/engine/brand/brand-rules.ts"],"sourcesContent":["/**\n * What the company is, read once per COMPANY.md and the same for every ad and every step: what it\n * sells and to whom (in its own language, for the people who judge an ad as its viewer), what an\n * ads library files that under (in English, for every search), its industry, its buyer, the\n * country it sells in, whether its ads may joke, and its own brand names. One small model reads it,\n * pinned and kept by the document's content. Prod, Oct 2026: COMPANY.md was read in ten places,\n * each cut differently (whole, 12k, 6k, 800 characters, its first line), and its first line gave\n * the library \"https\", a brand name or a table header for 6 of 14 companies.\n */\nimport { createHash } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\nimport { holdUnbilledSpend } from \"../../commands/canvas/unbilled-spend.ts\";\nimport { askAboutText, type ReviewUsage } from \"../nodes/local/lib/render-review.ts\";\n\nexport type CompanyBuyer = \"consumer\" | \"small_business\" | \"enterprise\";\nexport type CompanyHumour = \"fits\" | \"risky\" | \"off_brand\";\n\nexport type CompanyReading = {\n /** What the company sells and to whom, one plain sentence in its own language. */\n sells?: string;\n /** What an ads library files it under, 2-6 plain English words: \"trade school training program\". */\n category?: string;\n /** The broader industry, in English: \"vocational education\". */\n industry?: string;\n buyer?: CompanyBuyer;\n /** ISO country it sells in; null when it sells in several. */\n country?: string | null;\n humour?: CompanyHumour;\n /** Its own brand and product names: its own ads are never a reference for a new one. */\n brands: string[];\n};\n\nconst text = z\n .string()\n .transform((s) => s.trim())\n .pipe(z.string().min(1).max(300))\n .optional()\n .catch(undefined);\n\nconst Answer = z.object({\n sells: text,\n category: text,\n industry: text,\n buyer: z.enum([\"consumer\", \"small_business\", \"enterprise\"]).optional().catch(undefined),\n country: z\n .string()\n .transform((s) => s.trim().toUpperCase())\n .pipe(z.union([z.literal(\"\").transform(() => null), z.string().regex(/^[A-Z]{2}$/)]))\n .optional()\n .catch(undefined),\n humour: z.enum([\"fits\", \"risky\", \"off_brand\"]).optional().catch(undefined),\n brands: z\n .array(z.string())\n .transform((list) =>\n list\n .map((s) => s.trim())\n .filter(Boolean)\n .slice(0, 4),\n )\n .catch([]),\n});\n\n/** The model's answer as a reading, or null when it does not say what the company sells. Pure. */\nfunction parseCompanyReading(answer: unknown): CompanyReading | null {\n const parsed = Answer.safeParse(answer);\n if (!parsed.success || !parsed.data.category) return null;\n const { brands, ...rest } = parsed.data;\n return { ...Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined)), brands };\n}\n\n/** Pinned like the brand reading: the same notes read the same on every run. */\nexport const COMPANY_READING_MODEL = \"google/gemini-3.5-flash-lite\";\nconst REQUEST = {\n provider: { order: [\"google-vertex\"], allow_fallbacks: false },\n seed: 0,\n temperature: 0,\n response_format: { type: \"json_object\" },\n};\nconst PROMPT_VERSION = 1;\nconst TIMEOUT_MS = 20_000;\n/** The notes the reading takes: past this, the rest is detail no step reads. */\nconst COMPANY_CHARS = 12_000;\n\n/** The question the model answers about the company's own notes. Pure. */\nexport function companyReadingPrompt(companyDoc: string): string {\n return `You read a company's own notes (COMPANY.md), in whatever language they are written, for the people who make its ads. Extract only what they say; never invent.\n\n\"\"\"\n${companyDoc.slice(0, COMPANY_CHARS)}\n\"\"\"\n\nAnswer only JSON:\n{\n \"sells\": \"what the company sells and to whom, one plain sentence of at most 20 words in the notes' own language, no brand names (e.g. 'Cursos de electricidad y fontanería para desempleados que buscan un oficio estable')\",\n \"category\": \"what it sells, 2-6 plain English words an ads library would file it under, no brand names (e.g. 'electrician training course', 'pet health insurance', 'ecommerce customer service AI'); for a company selling several lines, the line its notes lead with\",\n \"industry\": \"the broader industry, 1-3 English words (e.g. 'vocational education', 'insurance', 'customer service software')\",\n \"buyer\": \"consumer\" | \"small_business\" | \"enterprise\",\n \"country\": \"the ISO country code it sells in, or '' when several\",\n \"humour\": \"fits\" | \"risky\" | \"off_brand\",\n \"brands\": [\"the company's own brand and product names, as an advertiser name would show them, at most 4\"]\n}`;\n}\n\n/** The company in one line for a question about who would buy from it: what it sells, its industry. Pure. */\nexport function companyLine(reading: CompanyReading | null): string {\n if (!reading) return \"\";\n const what = reading.sells ?? reading.category ?? \"\";\n return reading.industry && what ? `${what} (${reading.industry})` : what;\n}\n\n/** COMPANY.md's first line, past the brand's name, up to whom it is for: what the company sells when no model reads it. Pure. */\nexport function firstLineSells(companyDoc: string): string {\n const first = companyDoc.split(\"\\n\").find((line) => line.trim() && !line.trim().startsWith(\"#\")) ?? \"\";\n // The scaffold's own placeholder (\"TODO — Run /company-discovery…\"): nothing is said yet.\n if (/^\\s*TODO\\b/.test(first)) return \"\";\n const what = first\n .replace(/^\\s*[-*]\\s+/, \"\")\n .replace(/^\\*\\*[^*]+\\*\\*\\s*[—–:-]?\\s*/, \"\")\n .split(/\\s+for\\s+|[,.;:(]|\\s[—–-]\\s/)[0];\n return (what ?? \"\").trim().split(/\\s+/).filter(Boolean).slice(0, SELLS_WORDS).join(\" \");\n}\n/** The most words the first line keeps: the library's judge reads a long query for 45 s and more. */\nconst SELLS_WORDS = 6;\n\n/** Asks the model a prompt and returns its JSON answer, or null when nobody answered. */\nexport type AskCompany = (prompt: string) => Promise<unknown>;\n\n/** The live model, its spend held for the workspace's next bill like every other check's. */\nconst askModel =\n (root: string): AskCompany =>\n async (prompt) => {\n const spent: ReviewUsage[] = [];\n const answer = await askAboutText(COMPANY_READING_MODEL, prompt, (u) => spent.push(u), {\n timeoutMs: TIMEOUT_MS,\n request: REQUEST,\n }).catch(() => null);\n await holdUnbilledSpend(path.join(root, \"canvas\", \".cache\"), spent).catch(() => undefined);\n return answer;\n };\n\n/**\n * The company as every step of an ad reads it: from the cache when COMPANY.md is unchanged, else\n * asked once. With no model, what its first line says it sells, and asked again next time; null\n * when COMPANY.md says nothing yet.\n */\nexport async function readCompanyReading(\n root: string,\n ask: AskCompany = askModel(root),\n): Promise<CompanyReading | null> {\n const doc = await readFile(path.join(root, \"COMPANY.md\"), \"utf8\").catch(() => \"\");\n const fallback = firstLineSells(doc);\n if (!fallback) return null;\n const prompt = companyReadingPrompt(doc);\n const key = createHash(\"sha256\")\n .update(JSON.stringify([PROMPT_VERSION, COMPANY_READING_MODEL, prompt]))\n .digest(\"hex\")\n .slice(0, 16);\n const file = path.join(root, \"canvas\", \".cache\", \"company-reading\", `${key}.json`);\n // Several steps of one ad read the company at once (make: its reference brief and its rules): one question.\n const inFlight = reading.get(file);\n if (inFlight) return await inFlight;\n const read = readOrAsk(file, prompt, ask, fallback).finally(() => reading.delete(file));\n reading.set(file, read);\n return await read;\n}\n\n/** The readings being asked right now, by cache file. */\nconst reading = new Map<string, Promise<CompanyReading>>();\n\n/** The reading kept in `file`, else asked and kept; what the first line says when no model answers. */\nasync function readOrAsk(file: string, prompt: string, ask: AskCompany, fallback: string): Promise<CompanyReading> {\n const cached = await readFile(file, \"utf8\")\n .then((raw) => parseCompanyReading(JSON.parse(raw)))\n .catch(() => null);\n if (cached) return cached;\n const answer = parseCompanyReading(await ask(prompt).catch(() => null));\n if (!answer) return { sells: fallback, brands: [] };\n await mkdir(path.dirname(file), { recursive: true });\n await writeFile(file, `${JSON.stringify(answer, null, 2)}\\n`, \"utf8\");\n return answer;\n}\n","/**\n * One creative's case file: every verdict the people reviewing it gave, with their words whole. A\n * correction (`--fix`, the spec repair, an automatic round) reads it, so a direction the person\n * already rejected never comes back. Prod, Oct 2026: a fix saw only the spec and the findings, and\n * a rejected line (\"oficio\" for \"trabajo\") returned two rounds later.\n */\nimport {\n CREATIVE_FEEDBACK_REASON_LABELS,\n type CreativeFeedbackEntry,\n type CreativesFeedbackResponse,\n} from \"@baker/api\";\nimport { apiPost } from \"../../client.ts\";\n\n/** The case file a prompt is given at most: past it, the oldest rejections are left out. */\nconst CASE_FILE_CHARS = 3000;\n\n/** Each rejection of the creative as one line, newest first, the same words once. Pure. */\nexport function caseFileNotes(entries: readonly CreativeFeedbackEntry[]): string[] {\n const seen = new Set<string>();\n const lines: string[] = [];\n let used = 0;\n for (const entry of entries) {\n if (entry.verdict !== \"rejected\") continue;\n const note = entry.note?.trim();\n const reasons = entry.reasons.map((r) => CREATIVE_FEEDBACK_REASON_LABELS[r].toLowerCase()).join(\", \");\n const key = note ?? reasons;\n if (!key || seen.has(key)) continue;\n seen.add(key);\n const line = `Rejected${reasons ? ` (${reasons})` : \"\"}${note ? `: \"${note}\"` : \"\"}`;\n if (used + line.length > CASE_FILE_CHARS && lines.length > 0) break;\n used += line.length;\n lines.push(line);\n }\n return lines;\n}\n\n/** Asks the feedback route for one creative's verdicts. */\nexport type ReadFeedback = (body: { slug: string }) => Promise<CreativesFeedbackResponse>;\n\n/** One creative's rejections as `caseFileNotes` gives them; none when it has none or they cannot be read. */\nexport async function readCaseFile(\n slug: string,\n read: ReadFeedback = (body) => apiPost<CreativesFeedbackResponse>(\"/api/creatives/feedback\", body),\n): Promise<string[]> {\n const response = await read({ slug }).catch(() => null);\n return response?.ok ? caseFileNotes(response.data.recent) : [];\n}\n","/**\n * The one reading of a company every step of an ad takes its slice from: what it sells and to\n * whom (COMPANY.md), how its brand speaks and looks (BRAND.md), the rules that hold for this ad\n * (the brand's own and the ones a person taught), and one creative's case file. Each part is read\n * from the workspace as it stands when the call runs, and kept by content where a model reads it,\n * so nothing is copied into an ad's files and nothing goes stale.\n */\nimport path from \"node:path\";\nimport { type BrandDocReading, type ReadBrandDocs, readWorkspaceBrandDoc } from \"../brand/brand-doc.ts\";\nimport { type ReadBrandRules, readWorkspaceBrandRules } from \"../brand/brand-rules.ts\";\nimport { declaredRules, readLearnedRules } from \"../creative-rules/read.ts\";\nimport type { CreativeRule } from \"../creative-rules/schema.ts\";\nimport { type RuleTarget, rulesDigest, selectRules } from \"../creative-rules/select.ts\";\nimport type { ReviewUsage } from \"../nodes/local/lib/render-review.ts\";\nimport { readCaseFile } from \"./case-file.ts\";\nimport { type AskCompany, type CompanyReading, readCompanyReading } from \"./company.ts\";\n\nexport type CreativeContext = {\n company: CompanyReading | null;\n brand: BrandDocReading | null;\n /** The active rules that hold for this ad, strongest and newest first (`selectRules`). */\n rules: CreativeRule[];\n /** Which rules those are, in twelve characters, for the run record. */\n rulesDigest: string;\n /** One creative's rejections, in the person's words (`readCaseFile`). */\n caseFile: (slug: string) => Promise<string[]>;\n};\n\n/** The model readers, for a test or an eval; each defaults to the live, cached one. */\nexport type ContextReaders = {\n company?: AskCompany;\n brand?: ReadBrandDocs;\n brandRules?: ReadBrandRules;\n onSpend?: (usage: ReviewUsage) => void;\n};\n\n/** Every rule the company has, declared and learned, active and retired: what `rules list` and `rules add` read. */\nexport async function readAllRules(root: string, readers: ContextReaders = {}): Promise<CreativeRule[]> {\n const [declared, learned] = await Promise.all([\n readWorkspaceBrandRules(root, {\n ...(readers.brandRules ? { read: readers.brandRules } : {}),\n ...(readers.onSpend ? { onSpend: readers.onSpend } : {}),\n }),\n readLearnedRules(root),\n ]);\n return [...learned, ...declaredRules(declared)];\n}\n\n/** The company as this ad's every step reads it. Never throws: a part that cannot be read is empty. */\nexport async function readCreativeContext(\n root: string,\n target: RuleTarget = {},\n opts: { waived?: readonly string[]; readers?: ContextReaders } = {},\n): Promise<CreativeContext> {\n const readers = opts.readers ?? {};\n const [company, brand, rules] = await Promise.all([\n readCompanyReading(root, ...(readers.company ? [readers.company] : [])).catch(() => null),\n readWorkspaceBrandDoc(root, {\n ...(readers.brand ? { read: readers.brand } : {}),\n ...(readers.onSpend ? { onSpend: readers.onSpend } : {}),\n }).catch(() => null),\n readAllRules(root, readers).catch(() => []),\n ]);\n const held = selectRules(rules, target, opts.waived);\n return { company, brand, rules: held, rulesDigest: rulesDigest(held), caseFile: (slug) => readCaseFile(slug) };\n}\n\n/** The workspace a creative folder (`src/creatives/<slug>/`) belongs to. Pure. */\nexport const workspaceOfCreative = (creativeDir: string) => path.resolve(creativeDir, \"../../..\");\n\n/** What the brand never does, short, for a question that weighs another advertiser's ad for ours. Pure. */\nexport function avoidOf(rules: readonly CreativeRule[], max = 5): string[] {\n return rules\n .filter((r) => r.strength === \"never\" && r.applies !== \"voice\" && r.applies !== \"sound\")\n .slice(0, max)\n .map((r) => (r.text.length > 90 ? `${r.text.slice(0, 89)}…` : r.text));\n}\n","/**\n * The rules the brand's own documents set for its ads, read by a text model in whatever language\n * they are written: what its copy never says, the claims it may not make, how its name, numbers\n * and prices are written, what its pictures, people and logo must and must not be. These are the\n * declared creative rules (`creative-rules/read.ts` `declaredRules`); a person's are the learned.\n *\n * Its own reading, apart from the name, fonts and colours (`brand-doc.ts`): a long list of rules in\n * the same answer cost that reading its whole answer on 1 of 10 production documents. Kept by the\n * document's content, so it is one call per BRAND.md edit. Measured on\n * scripts/creative-rules-evals/declared.mts (recall of each document's labelled rules).\n */\nimport { createHash } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\nimport { holdUnbilledSpend } from \"../../commands/canvas/unbilled-spend.ts\";\nimport { Phrases, RULE_APPLIES, RULE_STRENGTHS } from \"../creative-rules/schema.ts\";\nimport { askAboutText, type ReviewUsage } from \"../nodes/local/lib/render-review.ts\";\n\nexport const BRAND_RULES_MODEL = \"google/gemini-3.5-flash\";\nconst REQUEST = {\n provider: { order: [\"google-vertex\"], allow_fallbacks: false },\n seed: 0,\n reasoning: { effort: \"low\" },\n response_format: { type: \"json_object\" },\n};\nconst PROMPT_VERSION = 2;\nconst TIMEOUT_MS = 60_000;\n/** About 15k tokens: the longest production BRAND.md (Oct 2026) ran to 38k characters, its copy rules past 33k. */\nconst BRAND_MD_CHARS = 60_000;\n/** The rules a reading keeps: more is a document listing its web components too. */\nconst MAX_RULES = 60;\nconst RULE_CHARS = 400;\n\n/** A rule the brand's documents set for its ads, in their words, with the phrases that decide it. */\nexport type BrandRule = {\n text: string;\n strength: (typeof RULE_STRENGTHS)[number];\n applies: (typeof RULE_APPLIES)[number];\n forbid?: string[];\n require?: string[];\n allow?: string[];\n};\n\nconst optionalPhrases = Phrases.optional().catch(undefined);\nconst Rule = z.object({\n text: z.string().trim().min(1).max(RULE_CHARS),\n strength: z.enum(RULE_STRENGTHS).catch(\"must\"),\n applies: z.enum(RULE_APPLIES).catch(\"any\"),\n forbid: optionalPhrases,\n require: optionalPhrases,\n allow: optionalPhrases,\n});\nconst Answer = z.object({ rules: z.array(z.unknown()) });\n\n/** The model's answer as rules, every entry it got wrong dropped; null when it is not an answer. Pure. */\nexport function parseBrandRules(answer: unknown): BrandRule[] | null {\n const parsed = Answer.safeParse(answer);\n if (!parsed.success) return null;\n return parsed.data.rules\n .flatMap((raw) => {\n const rule = Rule.safeParse(raw);\n if (!rule.success) return [];\n const { forbid, require, allow, ...rest } = rule.data;\n return [\n {\n ...rest,\n ...(forbid?.length ? { forbid } : {}),\n ...(require?.length ? { require } : {}),\n ...(allow?.length ? { allow } : {}),\n },\n ];\n })\n .slice(0, MAX_RULES);\n}\n\n/** A heading that likely holds rules for ads, in the languages brand documents are written in. */\nconst RULE_HEADING =\n /rule|never|copy|voice|tone|logo|imag|photo|illustrat|legal|claim|checklist|reflex|prohibit|say|regla|nunca|voz|tono|imagen|foto|prohib|decir|règle|jamais|regel|stimme|bild|regra|imagem/i;\n\n/**\n * The document's headings that likely hold rules, named before it so the model reads each whole.\n * Prod, Oct 2026: a 38k-character BRAND.md's copy rules sat at its end and came back empty. Pure.\n */\nfunction ruleSections(brandMd: string): string[] {\n const headings = [...brandMd.slice(0, BRAND_MD_CHARS).matchAll(/^#{2,4}\\s+(.+)$/gm)]\n .map((m) => (m[1] ?? \"\").trim())\n .filter((h) => RULE_HEADING.test(h));\n const fence = /```generation-style/.test(brandMd) ? [\"the ```generation-style block\"] : [];\n return [...new Set([...headings, ...fence])];\n}\n\n/** The question the model answers. Pure. */\nexport function brandRulesPrompt(brandMd: string): string {\n const sections = ruleSections(brandMd);\n return [\n \"You read a company's brand guidelines (BRAND.md), written in any language, for the people who make its ads. List every standing rule they set that an ad, its words, its pictures, its people or its logo must follow. Extract only what they state; never invent a rule.\",\n \"\",\n \"Go through the document section by section: every bullet or sentence of a section about copy or wording, voice, what the brand never says, things to reject, legal risk or claims it may not make, a checklist before publishing, how its name, numbers or prices are written, which logo to use and what never to do to it, photography, illustration, imagery or a generation-style block is one rule; keep each, a style taste included (as a prefer), whatever language the section is in. Leave out only rules about web pages, components, code, spacing, typefaces, colours' hex values and where a colour goes on a page.\",\n ...(sections.length > 0\n ? [\n \"\",\n `Sections that most likely hold rules, each to be read to its end: ${sections.map((h) => `\"${h}\"`).join(\", \")}.`,\n ]\n : []),\n \"\",\n `Return JSON only: {\"rules\": [{\"text\": string, \"strength\": \"never\" | \"must\" | \"prefer\", \"applies\": \"copy\" | \"picture\" | \"cast\" | \"logo\" | \"voice\" | \"sound\" | \"subtitles\" | \"any\", \"forbid\": string[], \"require\": string[], \"allow\": string[]}]}, at most ${MAX_RULES}, each rule once.`,\n '- text: the rule quoted from the document in its own language, whole but short (one bullet, at most one sentence of why). It must make sense read alone, as an ad\\'s reviewer reads it: when a bullet leans on its heading or table, name what it is about (\"Brand red: never for text or buttons\", not \"Never text, never a button\").',\n '- strength: \"never\" for a ban, \"must\" for something every ad has to do, \"prefer\" for a taste or a default.',\n '- applies: \"copy\" for words, \"picture\" for what is shown, \"cast\" for who appears, \"logo\", \"voice\" or \"sound\" for a video\\'s voice and audio, \"subtitles\", else \"any\".',\n '- forbid: the exact words the rule bans in every case, as the document quotes them (\"—\" for em-dashes, \"usted\"); [] when the ban depends on the context or names no words. allow: the right spelling when the rule is about how a word is written or capitalised (\"PontIA\"). require: words every ad must say; usually [].',\n \"\",\n \"<brand_md>\",\n brandMd.slice(0, BRAND_MD_CHARS),\n \"</brand_md>\",\n ].join(\"\\n\");\n}\n\n/** Reads the rules of a BRAND.md with the model, or null when it could not answer. */\nexport type ReadBrandRules = (brandMd: string) => Promise<BrandRule[] | null>;\n\n/**\n * The real reader: each reading kept in `cacheDir` by the document, the model and the prompt. Spend\n * goes to `onSpend`, or, without one, waits in `cacheDir` for the workspace's next run record.\n */\nexport function brandRulesReader(cacheDir: string, onSpend?: (usage: ReviewUsage) => void): ReadBrandRules {\n return async (brandMd) => {\n const prompt = brandRulesPrompt(brandMd);\n const key = createHash(\"sha256\")\n .update(JSON.stringify([PROMPT_VERSION, BRAND_RULES_MODEL, prompt]))\n .digest(\"hex\");\n const file = path.join(cacheDir, \"brand-rules\", `${key}.json`);\n // Several steps of one ad read the rules at once: one question, the others wait for its answer.\n const inFlight = reading.get(file);\n if (inFlight) return await inFlight;\n // A reading that failed in this process is not asked again: a run reads the rules several times.\n if (failed.has(file)) return null;\n const read = readOrAsk(file, prompt, onSpend, cacheDir)\n .then((rules) => {\n if (rules === null) failed.add(file);\n return rules;\n })\n .finally(() => reading.delete(file));\n reading.set(file, read);\n return await read;\n };\n}\n\n/** The readings being asked right now, by cache file, and the ones no model answered in this process. */\nconst reading = new Map<string, Promise<BrandRule[] | null>>();\nconst failed = new Set<string>();\n\n/** The rules kept in `file`, else asked of the model and kept; null when it could not answer. */\nasync function readOrAsk(\n file: string,\n prompt: string,\n onSpend: ((usage: ReviewUsage) => void) | undefined,\n cacheDir: string,\n): Promise<BrandRule[] | null> {\n const cached = parseBrandRules(await readFile(file, \"utf8\").then(JSON.parse, () => null));\n if (cached) return cached;\n const spent: ReviewUsage[] = [];\n const answer = await askAboutText(BRAND_RULES_MODEL, prompt, (u) => (onSpend ? onSpend(u) : spent.push(u)), {\n timeoutMs: TIMEOUT_MS,\n request: REQUEST,\n }).catch(() => null);\n await holdUnbilledSpend(cacheDir, spent).catch(() => undefined);\n const rules = parseBrandRules(answer);\n if (rules) {\n await mkdir(path.dirname(file), { recursive: true }).catch(() => undefined);\n await writeFile(file, JSON.stringify({ rules }), \"utf8\").catch(() => undefined);\n }\n return rules;\n}\n\n/** The rules the workspace's BRAND.md sets, read once per edit; none when it has none or no model answered. */\nexport async function readWorkspaceBrandRules(\n root: string,\n opts: { read?: ReadBrandRules; onSpend?: (usage: ReviewUsage) => void } = {},\n): Promise<BrandRule[]> {\n const brandMd = await readFile(path.join(root, \"src\", \"brand\", \"BRAND.md\"), \"utf8\").catch(() => \"\");\n if (!brandMd.trim()) return [];\n const read = opts.read ?? brandRulesReader(path.join(root, \"canvas\", \".cache\"), opts.onSpend);\n return (await read(brandMd).catch(() => null)) ?? [];\n}\n"],"mappings":"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","names":["reading","path","createHash","mkdir","readFile","writeFile","path","z","REQUEST","PROMPT_VERSION","TIMEOUT_MS","z","Answer","require","createHash","path","reading","readOrAsk","readFile","mkdir","writeFile","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/engine/creative-context/company.ts","../src/engine/creative-context/case-file.ts","../src/engine/creative-context/index.ts","../src/engine/brand/brand-rules.ts"],"sourcesContent":["/**\n * What the company is, read once per COMPANY.md and the same for every ad and every step: what it\n * sells and to whom (in its own language, for the people who judge an ad as its viewer), what an\n * ads library files that under (in English, for every search), its industry, its buyer, the\n * country it sells in, whether its ads may joke, and its own brand names. One small model reads it,\n * pinned and kept by the document's content. Prod, Oct 2026: COMPANY.md was read in ten places,\n * each cut differently (whole, 12k, 6k, 800 characters, its first line), and its first line gave\n * the library \"https\", a brand name or a table header for 6 of 14 companies.\n */\nimport { createHash } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\nimport { holdUnbilledSpend } from \"../../commands/canvas/unbilled-spend.ts\";\nimport { askAboutText, type ReviewUsage } from \"../nodes/local/lib/render-review.ts\";\n\nexport type CompanyBuyer = \"consumer\" | \"small_business\" | \"enterprise\";\nexport type CompanyHumour = \"fits\" | \"risky\" | \"off_brand\";\n\nexport type CompanyReading = {\n /** What the company sells and to whom, one plain sentence in its own language. */\n sells?: string;\n /** What an ads library files it under, 2-6 plain English words: \"trade school training program\". */\n category?: string;\n /** The broader industry, in English: \"vocational education\". */\n industry?: string;\n buyer?: CompanyBuyer;\n /** ISO country it sells in; null when it sells in several. */\n country?: string | null;\n humour?: CompanyHumour;\n /** Its own brand and product names: its own ads are never a reference for a new one. */\n brands: string[];\n};\n\nconst text = z\n .string()\n .transform((s) => s.trim())\n .pipe(z.string().min(1).max(300))\n .optional()\n .catch(undefined);\n\nconst Answer = z.object({\n sells: text,\n category: text,\n industry: text,\n buyer: z.enum([\"consumer\", \"small_business\", \"enterprise\"]).optional().catch(undefined),\n country: z\n .string()\n .transform((s) => s.trim().toUpperCase())\n .pipe(z.union([z.literal(\"\").transform(() => null), z.string().regex(/^[A-Z]{2}$/)]))\n .optional()\n .catch(undefined),\n humour: z.enum([\"fits\", \"risky\", \"off_brand\"]).optional().catch(undefined),\n brands: z\n .array(z.string())\n .transform((list) =>\n list\n .map((s) => s.trim())\n .filter(Boolean)\n .slice(0, 4),\n )\n .catch([]),\n});\n\n/** The model's answer as a reading, or null when it does not say what the company sells. Pure. */\nfunction parseCompanyReading(answer: unknown): CompanyReading | null {\n const parsed = Answer.safeParse(answer);\n if (!parsed.success || !parsed.data.category) return null;\n const { brands, ...rest } = parsed.data;\n return { ...Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined)), brands };\n}\n\n/** Pinned like the brand reading: the same notes read the same on every run. */\nexport const COMPANY_READING_MODEL = \"google/gemini-3.5-flash-lite\";\nconst REQUEST = {\n provider: { order: [\"google-vertex\"], allow_fallbacks: false },\n seed: 0,\n temperature: 0,\n response_format: { type: \"json_object\" },\n};\nconst PROMPT_VERSION = 1;\nconst TIMEOUT_MS = 20_000;\n/** The notes the reading takes: past this, the rest is detail no step reads. */\nconst COMPANY_CHARS = 12_000;\n\n/** The question the model answers about the company's own notes. Pure. */\nexport function companyReadingPrompt(companyDoc: string): string {\n return `You read a company's own notes (COMPANY.md), in whatever language they are written, for the people who make its ads. Extract only what they say; never invent.\n\n\"\"\"\n${companyDoc.slice(0, COMPANY_CHARS)}\n\"\"\"\n\nAnswer only JSON:\n{\n \"sells\": \"what the company sells and to whom, one plain sentence of at most 20 words in the notes' own language, no brand names (e.g. 'Cursos de electricidad y fontanería para desempleados que buscan un oficio estable')\",\n \"category\": \"what it sells, 2-6 plain English words an ads library would file it under, no brand names (e.g. 'electrician training course', 'pet health insurance', 'ecommerce customer service AI'); for a company selling several lines, the line its notes lead with\",\n \"industry\": \"the broader industry, 1-3 English words (e.g. 'vocational education', 'insurance', 'customer service software')\",\n \"buyer\": \"consumer\" | \"small_business\" | \"enterprise\",\n \"country\": \"the ISO country code it sells in, or '' when several\",\n \"humour\": \"fits\" | \"risky\" | \"off_brand\",\n \"brands\": [\"the company's own brand and product names, as an advertiser name would show them, at most 4\"]\n}`;\n}\n\n/** The company in one line for a question about who would buy from it: what it sells, its industry. Pure. */\nexport function companyLine(reading: CompanyReading | null): string {\n if (!reading) return \"\";\n const what = reading.sells ?? reading.category ?? \"\";\n return reading.industry && what ? `${what} (${reading.industry})` : what;\n}\n\n/** COMPANY.md's first line, past the brand's name, up to whom it is for: what the company sells when no model reads it. Pure. */\nexport function firstLineSells(companyDoc: string): string {\n const first = companyDoc.split(\"\\n\").find((line) => line.trim() && !line.trim().startsWith(\"#\")) ?? \"\";\n // The scaffold's own placeholder (\"TODO — Run /company-discovery…\"): nothing is said yet.\n if (/^\\s*TODO\\b/.test(first)) return \"\";\n const what = first\n .replace(/^\\s*[-*]\\s+/, \"\")\n .replace(/^\\*\\*[^*]+\\*\\*\\s*[—–:-]?\\s*/, \"\")\n .split(/\\s+for\\s+|[,.;:(]|\\s[—–-]\\s/)[0];\n return (what ?? \"\").trim().split(/\\s+/).filter(Boolean).slice(0, SELLS_WORDS).join(\" \");\n}\n/** The most words the first line keeps: the library's judge reads a long query for 45 s and more. */\nconst SELLS_WORDS = 6;\n\n/** Asks the model a prompt and returns its JSON answer, or null when nobody answered. */\nexport type AskCompany = (prompt: string) => Promise<unknown>;\n\n/** The live model, its spend held for the workspace's next bill like every other check's. */\nconst askModel =\n (root: string): AskCompany =>\n async (prompt) => {\n const spent: ReviewUsage[] = [];\n const answer = await askAboutText(COMPANY_READING_MODEL, prompt, (u) => spent.push(u), {\n timeoutMs: TIMEOUT_MS,\n request: REQUEST,\n }).catch(() => null);\n await holdUnbilledSpend(path.join(root, \"canvas\", \".cache\"), spent).catch(() => undefined);\n return answer;\n };\n\n/**\n * The company as every step of an ad reads it: from the cache when COMPANY.md is unchanged, else\n * asked once. With no model, what its first line says it sells, and asked again next time; null\n * when COMPANY.md says nothing yet.\n */\nexport async function readCompanyReading(\n root: string,\n ask: AskCompany = askModel(root),\n): Promise<CompanyReading | null> {\n const doc = await readFile(path.join(root, \"COMPANY.md\"), \"utf8\").catch(() => \"\");\n const fallback = firstLineSells(doc);\n if (!fallback) return null;\n const prompt = companyReadingPrompt(doc);\n const key = createHash(\"sha256\")\n .update(JSON.stringify([PROMPT_VERSION, COMPANY_READING_MODEL, prompt]))\n .digest(\"hex\")\n .slice(0, 16);\n const file = path.join(root, \"canvas\", \".cache\", \"company-reading\", `${key}.json`);\n // Several steps of one ad read the company at once (make: its reference brief and its rules): one question.\n const inFlight = reading.get(file);\n if (inFlight) return await inFlight;\n const read = readOrAsk(file, prompt, ask, fallback).finally(() => reading.delete(file));\n reading.set(file, read);\n return await read;\n}\n\n/** The readings being asked right now, by cache file. */\nconst reading = new Map<string, Promise<CompanyReading>>();\n\n/** The reading kept in `file`, else asked and kept; what the first line says when no model answers. */\nasync function readOrAsk(file: string, prompt: string, ask: AskCompany, fallback: string): Promise<CompanyReading> {\n const cached = await readFile(file, \"utf8\")\n .then((raw) => parseCompanyReading(JSON.parse(raw)))\n .catch(() => null);\n if (cached) return cached;\n const answer = parseCompanyReading(await ask(prompt).catch(() => null));\n if (!answer) return { sells: fallback, brands: [] };\n await mkdir(path.dirname(file), { recursive: true });\n await writeFile(file, `${JSON.stringify(answer, null, 2)}\\n`, \"utf8\");\n return answer;\n}\n","/**\n * One creative's case file: every verdict the people reviewing it gave, with their words whole. A\n * correction (`--fix`, the spec repair, an automatic round) reads it, so a direction the person\n * already rejected never comes back. Prod, Oct 2026: a fix saw only the spec and the findings, and\n * a rejected line (\"oficio\" for \"trabajo\") returned two rounds later.\n */\nimport {\n CREATIVE_FEEDBACK_REASON_LABELS,\n type CreativeFeedbackEntry,\n type CreativesFeedbackResponse,\n} from \"@baker/api\";\nimport { apiPost } from \"../../client.ts\";\n\n/** The case file a prompt is given at most: past it, the oldest rejections are left out. */\nconst CASE_FILE_CHARS = 3000;\n\n/** Each rejection of the creative as one line, newest first, the same words once. Pure. */\nexport function caseFileNotes(entries: readonly CreativeFeedbackEntry[]): string[] {\n const seen = new Set<string>();\n const lines: string[] = [];\n let used = 0;\n for (const entry of entries) {\n if (entry.verdict !== \"rejected\") continue;\n const note = entry.note?.trim();\n const reasons = entry.reasons.map((r) => CREATIVE_FEEDBACK_REASON_LABELS[r].toLowerCase()).join(\", \");\n const key = note ?? reasons;\n if (!key || seen.has(key)) continue;\n seen.add(key);\n const line = `Rejected${reasons ? ` (${reasons})` : \"\"}${note ? `: \"${note}\"` : \"\"}`;\n if (used + line.length > CASE_FILE_CHARS && lines.length > 0) break;\n used += line.length;\n lines.push(line);\n }\n return lines;\n}\n\n/** Asks the feedback route for one creative's verdicts. */\nexport type ReadFeedback = (body: { slug: string }) => Promise<CreativesFeedbackResponse>;\n\n/** One creative's rejections as `caseFileNotes` gives them; none when it has none or they cannot be read. */\nexport async function readCaseFile(\n slug: string,\n read: ReadFeedback = (body) => apiPost<CreativesFeedbackResponse>(\"/api/creatives/feedback\", body),\n): Promise<string[]> {\n const response = await read({ slug }).catch(() => null);\n return response?.ok ? caseFileNotes(response.data.recent) : [];\n}\n","/**\n * The one reading of a company every step of an ad takes its slice from: what it sells and to\n * whom (COMPANY.md), how its brand speaks and looks (BRAND.md), the rules that hold for this ad\n * (the brand's own and the ones a person taught), and one creative's case file. Each part is read\n * from the workspace as it stands when the call runs, and kept by content where a model reads it,\n * so nothing is copied into an ad's files and nothing goes stale.\n */\nimport path from \"node:path\";\nimport { type BrandDocReading, type ReadBrandDocs, readWorkspaceBrandDoc } from \"../brand/brand-doc.ts\";\nimport { type ReadBrandRules, readWorkspaceBrandRules } from \"../brand/brand-rules.ts\";\nimport { declaredRules, readLearnedRules } from \"../creative-rules/read.ts\";\nimport type { CreativeRule } from \"../creative-rules/schema.ts\";\nimport { type RuleTarget, rulesDigest, selectRules } from \"../creative-rules/select.ts\";\nimport type { ReviewUsage } from \"../nodes/local/lib/render-review.ts\";\nimport { readCaseFile } from \"./case-file.ts\";\nimport { type AskCompany, type CompanyReading, readCompanyReading } from \"./company.ts\";\n\nexport type CreativeContext = {\n company: CompanyReading | null;\n brand: BrandDocReading | null;\n /** The active rules that hold for this ad, strongest and newest first (`selectRules`). */\n rules: CreativeRule[];\n /** Which rules those are, in twelve characters, for the run record. */\n rulesDigest: string;\n /** One creative's rejections, in the person's words (`readCaseFile`). */\n caseFile: (slug: string) => Promise<string[]>;\n};\n\n/** The model readers, for a test or an eval; each defaults to the live, cached one. */\nexport type ContextReaders = {\n company?: AskCompany;\n brand?: ReadBrandDocs;\n brandRules?: ReadBrandRules;\n onSpend?: (usage: ReviewUsage) => void;\n};\n\n/** Every rule the company has, declared and learned, active and retired: what `rules list` and `rules add` read. */\nexport async function readAllRules(root: string, readers: ContextReaders = {}): Promise<CreativeRule[]> {\n const [declared, learned] = await Promise.all([\n readWorkspaceBrandRules(root, {\n ...(readers.brandRules ? { read: readers.brandRules } : {}),\n ...(readers.onSpend ? { onSpend: readers.onSpend } : {}),\n }),\n readLearnedRules(root),\n ]);\n return [...learned, ...declaredRules(declared)];\n}\n\n/** The company as this ad's every step reads it. Never throws: a part that cannot be read is empty. */\nexport async function readCreativeContext(\n root: string,\n target: RuleTarget = {},\n opts: { waived?: readonly string[]; readers?: ContextReaders } = {},\n): Promise<CreativeContext> {\n const readers = opts.readers ?? {};\n const [company, brand, rules] = await Promise.all([\n readCompanyReading(root, ...(readers.company ? [readers.company] : [])).catch(() => null),\n readWorkspaceBrandDoc(root, {\n ...(readers.brand ? { read: readers.brand } : {}),\n ...(readers.onSpend ? { onSpend: readers.onSpend } : {}),\n }).catch(() => null),\n readAllRules(root, readers).catch(() => []),\n ]);\n const held = selectRules(rules, target, opts.waived);\n return { company, brand, rules: held, rulesDigest: rulesDigest(held), caseFile: (slug) => readCaseFile(slug) };\n}\n\n/** The workspace a creative folder (`src/creatives/<slug>/`) belongs to. Pure. */\nexport const workspaceOfCreative = (creativeDir: string) => path.resolve(creativeDir, \"../../..\");\n\n/** What the brand never does, short, for a question that weighs another advertiser's ad for ours. Pure. */\nexport function avoidOf(rules: readonly CreativeRule[], max = 5): string[] {\n return rules\n .filter((r) => r.strength === \"never\" && r.applies !== \"voice\" && r.applies !== \"sound\")\n .slice(0, max)\n .map((r) => (r.text.length > 90 ? `${r.text.slice(0, 89)}…` : r.text));\n}\n","/**\n * The rules the brand's own documents set for its ads, read by a text model in whatever language\n * they are written: what its copy never says, the claims it may not make, how its name, numbers\n * and prices are written, what its pictures, people and logo must and must not be. These are the\n * declared creative rules (`creative-rules/read.ts` `declaredRules`); a person's are the learned.\n *\n * Its own reading, apart from the name, fonts and colours (`brand-doc.ts`): a long list of rules in\n * the same answer cost that reading its whole answer on 1 of 10 production documents. Kept by the\n * document's content, so it is one call per BRAND.md edit. Measured on\n * scripts/creative-rules-evals/declared.mts (recall of each document's labelled rules).\n */\nimport { createHash } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\nimport { holdUnbilledSpend } from \"../../commands/canvas/unbilled-spend.ts\";\nimport { Phrases, RULE_APPLIES, RULE_STRENGTHS } from \"../creative-rules/schema.ts\";\nimport { askAboutText, type ReviewUsage } from \"../nodes/local/lib/render-review.ts\";\n\nexport const BRAND_RULES_MODEL = \"google/gemini-3.5-flash\";\nconst REQUEST = {\n provider: { order: [\"google-vertex\"], allow_fallbacks: false },\n seed: 0,\n reasoning: { effort: \"low\" },\n response_format: { type: \"json_object\" },\n};\nconst PROMPT_VERSION = 2;\nconst TIMEOUT_MS = 60_000;\n/** About 15k tokens: the longest production BRAND.md (Oct 2026) ran to 38k characters, its copy rules past 33k. */\nconst BRAND_MD_CHARS = 60_000;\n/** The rules a reading keeps: more is a document listing its web components too. */\nconst MAX_RULES = 60;\nconst RULE_CHARS = 400;\n\n/** A rule the brand's documents set for its ads, in their words, with the phrases that decide it. */\nexport type BrandRule = {\n text: string;\n strength: (typeof RULE_STRENGTHS)[number];\n applies: (typeof RULE_APPLIES)[number];\n forbid?: string[];\n require?: string[];\n allow?: string[];\n};\n\nconst optionalPhrases = Phrases.optional().catch(undefined);\nconst Rule = z.object({\n text: z.string().trim().min(1).max(RULE_CHARS),\n strength: z.enum(RULE_STRENGTHS).catch(\"must\"),\n applies: z.enum(RULE_APPLIES).catch(\"any\"),\n forbid: optionalPhrases,\n require: optionalPhrases,\n allow: optionalPhrases,\n});\nconst Answer = z.object({ rules: z.array(z.unknown()) });\n\n/** The model's answer as rules, every entry it got wrong dropped; null when it is not an answer. Pure. */\nexport function parseBrandRules(answer: unknown): BrandRule[] | null {\n const parsed = Answer.safeParse(answer);\n if (!parsed.success) return null;\n return parsed.data.rules\n .flatMap((raw) => {\n const rule = Rule.safeParse(raw);\n if (!rule.success) return [];\n const { forbid, require, allow, ...rest } = rule.data;\n return [\n {\n ...rest,\n ...(forbid?.length ? { forbid } : {}),\n ...(require?.length ? { require } : {}),\n ...(allow?.length ? { allow } : {}),\n },\n ];\n })\n .slice(0, MAX_RULES);\n}\n\n/** A heading that likely holds rules for ads, in the languages brand documents are written in. */\nconst RULE_HEADING =\n /rule|never|copy|voice|tone|logo|imag|photo|illustrat|legal|claim|checklist|reflex|prohibit|say|regla|nunca|voz|tono|imagen|foto|prohib|decir|règle|jamais|regel|stimme|bild|regra|imagem/i;\n\n/**\n * The document's headings that likely hold rules, named before it so the model reads each whole.\n * Prod, Oct 2026: a 38k-character BRAND.md's copy rules sat at its end and came back empty. Pure.\n */\nfunction ruleSections(brandMd: string): string[] {\n const headings = [...brandMd.slice(0, BRAND_MD_CHARS).matchAll(/^#{2,4}\\s+(.+)$/gm)]\n .map((m) => (m[1] ?? \"\").trim())\n .filter((h) => RULE_HEADING.test(h));\n const fence = /```generation-style/.test(brandMd) ? [\"the ```generation-style block\"] : [];\n return [...new Set([...headings, ...fence])];\n}\n\n/**\n * A standing line `brand-build` writes into every BRAND.md, worded for a sales call kept in the\n * vault. Read bare, it bans every testimonial and case study, the proof that converts best: on dev\n * (Oct 2026) the rule dropped a named customer's published words from an enterprise ad.\n */\nconst PRIVATE_QUOTE_LINE = \"Never publish a customer quote or an identifiable customer conversation.\";\nconst PRIVATE_QUOTE_MEANT =\n \"Never publish what a customer said in a private call, chat or note, nor a conversation that identifies them. A testimonial, review or case study the customer published or agreed to is not a private call: an ad may quote it.\";\n\n/** The question the model answers. Pure. */\nexport function brandRulesPrompt(document: string): string {\n const brandMd = document.replaceAll(PRIVATE_QUOTE_LINE, PRIVATE_QUOTE_MEANT);\n const sections = ruleSections(brandMd);\n return [\n \"You read a company's brand guidelines (BRAND.md), written in any language, for the people who make its ads. List every standing rule they set that an ad, its words, its pictures, its people or its logo must follow. Extract only what they state; never invent a rule.\",\n \"\",\n \"Go through the document section by section: every bullet or sentence of a section about copy or wording, voice, what the brand never says, things to reject, legal risk or claims it may not make, a checklist before publishing, how its name, numbers or prices are written, which logo to use and what never to do to it, photography, illustration, imagery or a generation-style block is one rule; keep each, a style taste included (as a prefer), whatever language the section is in. Leave out only rules about web pages, components, code, spacing, typefaces, colours' hex values and where a colour goes on a page.\",\n ...(sections.length > 0\n ? [\n \"\",\n `Sections that most likely hold rules, each to be read to its end: ${sections.map((h) => `\"${h}\"`).join(\", \")}.`,\n ]\n : []),\n \"\",\n `Return JSON only: {\"rules\": [{\"text\": string, \"strength\": \"never\" | \"must\" | \"prefer\", \"applies\": \"copy\" | \"picture\" | \"cast\" | \"logo\" | \"voice\" | \"sound\" | \"subtitles\" | \"any\", \"forbid\": string[], \"require\": string[], \"allow\": string[]}]}, at most ${MAX_RULES}, each rule once.`,\n '- text: the rule quoted from the document in its own language, whole but short (one bullet, at most one sentence of why). It must make sense read alone, as an ad\\'s reviewer reads it: when a bullet leans on its heading or table, name what it is about (\"Brand red: never for text or buttons\", not \"Never text, never a button\").',\n '- strength: \"never\" for a ban, \"must\" for something every ad has to do, \"prefer\" for a taste or a default.',\n '- applies: \"copy\" for words, \"picture\" for what is shown, \"cast\" for who appears, \"logo\", \"voice\" or \"sound\" for a video\\'s voice and audio, \"subtitles\", else \"any\".',\n '- forbid: the exact words the rule bans in every case, as the document quotes them (\"—\" for em-dashes, \"usted\"); [] when the ban depends on the context or names no words. allow: the right spelling when the rule is about how a word is written or capitalised (\"PontIA\"). require: words every ad must say; usually [].',\n \"\",\n \"<brand_md>\",\n brandMd.slice(0, BRAND_MD_CHARS),\n \"</brand_md>\",\n ].join(\"\\n\");\n}\n\n/** Reads the rules of a BRAND.md with the model, or null when it could not answer. */\nexport type ReadBrandRules = (brandMd: string) => Promise<BrandRule[] | null>;\n\n/**\n * The real reader: each reading kept in `cacheDir` by the document, the model and the prompt. Spend\n * goes to `onSpend`, or, without one, waits in `cacheDir` for the workspace's next run record.\n */\nexport function brandRulesReader(cacheDir: string, onSpend?: (usage: ReviewUsage) => void): ReadBrandRules {\n return async (brandMd) => {\n const prompt = brandRulesPrompt(brandMd);\n const key = createHash(\"sha256\")\n .update(JSON.stringify([PROMPT_VERSION, BRAND_RULES_MODEL, prompt]))\n .digest(\"hex\");\n const file = path.join(cacheDir, \"brand-rules\", `${key}.json`);\n // Several steps of one ad read the rules at once: one question, the others wait for its answer.\n const inFlight = reading.get(file);\n if (inFlight) return await inFlight;\n // A reading that failed in this process is not asked again: a run reads the rules several times.\n if (failed.has(file)) return null;\n const read = readOrAsk(file, prompt, onSpend, cacheDir)\n .then((rules) => {\n if (rules === null) failed.add(file);\n return rules;\n })\n .finally(() => reading.delete(file));\n reading.set(file, read);\n return await read;\n };\n}\n\n/** The readings being asked right now, by cache file, and the ones no model answered in this process. */\nconst reading = new Map<string, Promise<BrandRule[] | null>>();\nconst failed = new Set<string>();\n\n/** The rules kept in `file`, else asked of the model and kept; null when it could not answer. */\nasync function readOrAsk(\n file: string,\n prompt: string,\n onSpend: ((usage: ReviewUsage) => void) | undefined,\n cacheDir: string,\n): Promise<BrandRule[] | null> {\n const cached = parseBrandRules(await readFile(file, \"utf8\").then(JSON.parse, () => null));\n if (cached) return cached;\n const spent: ReviewUsage[] = [];\n const answer = await askAboutText(BRAND_RULES_MODEL, prompt, (u) => (onSpend ? onSpend(u) : spent.push(u)), {\n timeoutMs: TIMEOUT_MS,\n request: REQUEST,\n }).catch(() => null);\n await holdUnbilledSpend(cacheDir, spent).catch(() => undefined);\n const rules = parseBrandRules(answer);\n if (rules) {\n await mkdir(path.dirname(file), { recursive: true }).catch(() => undefined);\n await writeFile(file, JSON.stringify({ rules }), \"utf8\").catch(() => undefined);\n }\n return rules;\n}\n\n/** The rules the workspace's BRAND.md sets, read once per edit; none when it has none or no model answered. */\nexport async function readWorkspaceBrandRules(\n root: string,\n opts: { read?: ReadBrandRules; onSpend?: (usage: ReviewUsage) => void } = {},\n): Promise<BrandRule[]> {\n const brandMd = await readFile(path.join(root, \"src\", \"brand\", \"BRAND.md\"), \"utf8\").catch(() => \"\");\n if (!brandMd.trim()) return [];\n const read = opts.read ?? brandRulesReader(path.join(root, \"canvas\", \".cache\"), opts.onSpend);\n return (await read(brandMd).catch(() => null)) ?? [];\n}\n"],"mappings":"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","names":["reading","path","createHash","mkdir","readFile","writeFile","path","z","REQUEST","PROMPT_VERSION","TIMEOUT_MS","z","Answer","require","createHash","path","reading","readOrAsk","readFile","mkdir","writeFile","path"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
languageTag
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FHYUIYU5.js";
|
|
4
4
|
import {
|
|
5
5
|
parseRefExpr
|
|
6
6
|
} from "./chunk-XXU7XRMM.js";
|
|
@@ -301,4 +301,4 @@ export {
|
|
|
301
301
|
buildInitialRunRecord,
|
|
302
302
|
buildFailedRunRecord
|
|
303
303
|
};
|
|
304
|
-
//# sourceMappingURL=chunk-
|
|
304
|
+
//# sourceMappingURL=chunk-5OLCELV7.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
heldRules
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZPMGYAYZ.js";
|
|
4
4
|
import {
|
|
5
5
|
companyLine,
|
|
6
6
|
readCompanyReading
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-5OAFKP6L.js";
|
|
8
8
|
import {
|
|
9
9
|
adSetViewerRows,
|
|
10
10
|
audienceNamesOf,
|
|
@@ -818,4 +818,4 @@ export {
|
|
|
818
818
|
viewerCheckBeforeRender,
|
|
819
819
|
viewerCheckCommand
|
|
820
820
|
};
|
|
821
|
-
//# sourceMappingURL=chunk-
|
|
821
|
+
//# sourceMappingURL=chunk-A7MTWTMM.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
heldRules,
|
|
6
6
|
ruleGate
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ZPMGYAYZ.js";
|
|
8
8
|
import {
|
|
9
9
|
sourcesClaims
|
|
10
10
|
} from "./chunk-S4ROJTDY.js";
|
|
@@ -6992,4 +6992,4 @@ export {
|
|
|
6992
6992
|
prepareCanvas,
|
|
6993
6993
|
exitOnPrepareError
|
|
6994
6994
|
};
|
|
6995
|
-
//# sourceMappingURL=chunk-
|
|
6995
|
+
//# sourceMappingURL=chunk-FHYUIYU5.js.map
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./chunk-B2SAOVMU.js";
|
|
11
11
|
import {
|
|
12
12
|
readAllRules
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-5OAFKP6L.js";
|
|
14
14
|
import {
|
|
15
15
|
IDEA_ANGLES,
|
|
16
16
|
IDEA_FORMATS,
|
|
@@ -155,4 +155,4 @@ export {
|
|
|
155
155
|
ideasReport,
|
|
156
156
|
ideasCommand
|
|
157
157
|
};
|
|
158
|
-
//# sourceMappingURL=chunk-
|
|
158
|
+
//# sourceMappingURL=chunk-GPWT42O3.js.map
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
parseDefinitionSeriesPlace,
|
|
7
7
|
parseDefinitionSourceFields,
|
|
8
8
|
parseDefinitionStrategyFields
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5OLCELV7.js";
|
|
10
10
|
import {
|
|
11
11
|
prepareCanvas
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-FHYUIYU5.js";
|
|
13
13
|
import {
|
|
14
14
|
BackendClient
|
|
15
15
|
} from "./chunk-NEHQGYZS.js";
|
|
@@ -213,4 +213,4 @@ export {
|
|
|
213
213
|
resolveCanvasPath,
|
|
214
214
|
syncDefinitionCommand
|
|
215
215
|
};
|
|
216
|
-
//# sourceMappingURL=chunk-
|
|
216
|
+
//# sourceMappingURL=chunk-HGTLGFKV.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
finalOutputLabelsFromCanvas,
|
|
3
3
|
parseDefinitionStrategyFields
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5OLCELV7.js";
|
|
5
5
|
import {
|
|
6
6
|
readRenderState
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-OTLYELOW.js";
|
|
8
8
|
import {
|
|
9
9
|
STATIC_AD_ROLE,
|
|
10
10
|
formatOfAdaptId,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isSignatureLine,
|
|
13
13
|
sameTag,
|
|
14
14
|
seriesMemberAd
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-FHYUIYU5.js";
|
|
16
16
|
import {
|
|
17
17
|
fetchRunRecord
|
|
18
18
|
} from "./chunk-L44WSWSZ.js";
|
|
@@ -506,4 +506,4 @@ export {
|
|
|
506
506
|
writeSeriesMemberCanvas,
|
|
507
507
|
seriesRunHints
|
|
508
508
|
};
|
|
509
|
-
//# sourceMappingURL=chunk-
|
|
509
|
+
//# sourceMappingURL=chunk-HY6DIWDV.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
latestVersion
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OTLYELOW.js";
|
|
4
4
|
import {
|
|
5
5
|
canvasOutputsRoot
|
|
6
6
|
} from "./chunk-DS2ZRTAL.js";
|
|
@@ -16,4 +16,4 @@ async function versionOf(slug, run) {
|
|
|
16
16
|
export {
|
|
17
17
|
versionOf
|
|
18
18
|
};
|
|
19
|
-
//# sourceMappingURL=chunk-
|
|
19
|
+
//# sourceMappingURL=chunk-IBJA2A4E.js.map
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
leadVersion,
|
|
7
7
|
writeSeriesMemberCanvas
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HY6DIWDV.js";
|
|
9
9
|
import {
|
|
10
10
|
definitionFieldsOf,
|
|
11
11
|
setDefinitionFields,
|
|
@@ -89,4 +89,4 @@ export {
|
|
|
89
89
|
makeSeriesAd,
|
|
90
90
|
seriesMadeEnvelope
|
|
91
91
|
};
|
|
92
|
-
//# sourceMappingURL=chunk-
|
|
92
|
+
//# sourceMappingURL=chunk-MKL7CL4P.js.map
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
variantImage,
|
|
30
30
|
versionRun,
|
|
31
31
|
writeSeriesMemberCanvas
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-HY6DIWDV.js";
|
|
33
33
|
import {
|
|
34
34
|
buildFailedRunRecord,
|
|
35
35
|
buildInitialRunRecord,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
finalFiles,
|
|
39
39
|
finalOutputLabelsFromCanvas,
|
|
40
40
|
nodeOutputsToRecord
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-5OLCELV7.js";
|
|
42
42
|
import {
|
|
43
43
|
clearLegacyDirtyMarker,
|
|
44
44
|
readRenderState,
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
recordReviewOutcome,
|
|
48
48
|
recordSeriesLeadRun,
|
|
49
49
|
recordSnapshot
|
|
50
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-OTLYELOW.js";
|
|
51
51
|
import {
|
|
52
52
|
BRAND_LOGO_ROLE,
|
|
53
53
|
CLAIMS_MODEL,
|
|
@@ -75,7 +75,7 @@ import {
|
|
|
75
75
|
uncastPeople,
|
|
76
76
|
wireNamedPeople,
|
|
77
77
|
wireProofBadges
|
|
78
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-FHYUIYU5.js";
|
|
79
79
|
import {
|
|
80
80
|
ViewerAnswerSchema,
|
|
81
81
|
describeViewer,
|
|
@@ -95,10 +95,10 @@ import {
|
|
|
95
95
|
viewerCheckBeforeRender,
|
|
96
96
|
viewerCheckSkip,
|
|
97
97
|
viewerChecksSpent
|
|
98
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-A7MTWTMM.js";
|
|
99
99
|
import {
|
|
100
100
|
heldRules
|
|
101
|
-
} from "./chunk-
|
|
101
|
+
} from "./chunk-ZPMGYAYZ.js";
|
|
102
102
|
import {
|
|
103
103
|
readAvatars,
|
|
104
104
|
writeAvatars
|
|
@@ -106,7 +106,7 @@ import {
|
|
|
106
106
|
import {
|
|
107
107
|
readCaseFile,
|
|
108
108
|
readCompanyReading
|
|
109
|
-
} from "./chunk-
|
|
109
|
+
} from "./chunk-5OAFKP6L.js";
|
|
110
110
|
import {
|
|
111
111
|
ruleTells
|
|
112
112
|
} from "./chunk-S4ROJTDY.js";
|
|
@@ -3191,4 +3191,4 @@ export {
|
|
|
3191
3191
|
executeCanvasRun,
|
|
3192
3192
|
buildBackendClient
|
|
3193
3193
|
};
|
|
3194
|
-
//# sourceMappingURL=chunk-
|
|
3194
|
+
//# sourceMappingURL=chunk-NDIBFOFB.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
prepareCanvas,
|
|
3
3
|
unsuppliedPlaceholderAssets
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FHYUIYU5.js";
|
|
5
5
|
import {
|
|
6
6
|
isSourceStale
|
|
7
7
|
} from "./chunk-MV2J73HP.js";
|
|
@@ -108,4 +108,4 @@ export {
|
|
|
108
108
|
staleCreatives,
|
|
109
109
|
clearLegacyDirtyMarker
|
|
110
110
|
};
|
|
111
|
-
//# sourceMappingURL=chunk-
|
|
111
|
+
//# sourceMappingURL=chunk-OTLYELOW.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readCreativeContext
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5OAFKP6L.js";
|
|
4
4
|
import {
|
|
5
5
|
holdUnbilledSpend
|
|
6
6
|
} from "./chunk-6FHP2HIK.js";
|
|
@@ -515,4 +515,4 @@ export {
|
|
|
515
515
|
fitReference,
|
|
516
516
|
fitCommand
|
|
517
517
|
};
|
|
518
|
-
//# sourceMappingURL=chunk-
|
|
518
|
+
//# sourceMappingURL=chunk-T235G4C7.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
readCreativeContext,
|
|
6
6
|
workspaceOfCreative
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-5OAFKP6L.js";
|
|
8
8
|
import {
|
|
9
9
|
frontmatterOf,
|
|
10
10
|
ruleTells
|
|
@@ -125,7 +125,7 @@ function tellLine(tell) {
|
|
|
125
125
|
function ruleRefusal(tells) {
|
|
126
126
|
if (tells.length === 0) return null;
|
|
127
127
|
return {
|
|
128
|
-
problems: tells.map(tellLine),
|
|
128
|
+
problems: [...new Set(tells.map(tellLine))],
|
|
129
129
|
fix: 'Rewrite each line named above within its rule (a static: its `copy` in prompt.json; a video: its scene files and ad.spec.json), then re-run. Only when the person explicitly asked for these words in this ad, keep them and add to prompt.json `"waive": [{"rule": "<id>", "why": "<their ask>"}]`.'
|
|
130
130
|
};
|
|
131
131
|
}
|
|
@@ -166,4 +166,4 @@ export {
|
|
|
166
166
|
heldRules,
|
|
167
167
|
ruleGate
|
|
168
168
|
};
|
|
169
|
-
//# sourceMappingURL=chunk-
|
|
169
|
+
//# sourceMappingURL=chunk-ZPMGYAYZ.js.map
|