@koda-sl/baker-cli 0.484.0 → 0.485.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 +1 -1
  2. package/dist/{brand-LAIIXZBL.js → brand-TRPJFCRL.js} +2 -2
  3. package/dist/{canvas-EAC3236V.js → canvas-ZKBXEUYK.js} +11 -11
  4. package/dist/{chunk-FY5RZBOY.js → chunk-67S6LADS.js} +2 -2
  5. package/dist/{chunk-Q4WWS3UQ.js → chunk-7SHNGCJ2.js} +2 -2
  6. package/dist/{chunk-AXDKLXCG.js → chunk-CTJPTSD4.js} +2 -2
  7. package/dist/{chunk-5O77KEKO.js → chunk-DFQHO3SL.js} +3 -3
  8. package/dist/{chunk-Z5SOZUUB.js → chunk-F4WLKHUS.js} +2 -2
  9. package/dist/{chunk-MGIOV72F.js → chunk-GIECLBJ3.js} +3 -3
  10. package/dist/{chunk-27DJRM3F.js → chunk-GPY2JEXL.js} +2 -2
  11. package/dist/{chunk-CS5JF52J.js → chunk-HDTR5DNA.js} +2 -2
  12. package/dist/{chunk-VNNCYWNN.js → chunk-HKQH6H2P.js} +3 -3
  13. package/dist/{chunk-IX2QLW6G.js → chunk-OEYVPXQZ.js} +28 -4
  14. package/dist/chunk-OEYVPXQZ.js.map +1 -0
  15. package/dist/{chunk-K2JTERIO.js → chunk-RSYOUKIQ.js} +2 -2
  16. package/dist/{chunk-3PXC2OXT.js → chunk-X3PM773M.js} +2 -2
  17. package/dist/{chunk-EFL6RNH3.js → chunk-YXVJA37O.js} +5 -5
  18. package/dist/cli.js +4 -4
  19. package/dist/{creatives-NK5HOXSF.js → creatives-K2BKGN6J.js} +5 -5
  20. package/dist/{critique-N5OL42C6.js → critique-2UMC3GJP.js} +14 -7
  21. package/dist/critique-2UMC3GJP.js.map +1 -0
  22. package/dist/{critique-EDMP5P5R.js → critique-GTUDKADX.js} +4 -4
  23. package/dist/{feedback-OCN3XNKF.js → feedback-7JVO62CE.js} +7 -7
  24. package/dist/{feedback-record-Z66AXZ57.js → feedback-record-YEJZKVFC.js} +7 -7
  25. package/dist/{fonts-I434ILTG.js → fonts-DIVC6DP2.js} +4 -4
  26. package/dist/{landing-EJGYTI5W.js → landing-6OJYUEWP.js} +2 -2
  27. package/dist/{make-ZYEFMAR6.js → make-2XZBKFJC.js} +8 -8
  28. package/dist/{rerun-VL3FSINT.js → rerun-VCZBNGDW.js} +8 -8
  29. package/dist/{run-NTUJERPZ.js → run-WOOBUVP3.js} +8 -8
  30. package/dist/{scaffold-ad-LZSSM5HS.js → scaffold-ad-PJ47R3IF.js} +6 -6
  31. package/dist/{scaffold-static-ad-F4HEJJ5N.js → scaffold-static-ad-PZBZV6SQ.js} +11 -11
  32. package/dist/{scaffold-video-7KSBESQ6.js → scaffold-video-GU4C7HCK.js} +5 -5
  33. package/dist/{select-POYNXE2L.js → select-JM2MFVNP.js} +6 -6
  34. package/dist/{set-prompt-USTAJUV6.js → set-prompt-JP3MASVF.js} +4 -4
  35. package/dist/{stale-6HL4ELJW.js → stale-VCZRIG25.js} +5 -5
  36. package/dist/{sync-definition-YGQU4BFK.js → sync-definition-Z7TY43IA.js} +5 -5
  37. package/dist/{translate-ACGKNKQP.js → translate-ZONETQ2W.js} +7 -7
  38. package/dist/{validate-SXDUQ5KO.js → validate-UTNTXLOL.js} +6 -6
  39. package/package.json +1 -1
  40. package/dist/chunk-IX2QLW6G.js.map +0 -1
  41. package/dist/critique-N5OL42C6.js.map +0 -1
  42. /package/dist/{brand-LAIIXZBL.js.map → brand-TRPJFCRL.js.map} +0 -0
  43. /package/dist/{canvas-EAC3236V.js.map → canvas-ZKBXEUYK.js.map} +0 -0
  44. /package/dist/{chunk-FY5RZBOY.js.map → chunk-67S6LADS.js.map} +0 -0
  45. /package/dist/{chunk-Q4WWS3UQ.js.map → chunk-7SHNGCJ2.js.map} +0 -0
  46. /package/dist/{chunk-AXDKLXCG.js.map → chunk-CTJPTSD4.js.map} +0 -0
  47. /package/dist/{chunk-5O77KEKO.js.map → chunk-DFQHO3SL.js.map} +0 -0
  48. /package/dist/{chunk-Z5SOZUUB.js.map → chunk-F4WLKHUS.js.map} +0 -0
  49. /package/dist/{chunk-MGIOV72F.js.map → chunk-GIECLBJ3.js.map} +0 -0
  50. /package/dist/{chunk-27DJRM3F.js.map → chunk-GPY2JEXL.js.map} +0 -0
  51. /package/dist/{chunk-CS5JF52J.js.map → chunk-HDTR5DNA.js.map} +0 -0
  52. /package/dist/{chunk-VNNCYWNN.js.map → chunk-HKQH6H2P.js.map} +0 -0
  53. /package/dist/{chunk-K2JTERIO.js.map → chunk-RSYOUKIQ.js.map} +0 -0
  54. /package/dist/{chunk-3PXC2OXT.js.map → chunk-X3PM773M.js.map} +0 -0
  55. /package/dist/{chunk-EFL6RNH3.js.map → chunk-YXVJA37O.js.map} +0 -0
  56. /package/dist/{creatives-NK5HOXSF.js.map → creatives-K2BKGN6J.js.map} +0 -0
  57. /package/dist/{critique-EDMP5P5R.js.map → critique-GTUDKADX.js.map} +0 -0
  58. /package/dist/{feedback-OCN3XNKF.js.map → feedback-7JVO62CE.js.map} +0 -0
  59. /package/dist/{feedback-record-Z66AXZ57.js.map → feedback-record-YEJZKVFC.js.map} +0 -0
  60. /package/dist/{fonts-I434ILTG.js.map → fonts-DIVC6DP2.js.map} +0 -0
  61. /package/dist/{landing-EJGYTI5W.js.map → landing-6OJYUEWP.js.map} +0 -0
  62. /package/dist/{make-ZYEFMAR6.js.map → make-2XZBKFJC.js.map} +0 -0
  63. /package/dist/{rerun-VL3FSINT.js.map → rerun-VCZBNGDW.js.map} +0 -0
  64. /package/dist/{run-NTUJERPZ.js.map → run-WOOBUVP3.js.map} +0 -0
  65. /package/dist/{scaffold-ad-LZSSM5HS.js.map → scaffold-ad-PJ47R3IF.js.map} +0 -0
  66. /package/dist/{scaffold-static-ad-F4HEJJ5N.js.map → scaffold-static-ad-PZBZV6SQ.js.map} +0 -0
  67. /package/dist/{scaffold-video-7KSBESQ6.js.map → scaffold-video-GU4C7HCK.js.map} +0 -0
  68. /package/dist/{select-POYNXE2L.js.map → select-JM2MFVNP.js.map} +0 -0
  69. /package/dist/{set-prompt-USTAJUV6.js.map → set-prompt-JP3MASVF.js.map} +0 -0
  70. /package/dist/{stale-6HL4ELJW.js.map → stale-VCZRIG25.js.map} +0 -0
  71. /package/dist/{sync-definition-YGQU4BFK.js.map → sync-definition-Z7TY43IA.js.map} +0 -0
  72. /package/dist/{translate-ACGKNKQP.js.map → translate-ZONETQ2W.js.map} +0 -0
  73. /package/dist/{validate-SXDUQ5KO.js.map → validate-UTNTXLOL.js.map} +0 -0
package/README.md CHANGED
@@ -7011,7 +7011,7 @@ baker landing critique spring-offer summer-offer claude # score three landings
7011
7011
  - **Takes several slugs at once.** `data.landings` is always an array with one entry per slug (`{ slug, overall, counts, dimensions, findings }`). For a single slug those fields are also spread at the top of `data`, so existing one-landing readers are unaffected. A wrong slug anywhere in the batch fails the whole call before any landing is scored.
7012
7012
  - **Advisory by design — findings never fail it.** A finding is guidance, not a gate; the command errors (exit 2) only on a wrong slug (`NOT_FOUND` / `INVALID_SLUG`, with `error.fix.availableSlugs`).
7013
7013
  - **The copy is never judged on a guess.** When the decision model cannot answer (no key, a timeout), no copy rule reports anything — a concession included — and a hint says the copy went unchecked and to re-run.
7014
- - **The brand is the allowlist.** A font, hue, or ground the client's brand commits is a decision, not a tell — matched values downgrade to advisory `brandDrift` so the critic never fights a real brand. The allowlist reads `src/brand/tokens.json` (the canonical machine-readable brand token file) when present; workspaces that predate it fall back to scanning the `@theme` block in `src/styles/global.css` (hex or oklch) plus the faces and colours `src/brand/BRAND.md` says the brand uses, read in any language by the brand reader — a face or colour BRAND.md bans ("nunca uses Inter") is never allowlisted.
7014
+ - **The brand is the allowlist.** A font, hue, or ground the client's brand commits is a decision, not a tell — matched values downgrade to advisory `brandDrift` so the critic never fights a real brand. The allowlist reads `src/brand/tokens.json` (the canonical machine-readable brand token file) when present; workspaces that predate it fall back to scanning the `@theme` block in `src/styles/global.css` (hex or oklch) plus the faces and colours `src/brand/BRAND.md` says the brand uses, read in any language by the brand reader — a face or colour BRAND.md bans ("nunca uses Inter") is never allowlisted. Gradient text is allowlisted the same way: it stays a block unless every coloured stop is a committed brand colour or a stop of a gradient `BRAND.md` writes out as CSS (`linear-gradient(90deg, #5061FF, #AA46CE, #FF9B63)`), so a brand's signature headline gradient publishes and an invented one does not.
7015
7015
  - **Records a freshness snapshot** to `.cache/landing-critique/<slug>.json` (the source hash it scored). The publish quality gate reads it to enforce that every changed landing was critiqued and ships no block-tier tell. Re-run after edits so the snapshot stays fresh.
7016
7016
 
7017
7017
  - **AI writing tells are scored in the `copy` family, in any language.** Beyond buzzwords and em-dash density, the critic detects the constructions models over-produce: the negation pivot ("it's not just X, it's Y", "Not a tool. A partner."), fake-candid openers ("Honestly?", "La verdad es que"), participles hung off a comma (", ensuring…", ", permitiendo…"), copula avoidance ("serves as", "cuenta con"), vague attributions, signposting, filler, hollow closers, aphorism formulas, false ranges, and chatbot framing that leaked into the page — each asked of every sentence; staccato runs and Title Case headings are measured in code. Most need a **cluster** (≥2 on the page) before they fire — the source guidance is explicit that isolated tells prove nothing — with two deliberate exceptions: chatbot framing fires at one, and curly quotes are not detected at all, since every editor inserts them automatically. Nothing in this family is block-tier, so it never fails the publish gate. Pattern set adapted from Wikipedia's "Signs of AI writing" (WikiProject AI Cleanup).
@@ -13,10 +13,10 @@ Subcommands:
13
13
  baker brand fonts \u2014 verify the brand's typefaces really load, and self-host them into src/brand/fonts/`
14
14
  },
15
15
  subCommands: {
16
- fonts: () => import("./fonts-I434ILTG.js").then((m) => m.fontsCommand)
16
+ fonts: () => import("./fonts-DIVC6DP2.js").then((m) => m.fontsCommand)
17
17
  }
18
18
  });
19
19
  export {
20
20
  brandCommand
21
21
  };
22
- //# sourceMappingURL=brand-LAIIXZBL.js.map
22
+ //# sourceMappingURL=brand-TRPJFCRL.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-NTUJERPZ.js").then((m) => m.runCommand),
29
- rerun: () => import("./rerun-VL3FSINT.js").then((m) => m.rerunCommand),
30
- validate: () => import("./validate-SXDUQ5KO.js").then((m) => m.validateCommand),
31
- critique: () => import("./critique-EDMP5P5R.js").then((m) => m.critiqueCommand),
28
+ run: () => import("./run-WOOBUVP3.js").then((m) => m.runCommand),
29
+ rerun: () => import("./rerun-VCZBNGDW.js").then((m) => m.rerunCommand),
30
+ validate: () => import("./validate-UTNTXLOL.js").then((m) => m.validateCommand),
31
+ critique: () => import("./critique-GTUDKADX.js").then((m) => m.critiqueCommand),
32
32
  "viewer-check": () => import("./viewer-check-7S2EB32L.js").then((m) => m.viewerCheckCommand),
33
33
  catalog: () => import("./catalog-USZL4SGX.js").then((m) => m.catalogCommand),
34
34
  inspect: () => import("./inspect-BMQTTKMW.js").then((m) => m.inspectCommand),
35
- "scaffold-ad": () => import("./scaffold-ad-LZSSM5HS.js").then((m) => m.scaffoldAdCommand),
36
- "scaffold-video": () => import("./scaffold-video-7KSBESQ6.js").then((m) => m.scaffoldVideoCommand),
37
- "scaffold-static-ad": () => import("./scaffold-static-ad-F4HEJJ5N.js").then((m) => m.scaffoldStaticAdCommand),
38
- "set-prompt": () => import("./set-prompt-USTAJUV6.js").then((m) => m.setPromptCommand),
39
- "sync-definition": () => import("./sync-definition-YGQU4BFK.js").then((m) => m.syncDefinitionCommand),
40
- stale: () => import("./stale-6HL4ELJW.js").then((m) => m.staleCommand)
35
+ "scaffold-ad": () => import("./scaffold-ad-PJ47R3IF.js").then((m) => m.scaffoldAdCommand),
36
+ "scaffold-video": () => import("./scaffold-video-GU4C7HCK.js").then((m) => m.scaffoldVideoCommand),
37
+ "scaffold-static-ad": () => import("./scaffold-static-ad-PZBZV6SQ.js").then((m) => m.scaffoldStaticAdCommand),
38
+ "set-prompt": () => import("./set-prompt-JP3MASVF.js").then((m) => m.setPromptCommand),
39
+ "sync-definition": () => import("./sync-definition-Z7TY43IA.js").then((m) => m.syncDefinitionCommand),
40
+ stale: () => import("./stale-VCZRIG25.js").then((m) => m.staleCommand)
41
41
  }
42
42
  });
43
43
  export {
44
44
  canvasCommand
45
45
  };
46
- //# sourceMappingURL=canvas-EAC3236V.js.map
46
+ //# sourceMappingURL=canvas-ZKBXEUYK.js.map
@@ -10,7 +10,7 @@ import {
10
10
  parseDefinitionSourceFields,
11
11
  parseDefinitionStrategyFields,
12
12
  prepareCanvas
13
- } from "./chunk-AXDKLXCG.js";
13
+ } from "./chunk-CTJPTSD4.js";
14
14
  import {
15
15
  BackendClient
16
16
  } from "./chunk-6WXV5GTA.js";
@@ -221,4 +221,4 @@ export {
221
221
  resolveCanvasPath,
222
222
  syncDefinitionCommand
223
223
  };
224
- //# sourceMappingURL=chunk-FY5RZBOY.js.map
224
+ //# sourceMappingURL=chunk-67S6LADS.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  prepareCanvas,
3
3
  unsuppliedPlaceholderAssets
4
- } from "./chunk-AXDKLXCG.js";
4
+ } from "./chunk-CTJPTSD4.js";
5
5
  import {
6
6
  isSourceStale
7
7
  } from "./chunk-FRK7SSEH.js";
@@ -119,4 +119,4 @@ export {
119
119
  staleCreatives,
120
120
  clearLegacyDirtyMarker
121
121
  };
122
- //# sourceMappingURL=chunk-Q4WWS3UQ.js.map
122
+ //# sourceMappingURL=chunk-7SHNGCJ2.js.map
@@ -20,7 +20,7 @@ import {
20
20
  import {
21
21
  GENERIC_FAMILIES,
22
22
  resolveBrandFaces
23
- } from "./chunk-K2JTERIO.js";
23
+ } from "./chunk-RSYOUKIQ.js";
24
24
  import {
25
25
  LOGO_FILE,
26
26
  readWorkspaceBrandDoc
@@ -8936,4 +8936,4 @@ export {
8936
8936
  prepareCanvas,
8937
8937
  exitOnPrepareError
8938
8938
  };
8939
- //# sourceMappingURL=chunk-AXDKLXCG.js.map
8939
+ //# sourceMappingURL=chunk-CTJPTSD4.js.map
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  readRenderState,
3
3
  recordSeen
4
- } from "./chunk-Q4WWS3UQ.js";
4
+ } from "./chunk-7SHNGCJ2.js";
5
5
  import {
6
6
  GLOBAL_PROMPT_FILE,
7
7
  ideaBaseline,
8
8
  isVideoBlueprint,
9
9
  readIdeaAnchor,
10
10
  recordedChanges
11
- } from "./chunk-AXDKLXCG.js";
11
+ } from "./chunk-CTJPTSD4.js";
12
12
  import {
13
13
  loadCompositionMeta
14
14
  } from "./chunk-6WXV5GTA.js";
@@ -188,4 +188,4 @@ export {
188
188
  shownVersion,
189
189
  shownBaseline
190
190
  };
191
- //# sourceMappingURL=chunk-5O77KEKO.js.map
191
+ //# sourceMappingURL=chunk-DFQHO3SL.js.map
@@ -3,7 +3,7 @@ import {
3
3
  parseDefinitionDelivery,
4
4
  resolveDelivery,
5
5
  resolveFormatList
6
- } from "./chunk-AXDKLXCG.js";
6
+ } from "./chunk-CTJPTSD4.js";
7
7
  import {
8
8
  AD_PLACEMENTS
9
9
  } from "./chunk-MCJ5I5LR.js";
@@ -88,4 +88,4 @@ export {
88
88
  deliveryDefinitionFields,
89
89
  deliveryNote
90
90
  };
91
- //# sourceMappingURL=chunk-Z5SOZUUB.js.map
91
+ //# sourceMappingURL=chunk-F4WLKHUS.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-UWXDNVS5.js";
4
4
  import {
5
5
  readRenderState
6
- } from "./chunk-Q4WWS3UQ.js";
6
+ } from "./chunk-7SHNGCJ2.js";
7
7
  import {
8
8
  STATIC_AD_ROLE,
9
9
  fixBriefNode,
@@ -14,7 +14,7 @@ import {
14
14
  plateFixPrompt,
15
15
  sameTag,
16
16
  seriesMemberAd
17
- } from "./chunk-AXDKLXCG.js";
17
+ } from "./chunk-CTJPTSD4.js";
18
18
  import {
19
19
  fetchRunRecord
20
20
  } from "./chunk-ABXTGAG7.js";
@@ -939,4 +939,4 @@ export {
939
939
  writeSeriesMemberCanvas,
940
940
  seriesRunHints
941
941
  };
942
- //# sourceMappingURL=chunk-MGIOV72F.js.map
942
+ //# sourceMappingURL=chunk-GIECLBJ3.js.map
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  leadVersion,
7
7
  writeSeriesMemberCanvas
8
- } from "./chunk-MGIOV72F.js";
8
+ } from "./chunk-GIECLBJ3.js";
9
9
  import {
10
10
  definitionFieldsOf,
11
11
  setDefinitionFields,
@@ -94,4 +94,4 @@ export {
94
94
  makeSeriesAd,
95
95
  seriesMadeEnvelope
96
96
  };
97
- //# sourceMappingURL=chunk-27DJRM3F.js.map
97
+ //# sourceMappingURL=chunk-GPY2JEXL.js.map
@@ -20,7 +20,7 @@ import {
20
20
  suggestFamilies,
21
21
  unresolvedBrandFamilies,
22
22
  upsertFontFaceCss
23
- } from "./chunk-K2JTERIO.js";
23
+ } from "./chunk-RSYOUKIQ.js";
24
24
  import {
25
25
  registerSchema,
26
26
  writeJson
@@ -491,4 +491,4 @@ export {
491
491
  fontsAdoptCommand,
492
492
  fontsCommand
493
493
  };
494
- //# sourceMappingURL=chunk-CS5JF52J.js.map
494
+ //# sourceMappingURL=chunk-HDTR5DNA.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  recordIdeaLines
3
- } from "./chunk-Q4WWS3UQ.js";
3
+ } from "./chunk-7SHNGCJ2.js";
4
4
  import {
5
5
  anchorLines,
6
6
  readIdeaAnchor
7
- } from "./chunk-AXDKLXCG.js";
7
+ } from "./chunk-CTJPTSD4.js";
8
8
  import {
9
9
  findWorkspaceRoot
10
10
  } from "./chunk-23H4ZJRP.js";
@@ -63,4 +63,4 @@ export {
63
63
  withAnchor,
64
64
  readCreativeIdea
65
65
  };
66
- //# sourceMappingURL=chunk-VNNCYWNN.js.map
66
+ //# sourceMappingURL=chunk-HKQH6H2P.js.map
@@ -134,7 +134,7 @@ function isCreamColor(c) {
134
134
  }
135
135
 
136
136
  // src/engine/landing/lib/brand-tokens.ts
137
- var EMPTY = { fonts: /* @__PURE__ */ new Set(), colors: [], hasTokens: false };
137
+ var EMPTY = { fonts: /* @__PURE__ */ new Set(), colors: [], gradientStops: [], hasTokens: false };
138
138
  function isNotAFamily(value) {
139
139
  return /^[1-9]00$/.test(value) || /^\d+(?:\.\d+)?(?:px|rem|em|%|pt|vw|vh|ch|ex)$/i.test(value) || /^(?:normal|bold|bolder|lighter|italic|oblique|none|auto|inherit|initial|unset)$/i.test(value);
140
140
  }
@@ -147,7 +147,15 @@ function parseBrandTokens(globalCss, brandDoc) {
147
147
  const parsed = color.use ? parseColor(color.hex) : null;
148
148
  if (parsed) colors.push(parsed);
149
149
  }
150
- return { fonts, colors, hasTokens: fonts.size > 0 || colors.length > 0 };
150
+ return { fonts, colors, gradientStops: [], hasTokens: fonts.size > 0 || colors.length > 0 };
151
+ }
152
+ var COLOR_LITERAL = /#[0-9a-f]{3,8}\b|rgba?\([^)]*\)|oklch\([^)]*\)/gi;
153
+ function chromaticStops(text) {
154
+ return (text.match(COLOR_LITERAL) ?? []).map(parseColor).filter((c) => c !== null && hasChroma(c));
155
+ }
156
+ function brandDocGradientStops(brandMd) {
157
+ const gradients = brandMd.match(/(?:repeating-)?(?:linear|radial|conic)-gradient\((?:[^()]|\([^()]*\))*\)/gi) ?? [];
158
+ return gradients.flatMap(chromaticStops);
151
159
  }
152
160
  function parseGlobalCss(rawCss, fonts, colors) {
153
161
  const globalCss = rawCss.replace(/\/\*[\s\S]*?\*\//g, (block) => block.replace(/[^\n]/g, " "));
@@ -172,9 +180,14 @@ function brandTokensFromCanonical(tokens) {
172
180
  const parsed = parseColor(color.value);
173
181
  if (parsed) colors.push(parsed);
174
182
  }
175
- return { fonts, colors, hasTokens: fonts.size > 0 || colors.length > 0 };
183
+ return { fonts, colors, gradientStops: [], hasTokens: fonts.size > 0 || colors.length > 0 };
176
184
  }
177
185
  async function loadBrandTokens(projectRoot, opts = {}) {
186
+ const gradientStops = brandDocGradientStops(await safeRead(path.join(projectRoot, "src", "brand", "BRAND.md")));
187
+ const brand = await loadCommittedTokens(projectRoot, opts);
188
+ return gradientStops.length > 0 ? { ...brand, gradientStops, hasTokens: true } : brand;
189
+ }
190
+ async function loadCommittedTokens(projectRoot, opts) {
178
191
  const tokensRaw = await safeRead(path.join(projectRoot, "src", "brand", "tokens.json"));
179
192
  if (tokensRaw) {
180
193
  try {
@@ -207,6 +220,16 @@ function brandCommitsFont(brand, snippet) {
207
220
  function brandCommitsAiHue(brand) {
208
221
  return brand.colors.some((c) => hasChroma(c, 40) && isAiPurpleHue(getHue(c)));
209
222
  }
223
+ var SAME_COLOR_TOLERANCE = 6;
224
+ function sameColor(a, b) {
225
+ return Math.abs(a.r - b.r) <= SAME_COLOR_TOLERANCE && Math.abs(a.g - b.g) <= SAME_COLOR_TOLERANCE && Math.abs(a.b - b.b) <= SAME_COLOR_TOLERANCE;
226
+ }
227
+ function brandCommitsGradient(brand, snippet) {
228
+ const stops = chromaticStops(snippet);
229
+ if (stops.length === 0) return false;
230
+ const committed = [...brand.colors, ...brand.gradientStops];
231
+ return stops.every((stop) => committed.some((c) => sameColor(c, stop)));
232
+ }
210
233
  function brandCommitsCream(brand) {
211
234
  return brand.colors.some((c) => isCreamColor(c));
212
235
  }
@@ -220,6 +243,7 @@ export {
220
243
  loadBrandTokens,
221
244
  brandCommitsFont,
222
245
  brandCommitsAiHue,
246
+ brandCommitsGradient,
223
247
  brandCommitsCream
224
248
  };
225
- //# sourceMappingURL=chunk-IX2QLW6G.js.map
249
+ //# sourceMappingURL=chunk-OEYVPXQZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engine/landing/lib/brand-tokens.ts","../src/engine/landing/lib/color.ts"],"sourcesContent":["/**\n * Landing anti-slop detector — brand allowlist.\n *\n * The client's committed brand is the allowlist: a value that matches a brand\n * token is a decision, not a tell, so the detector downgrades it to advisory\n * (mirrors impeccable's DESIGN.md drift model). The canonical source is\n * `src/brand/tokens.json` (validated with the shared `@baker/api` schema); a\n * workspace that predates it falls back to the legacy scan of the global.css\n * `@theme` block plus the faces and colours BRAND.md says the brand uses, read\n * in any language by the brand reader (`brand/brand-doc.ts`). A value BRAND.md\n * bans is never allowlisted.\n *\n * Empty/TODO brand (a fresh scaffold) → no allowlist, full strictness.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { brandTokensSchema, type BrandTokens as CanonicalBrandTokens } from \"@baker/api\";\nimport { type BrandDocReading, type ReadBrandDocs, readWorkspaceBrandDoc } from \"../../brand/brand-doc.ts\";\nimport type { ReviewUsage } from \"../../nodes/local/lib/render-review.ts\";\nimport { getHue, hasChroma, isAiPurpleHue, isCreamColor, parseColor, type Rgb } from \"./color.ts\";\n\nexport type BrandTokens = {\n /** Lowercased committed font-family names (e.g. \"inter\", \"geist\"). */\n fonts: Set<string>;\n /** Parsed committed colors. */\n colors: Rgb[];\n /** The stops of every gradient BRAND.md writes out — committed for gradients only. */\n gradientStops: Rgb[];\n /** True once any real token was found — distinguishes \"branded\" from \"blank\". */\n hasTokens: boolean;\n};\n\nconst EMPTY: BrandTokens = { fonts: new Set(), colors: [], gradientStops: [], hasTokens: false };\n\n/**\n * A token value that is a weight, a length, or a style keyword — never a family.\n *\n * Deliberately narrow, and the same predicate as `isNotAFamily` in the\n * scaffold's `__tooling__/checks/sdk/theme-css.mjs`. \"Anything starting with a\n * digit\" also swallows the real families that are named with digits — SAP's\n * \"72\", the \"3270\" monospace face — and because the scaffold keeps those,\n * dropping them here made `baker brand fonts check` clear a brand the client's\n * verify then rejected. `__tests__/theme-css-contract.test.ts` pins the two.\n */\nexport function isNotAFamily(value: string): boolean {\n return (\n /^[1-9]00$/.test(value) ||\n /^\\d+(?:\\.\\d+)?(?:px|rem|em|%|pt|vw|vh|ch|ex)$/i.test(value) ||\n /^(?:normal|bold|bolder|lighter|italic|oblique|none|auto|inherit|initial|unset)$/i.test(value)\n );\n}\n\n/** Pure parse of the two legacy brand sources: the theme, and what BRAND.md says it uses. */\nexport function parseBrandTokens(globalCss: string, brandDoc?: BrandDocReading | null): BrandTokens {\n const fonts = new Set<string>();\n const colors: Rgb[] = [];\n parseGlobalCss(globalCss, fonts, colors);\n for (const font of brandDoc?.fonts ?? []) if (font.use) fonts.add(font.family.toLowerCase());\n for (const color of brandDoc?.colors ?? []) {\n const parsed = color.use ? parseColor(color.hex) : null;\n if (parsed) colors.push(parsed);\n }\n return { fonts, colors, gradientStops: [], hasTokens: fonts.size > 0 || colors.length > 0 };\n}\n\nconst COLOR_LITERAL = /#[0-9a-f]{3,8}\\b|rgba?\\([^)]*\\)|oklch\\([^)]*\\)/gi;\n\n/** The coloured stops written in a piece of CSS or a Tailwind class list. */\nfunction chromaticStops(text: string): Rgb[] {\n return (text.match(COLOR_LITERAL) ?? []).map(parseColor).filter((c): c is Rgb => c !== null && hasChroma(c));\n}\n\n/**\n * The stops of the gradients BRAND.md writes out as CSS. Read as syntax, not\n * prose: a brand that spells out `linear-gradient(…)` with its exact stops is\n * specifying it, and tokens.json has no field for a gradient to live in.\n */\nfunction brandDocGradientStops(brandMd: string): Rgb[] {\n const gradients = brandMd.match(/(?:repeating-)?(?:linear|radial|conic)-gradient\\((?:[^()]|\\([^()]*\\))*\\)/gi) ?? [];\n return gradients.flatMap(chromaticStops);\n}\n\n/** global.css `@theme` tokens: `--font-*` families and `--color-*` values.\n * Comments are stripped first — a commented-out legacy token is not a\n * commitment, and one stale purple would defang the color rules page-wide.\n *\n * The `--font-` match is anchored with a lookbehind and its value is checked:\n * `--text-h1--font-weight: 700` is a Tailwind v4 sub-property, and reading its\n * `--font-weight: 700` substring as a family called \"700\" put a 3-character\n * numeric string into the committed set — which `brandCommitsFont` then\n * substring-matched against every snippet containing \"700\", silently\n * whitelisting off-brand fonts. Mirrors\n * `apps/scaffold-template/__tooling__/checks/sdk/theme-css.mjs`. */\nfunction parseGlobalCss(rawCss: string, fonts: Set<string>, colors: Rgb[]): void {\n const globalCss = rawCss.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (block) => block.replace(/[^\\n]/g, \" \"));\n for (const m of globalCss.matchAll(/(?<![\\w-])--font-([\\w-]*)\\s*:\\s*([^;]+);/gi)) {\n const slug = m[1] ?? \"\";\n if (slug.includes(\"--\") || /^weight(?:$|[-\\s])/i.test(slug)) continue;\n const first = (m[2] ?? \"\")\n .split(\",\")[0]\n ?.trim()\n .replace(/^['\"]|['\"]$/g, \"\")\n .toLowerCase();\n if (!first || isNotAFamily(first)) continue;\n fonts.add(first);\n }\n for (const m of globalCss.matchAll(\n /--color-[\\w-]*\\s*:\\s*(#[0-9a-f]{3,8}\\b|rgba?\\([^)]+\\)|oklch\\([^)]+\\)|hsla?\\([^)]+\\))/gi,\n )) {\n const c = parseColor(m[1] ?? \"\");\n if (c) colors.push(c);\n }\n}\n\n/** Canonical tokens.json → allowlist shape — exported for tests. */\nexport function brandTokensFromCanonical(tokens: CanonicalBrandTokens): BrandTokens {\n const fonts = new Set<string>(tokens.fonts.map((f) => f.family.toLowerCase()));\n const colors: Rgb[] = [];\n for (const color of tokens.colors) {\n const parsed = parseColor(color.value);\n if (parsed) colors.push(parsed);\n }\n return { fonts, colors, gradientStops: [], hasTokens: fonts.size > 0 || colors.length > 0 };\n}\n\n/**\n * Load brand tokens from a project root; degrades to empty when absent.\n * `src/brand/tokens.json` (canonical) wins; an absent or invalid file falls\n * back to the legacy global.css/BRAND.md scan — invalid is the drift\n * validator's finding to report, not a reason to run blind here.\n */\nexport async function loadBrandTokens(\n projectRoot: string,\n opts: { readBrandDoc?: ReadBrandDocs; onSpend?: (usage: ReviewUsage) => void } = {},\n): Promise<BrandTokens> {\n const gradientStops = brandDocGradientStops(await safeRead(path.join(projectRoot, \"src\", \"brand\", \"BRAND.md\")));\n const brand = await loadCommittedTokens(projectRoot, opts);\n return gradientStops.length > 0 ? { ...brand, gradientStops, hasTokens: true } : brand;\n}\n\nasync function loadCommittedTokens(\n projectRoot: string,\n opts: { readBrandDoc?: ReadBrandDocs; onSpend?: (usage: ReviewUsage) => void },\n): Promise<BrandTokens> {\n const tokensRaw = await safeRead(path.join(projectRoot, \"src\", \"brand\", \"tokens.json\"));\n if (tokensRaw) {\n try {\n const canonical = brandTokensSchema.safeParse(JSON.parse(tokensRaw));\n if (canonical.success) return brandTokensFromCanonical(canonical.data);\n } catch {\n // fall through to the legacy scan\n }\n }\n const globalCss = await safeRead(path.join(projectRoot, \"src\", \"styles\", \"global.css\"));\n const brandDoc = await readWorkspaceBrandDoc(projectRoot, {\n ...(opts.readBrandDoc ? { read: opts.readBrandDoc } : {}),\n ...(opts.onSpend ? { onSpend: opts.onSpend } : {}),\n });\n if (!globalCss && !brandDoc) return EMPTY;\n return parseBrandTokens(globalCss, brandDoc);\n}\n\nasync function safeRead(file: string): Promise<string> {\n try {\n return await readFile(file, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\n/** Does the brand commit this font name (as it appears in a finding snippet)? */\nexport function brandCommitsFont(brand: BrandTokens, snippet: string): boolean {\n if (brand.fonts.size === 0) return false;\n const lower = snippet.toLowerCase();\n for (const f of brand.fonts) if (f.length >= 3 && lower.includes(f)) return true;\n return false;\n}\n\n/** Does the brand commit a purple/indigo hue (so the AI-palette tell is a choice)? */\nexport function brandCommitsAiHue(brand: BrandTokens): boolean {\n return brand.colors.some((c) => hasChroma(c, 40) && isAiPurpleHue(getHue(c)));\n}\n\n/** Channels within this distance read as the same colour (rounding, case, rgb vs hex). */\nconst SAME_COLOR_TOLERANCE = 6;\n\nfunction sameColor(a: Rgb, b: Rgb): boolean {\n return (\n Math.abs(a.r - b.r) <= SAME_COLOR_TOLERANCE &&\n Math.abs(a.g - b.g) <= SAME_COLOR_TOLERANCE &&\n Math.abs(a.b - b.b) <= SAME_COLOR_TOLERANCE\n );\n}\n\n/**\n * Is this gradient the brand's own (so the gradient-text tell is a choice)?\n * Every coloured stop must be a committed colour or a stop of a gradient\n * BRAND.md writes out; neutral stops (white, black,\n * transparent) fade into anything. A gradient whose stops we cannot read\n * (Tailwind palette names, `var()`) is not the brand's as far as we can tell.\n */\nexport function brandCommitsGradient(brand: BrandTokens, snippet: string): boolean {\n const stops = chromaticStops(snippet);\n if (stops.length === 0) return false;\n const committed = [...brand.colors, ...brand.gradientStops];\n return stops.every((stop) => committed.some((c) => sameColor(c, stop)));\n}\n\n/** Does the brand commit a cream ground (so the cream tell is a choice)? */\nexport function brandCommitsCream(brand: BrandTokens): boolean {\n return brand.colors.some((c) => isCreamColor(c));\n}\n","/**\n * Landing anti-slop detector — color utilities.\n *\n * Ported (with sense) from impeccable's `cli/engine/shared/color.mjs` +\n * `isNeutralAuthoredColor` from `detect-text.mjs`. These parse colors as WRITTEN\n * in authored CSS (hex, named, rgb/hsl/oklch), not just the computed forms a\n * browser emits — because we scan `.astro` source, not a rendered DOM.\n */\n\nexport type Rgb = { r: number; g: number; b: number; a: number };\n\n/** CSS named colors whose channels are equal (achromatic). */\nconst NEUTRAL_COLOR_KEYWORDS = new Set<string>([\n \"transparent\",\n \"currentcolor\",\n \"black\",\n \"white\",\n \"gray\",\n \"grey\",\n \"silver\",\n \"dimgray\",\n \"dimgrey\",\n \"darkgray\",\n \"darkgrey\",\n \"lightgray\",\n \"lightgrey\",\n \"gainsboro\",\n \"whitesmoke\",\n]);\n\nfunction hexChannels(color: string): [number, number, number] | null {\n const long = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})(?:[0-9a-f]{2})?$/i);\n if (long)\n return [\n Number.parseInt(long[1] ?? \"0\", 16),\n Number.parseInt(long[2] ?? \"0\", 16),\n Number.parseInt(long[3] ?? \"0\", 16),\n ];\n const short = color.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])(?:[0-9a-f])?$/i);\n if (short) {\n const dup = (s: string | undefined) => Number.parseInt(`${s ?? \"0\"}${s ?? \"0\"}`, 16);\n return [dup(short[1]), dup(short[2]), dup(short[3])];\n }\n return null;\n}\n\n/** Parse any authored hex / rgb() / oklch() color to {r,g,b,a}. Null if unknown. */\nexport function parseColor(raw: string): Rgb | null {\n const c = String(raw || \"\")\n .trim()\n .toLowerCase();\n if (!c || c === \"transparent\") return null;\n const rgb = c.match(/rgba?\\(\\s*([\\d.]+)[\\s,]+([\\d.]+)[\\s,]+([\\d.]+)(?:[\\s,/]+([\\d.]+%?))?\\s*\\)/i);\n if (rgb) {\n const alpha = rgb[4];\n const a = alpha === undefined ? 1 : alpha.endsWith(\"%\") ? Number.parseFloat(alpha) / 100 : Number.parseFloat(alpha);\n return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]), a };\n }\n const oklch = parseOklch(c);\n if (oklch) return oklch;\n const hex = hexChannels(c);\n if (hex) return { r: hex[0], g: hex[1], b: hex[2], a: 1 };\n return null;\n}\n\n/** `oklch(L C H)` / `oklch(L% C H / a)` → Rgb, or null when not oklch. */\nfunction parseOklch(c: string): Rgb | null {\n const m = c.match(/oklch\\(\\s*([\\d.]+%?)\\s+([\\d.]+%?)\\s+([\\d.]+)(?:deg)?(?:\\s*\\/\\s*([\\d.]+%?))?\\s*\\)/i);\n if (!m) return null;\n const pct = (s: string, scale: number) =>\n s.endsWith(\"%\") ? (Number.parseFloat(s) / 100) * scale : Number.parseFloat(s);\n const alpha = m[4];\n const a = alpha === undefined ? 1 : pct(alpha, 1);\n return { ...oklchToRgb(pct(m[1] ?? \"0\", 1), pct(m[2] ?? \"0\", 0.4), Number.parseFloat(m[3] ?? \"0\")), a };\n}\n\n/** oklch(L C H) → sRGB via OKLab (Björn Ottosson's reference constants), so\n * Tailwind v4-idiomatic `@theme` colors participate in the brand allowlist. */\nfunction oklchToRgb(L: number, C: number, H: number): { r: number; g: number; b: number } {\n const hRad = (H * Math.PI) / 180;\n const a = C * Math.cos(hRad);\n const b = C * Math.sin(hRad);\n const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;\n const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;\n const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;\n const lin = [\n 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,\n ].map((v) => {\n const g = v <= 0.0031308 ? 12.92 * v : 1.055 * Math.sign(v) * Math.abs(v) ** (1 / 2.4) - 0.055;\n return Math.round(Math.max(0, Math.min(1, g)) * 255);\n });\n return { r: lin[0] ?? 0, g: lin[1] ?? 0, b: lin[2] ?? 0 };\n}\n\n/**\n * Neutrality test for colors as written in source CSS. Handles hex and named\n * neutrals directly; for rgb()/hsl()/oklch() etc. uses the channel/component\n * spread. An unrecognized spelling stays chromatic so a real accent is never\n * silently skipped (impeccable's fail-toward-detection default).\n */\nexport function isNeutralAuthoredColor(rawColor: string): boolean {\n const c = String(rawColor || \"\")\n .trim()\n .toLowerCase();\n if (!c) return false;\n if (NEUTRAL_COLOR_KEYWORDS.has(c)) return true;\n if (/^rgba?\\(/i.test(c)) {\n const channels = c.match(/^rgba?\\(\\s*([\\d.]+)[\\s,]+([\\d.]+)[\\s,]+([\\d.]+)/i);\n if (channels) {\n const values = [1, 2, 3].map((i) => Number(channels[i]));\n return Math.max(...values) - Math.min(...values) < 30;\n }\n return false;\n }\n // oklch/lch chroma is the second component; hsl saturation is the second.\n const oklch = c.match(/oklch\\(\\s*[\\d.]+%?\\s+([\\d.-]+)/i);\n if (oklch) return Number.parseFloat(oklch[1] ?? \"0\") < 0.02;\n const lch = c.match(/lch\\(\\s*[\\d.]+%?\\s+([\\d.-]+)/i);\n if (lch) return Number.parseFloat(lch[1] ?? \"0\") < 3;\n const hsl = c.match(/hsla?\\(\\s*[\\d.-]+\\s*,?\\s*([\\d.]+)%/i);\n if (hsl) return Number.parseFloat(hsl[1] ?? \"0\") < 10;\n const channels = hexChannels(c);\n if (channels) return Math.max(...channels) - Math.min(...channels) < 30;\n return false;\n}\n\nexport function hasChroma(c: Rgb | null, threshold = 30): boolean {\n if (!c) return false;\n return Math.max(c.r, c.g, c.b) - Math.min(c.r, c.g, c.b) >= threshold;\n}\n\n/** Hue in degrees (0–360) for an rgb color. */\nexport function getHue(c: Rgb | null): number {\n if (!c) return 0;\n const r = c.r / 255;\n const g = c.g / 255;\n const b = c.b / 255;\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n if (max === min) return 0;\n const d = max - min;\n let h: number;\n if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n else if (max === g) h = ((b - r) / d + 2) / 6;\n else h = ((r - g) / d + 4) / 6;\n return Math.round(h * 360);\n}\n\n/** Is this hue in the purple/violet/indigo band the AI-palette rule flags?\n * 225–310 covers everything the detection side bans: Tailwind indigo (~239°),\n * the indigo hero hexes (#667eea 229°, #6366f1 239°), violet (#8b5cf6 258°),\n * and purple (#7c3aed 262°+). Keep in lockstep with `AI_PURPLE_HEXES`. */\nexport function isAiPurpleHue(hue: number): boolean {\n return hue >= 225 && hue <= 310;\n}\n\n/** Is this a warm cream/parchment ground (light, r≥g≥b, small warmth)? */\nexport function isCreamColor(c: Rgb): boolean {\n if (Math.min(c.r, c.g, c.b) < 209) return false;\n if (!(c.r >= c.g && c.g >= c.b)) return false;\n const warmth = c.r - c.b;\n return warmth >= 6 && warmth <= 48;\n}\n"],"mappings":"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","names":["channels"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseBrandTokens
3
- } from "./chunk-IX2QLW6G.js";
3
+ } from "./chunk-OEYVPXQZ.js";
4
4
 
5
5
  // src/engine/brand/fonts.ts
6
6
  import { posix } from "path";
@@ -452,4 +452,4 @@ export {
452
452
  stripFontFaceBlocks,
453
453
  upsertFontFaceCss
454
454
  };
455
- //# sourceMappingURL=chunk-K2JTERIO.js.map
455
+ //# sourceMappingURL=chunk-RSYOUKIQ.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  latestVersion
3
- } from "./chunk-Q4WWS3UQ.js";
3
+ } from "./chunk-7SHNGCJ2.js";
4
4
  import {
5
5
  canvasOutputsRoot
6
6
  } from "./chunk-23H4ZJRP.js";
@@ -16,4 +16,4 @@ async function versionOf(slug, run) {
16
16
  export {
17
17
  versionOf
18
18
  };
19
- //# sourceMappingURL=chunk-3PXC2OXT.js.map
19
+ //# sourceMappingURL=chunk-X3PM773M.js.map
@@ -5,7 +5,7 @@ import {
5
5
  outputRatioWarningsFor,
6
6
  shownBaseline,
7
7
  shownVersion
8
- } from "./chunk-5O77KEKO.js";
8
+ } from "./chunk-DFQHO3SL.js";
9
9
  import {
10
10
  waitForSheet
11
11
  } from "./chunk-WN4KX7GE.js";
@@ -36,7 +36,7 @@ import {
36
36
  variantImage,
37
37
  versionRun,
38
38
  writeSeriesMemberCanvas
39
- } from "./chunk-MGIOV72F.js";
39
+ } from "./chunk-GIECLBJ3.js";
40
40
  import {
41
41
  buildFailedRunRecord,
42
42
  buildInitialRunRecord,
@@ -55,7 +55,7 @@ import {
55
55
  recordReviewOutcome,
56
56
  recordSeriesLeadRun,
57
57
  recordSnapshot
58
- } from "./chunk-Q4WWS3UQ.js";
58
+ } from "./chunk-7SHNGCJ2.js";
59
59
  import {
60
60
  BRAND_LOGO_ROLE,
61
61
  CLAIMS_MODEL,
@@ -85,7 +85,7 @@ import {
85
85
  uncastPeople,
86
86
  wireNamedPeople,
87
87
  wireProofBadges
88
- } from "./chunk-AXDKLXCG.js";
88
+ } from "./chunk-CTJPTSD4.js";
89
89
  import {
90
90
  ViewerAnswerSchema,
91
91
  describeViewer,
@@ -3865,4 +3865,4 @@ export {
3865
3865
  executeCanvasRun,
3866
3866
  buildBackendClient
3867
3867
  };
3868
- //# sourceMappingURL=chunk-EFL6RNH3.js.map
3868
+ //# sourceMappingURL=chunk-YXVJA37O.js.map
package/dist/cli.js CHANGED
@@ -116,23 +116,23 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
116
116
  actions: () => import("./actions-KQTXBNJU.js").then((m) => m.actionsCommand),
117
117
  "scheduled-agents": () => import("./scheduled-agents-MFLMG34I.js").then((m) => m.scheduledAgentsCommand),
118
118
  ads: () => import("./ads-WPI3AMSR.js").then((m) => m.adsCommand),
119
- brand: () => import("./brand-LAIIXZBL.js").then((m) => m.brandCommand),
119
+ brand: () => import("./brand-TRPJFCRL.js").then((m) => m.brandCommand),
120
120
  analytics: () => import("./analytics-IFNBT7IM.js").then((m) => m.analyticsCommand),
121
121
  experiment: () => import("./experiment-G3FQVAJU.js").then((m) => m.experimentCommand),
122
122
  ga4: () => import("./ga4-2BL5YAYN.js").then((m) => m.ga4Command),
123
123
  gsc: () => import("./gsc-UU3T2C6B.js").then((m) => m.gscCommand),
124
124
  research: () => import("./research-L2DH5P22.js").then((m) => m.researchCommand),
125
125
  decide: () => import("./decide-TWKH5QQU.js").then((m) => m.decideCommand),
126
- creatives: () => import("./creatives-NK5HOXSF.js").then((m) => m.creativesCommand),
126
+ creatives: () => import("./creatives-K2BKGN6J.js").then((m) => m.creativesCommand),
127
127
  flows: () => import("./flows-274YPU6I.js").then((m) => m.flowsCommand),
128
128
  funnel: () => import("./funnel-LEUXICXU.js").then((m) => m.funnelCommand),
129
129
  images: () => import("./images-ZMZ4WLYH.js").then((m) => m.imagesCommand),
130
130
  studio: () => import("./studio-MARZ22J2.js").then((m) => m.studioCommand),
131
131
  avatars: () => import("./avatars-EIETXCF3.js").then((m) => m.avatarsCommand),
132
- landing: () => import("./landing-EJGYTI5W.js").then((m) => m.landingCommand),
132
+ landing: () => import("./landing-6OJYUEWP.js").then((m) => m.landingCommand),
133
133
  videos: () => import("./videos-KEDR4GG3.js").then((m) => m.videosCommand),
134
134
  testimonials: () => import("./testimonials-G7CCH7VN.js").then((m) => m.testimonialsCommand),
135
- canvas: () => import("./canvas-EAC3236V.js").then((m) => m.canvasCommand),
135
+ canvas: () => import("./canvas-ZKBXEUYK.js").then((m) => m.canvasCommand),
136
136
  tags: () => import("./tags-6RQVXS47.js").then((m) => m.tagsCommand),
137
137
  "tag-manager": () => import("./tag-manager-G3LBQZUL.js").then((m) => m.tagManagerCommand),
138
138
  chats: () => import("./chats-S5FCV7YO.js").then((m) => m.chatsCommand),
@@ -41,13 +41,13 @@ Full guide: __tooling__/docs/tools/baker/creatives.md`
41
41
  subCommands: {
42
42
  brief: () => import("./brief-7PICJ4CO.js").then((m) => m.briefCommand),
43
43
  ideas: () => import("./ideas-K6FQVDUA.js").then((m) => m.ideasCommand),
44
- make: () => import("./make-ZYEFMAR6.js").then((m) => m.makeCommand),
45
- translate: () => import("./translate-ACGKNKQP.js").then((m) => m.translateCommand),
44
+ make: () => import("./make-2XZBKFJC.js").then((m) => m.makeCommand),
45
+ translate: () => import("./translate-ZONETQ2W.js").then((m) => m.translateCommand),
46
46
  learn: () => import("./learn-66SKYR36.js").then((m) => m.learnCommand),
47
- feedback: () => import("./feedback-OCN3XNKF.js").then((m) => m.feedbackCommand),
47
+ feedback: () => import("./feedback-7JVO62CE.js").then((m) => m.feedbackCommand),
48
48
  rules: () => import("./rules-TPW37OVS.js").then((m) => m.rulesCommand),
49
49
  check: () => import("./check-5RW2MX7V.js").then((m) => m.checkCommand),
50
- select: () => import("./select-POYNXE2L.js").then((m) => m.selectCommand),
50
+ select: () => import("./select-JM2MFVNP.js").then((m) => m.selectCommand),
51
51
  publish: () => import("./publish-V5YGMK53.js").then((m) => m.publishCommand),
52
52
  performance: () => import("./performance-SPGPGXE5.js").then((m) => m.performanceCommand)
53
53
  }
@@ -55,4 +55,4 @@ Full guide: __tooling__/docs/tools/baker/creatives.md`
55
55
  export {
56
56
  creativesCommand
57
57
  };
58
- //# sourceMappingURL=creatives-NK5HOXSF.js.map
58
+ //# sourceMappingURL=creatives-K2BKGN6J.js.map
@@ -5,12 +5,13 @@ import {
5
5
  brandCommitsAiHue,
6
6
  brandCommitsCream,
7
7
  brandCommitsFont,
8
+ brandCommitsGradient,
8
9
  hasChroma,
9
10
  isCreamColor,
10
11
  isNeutralAuthoredColor,
11
12
  loadBrandTokens,
12
13
  parseColor
13
- } from "./chunk-IX2QLW6G.js";
14
+ } from "./chunk-OEYVPXQZ.js";
14
15
  import "./chunk-JYALBYN7.js";
15
16
  import {
16
17
  STACCATO_MAX_WORDS,
@@ -173,7 +174,8 @@ var RULE_META = {
173
174
  "gradient-text": {
174
175
  family: "color",
175
176
  severity: "block",
176
- note: "Gradient text is a top AI tell. Emphasis comes from weight or size, not a clipped gradient fill."
177
+ note: "Gradient text is a top AI tell. Emphasis comes from weight or size, not a clipped gradient fill.",
178
+ brandAware: "gradient"
177
179
  },
178
180
  "broken-image": {
179
181
  family: "integrity",
@@ -515,7 +517,11 @@ var LINE_MATCHERS = [
515
517
  // and arbitrary-value `bg-[linear-gradient(…)]` spellings.
516
518
  /\bbg-gradient-to-|\bbg-linear-to-|\bbg-(?:radial|conic)\b|\bbg-\[(?:linear|radial|conic)-gradient/i.test(line)
517
519
  ),
518
- fmt: () => "bg-clip-text + gradient"
520
+ // The stops ride in the snippet so the brand allowlist can read them.
521
+ fmt: (_m, line) => {
522
+ const stops = line.match(/\b(?:from|via|to)-\[[^\]\s]+\]|\bbg-\[(?:linear|radial|conic)-gradient[^\]\s]*\]/g);
523
+ return stops ? `bg-clip-text + ${stops.join(" ")}` : "bg-clip-text + gradient";
524
+ }
519
525
  },
520
526
  // ── Gray text on a colored background (Tailwind). Shade ≥ 300 only: a pale
521
527
  // tint panel (bg-blue-50 info box) is a normal light ground, not "on color". ──
@@ -857,8 +863,9 @@ var ANALYZERS = [
857
863
  for (const m of text.matchAll(/(?:-webkit-)?background-clip\s*:\s*text/gi)) {
858
864
  const line = lineOf(text, m.index ?? 0);
859
865
  const window = lines.slice(Math.max(0, line - 7), Math.min(lines.length, line + 6)).join("\n");
860
- if (/gradient\(/i.test(window)) {
861
- out.push({ id: "gradient-text", snippet: "background-clip: text + gradient", file, line });
866
+ const gradient = window.match(/(?:repeating-)?(?:linear|radial|conic)-gradient\((?:[^()]|\([^()]*\))*\)/i);
867
+ if (gradient) {
868
+ out.push({ id: "gradient-text", snippet: `background-clip: text + ${gradient[0]}`, file, line });
862
869
  }
863
870
  }
864
871
  return out;
@@ -1134,7 +1141,7 @@ function clamp01(n) {
1134
1141
  function applyBrand(id, snippet2, severity, brand) {
1135
1142
  const meta = RULE_META[id];
1136
1143
  if (!meta?.brandAware || !brand.hasTokens) return { severity, brandDrift: false };
1137
- const matched = meta.brandAware === "font" && brandCommitsFont(brand, snippet2) || meta.brandAware === "color" && id === "ai-color-palette" && brandCommitsAiHue(brand) || meta.brandAware === "color" && id === "cream-palette" && brandCommitsCream(brand);
1144
+ const matched = meta.brandAware === "font" && brandCommitsFont(brand, snippet2) || meta.brandAware === "color" && id === "ai-color-palette" && brandCommitsAiHue(brand) || meta.brandAware === "color" && id === "cream-palette" && brandCommitsCream(brand) || meta.brandAware === "gradient" && brandCommitsGradient(brand, snippet2);
1138
1145
  return matched ? { severity: "advisory", brandDrift: true } : { severity, brandDrift: false };
1139
1146
  }
1140
1147
  async function critiqueLanding(input) {
@@ -1448,4 +1455,4 @@ export {
1448
1455
  critiqueOne,
1449
1456
  parseCritiqueSlugs
1450
1457
  };
1451
- //# sourceMappingURL=critique-N5OL42C6.js.map
1458
+ //# sourceMappingURL=critique-2UMC3GJP.js.map