@koda-sl/baker-cli 0.271.0-dev.1f1c09c80 → 0.272.0-dev.1f1c09c80

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/README.md CHANGED
@@ -3669,6 +3669,9 @@ paid for by the time it runs — and the findings come back as `review` and `hin
3669
3669
  - **Across the reel:** whether the cast stays the same people, whether a brand mark is
3670
3670
  visible anywhere, and whether the last frames close the ad or simply stop.
3671
3671
 
3672
+ Each per-frame finding names the **scene file** that produced that second, so a defect is
3673
+ an edit rather than a hunt — and re-running re-renders only the scenes you changed.
3674
+
3672
3675
  It needs `GEMINI_API_KEY` (the Runtime receives it automatically). Without one the run
3673
3676
  says the video was **not** reviewed rather than reporting it clean, and a frame that
3674
3677
  could not be read is reported as unchecked for the same reason.
package/dist/cli.js CHANGED
@@ -30531,10 +30531,10 @@ function runDirsToPrune(entries, keep, currentRunId) {
30531
30531
  return runs.slice(0, Math.max(0, runs.length - keep));
30532
30532
  }
30533
30533
  async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
30534
- const { readdir: readdir11 } = await import("fs/promises");
30534
+ const { readdir: readdir12 } = await import("fs/promises");
30535
30535
  let entries;
30536
30536
  try {
30537
- entries = await readdir11(outputsDir);
30537
+ entries = await readdir12(outputsDir);
30538
30538
  } catch {
30539
30539
  return;
30540
30540
  }
@@ -30827,7 +30827,7 @@ async function restoreRunSnapshot(manifest, targetDir, opts = {}) {
30827
30827
 
30828
30828
  // src/engine/nodes/local/lib/render-review.ts
30829
30829
  import { execFile as execFile2 } from "child_process";
30830
- import { mkdtemp, readFile as readFile11, rm as rm5 } from "fs/promises";
30830
+ import { mkdtemp, readdir as readdir6, readFile as readFile11, rm as rm5 } from "fs/promises";
30831
30831
  import { tmpdir } from "os";
30832
30832
  import path15 from "path";
30833
30833
  import { promisify as promisify2 } from "util";
@@ -30835,6 +30835,9 @@ import { z as z28 } from "zod";
30835
30835
  var RENDER_REVIEW_MODEL = "gemini-pro-latest";
30836
30836
  var RENDER_REVIEW_FRAMES = 12;
30837
30837
  var RENDER_REVIEW_WIDTH = 1024;
30838
+ function sceneAt(windows, atS) {
30839
+ return windows.find((w) => atS >= w.startS && atS < w.endS)?.file;
30840
+ }
30838
30841
  function reviewFrameTimes(durationS, frames) {
30839
30842
  const n = Math.max(2, frames);
30840
30843
  const last = Math.max(0, durationS - 0.15);
@@ -30937,19 +30940,23 @@ function collapseRepeats(frames) {
30937
30940
  }
30938
30941
  return [...seen.values()];
30939
30942
  }
30940
- function classifyFrameFindings(frames) {
30943
+ function classifyFrameFindings(frames, windows = []) {
30941
30944
  const seen = collapseRepeats(frames);
30942
30945
  const anatomy = seen.filter((f) => ANATOMY.test(f.what));
30943
30946
  const rest = seen.filter((f) => !ANATOMY.test(f.what));
30944
- const findings = rest.map((f) => ({
30945
- severity: "blocking",
30946
- what: `At ${f.at.map((s) => `${s}s`).join(", ")}: ${f.what}`
30947
- }));
30947
+ const findings = rest.map((f) => {
30948
+ const scene = sceneAt(windows, f.at[0]);
30949
+ return {
30950
+ severity: "blocking",
30951
+ what: `At ${f.at.map((s) => `${s}s`).join(", ")}${scene ? ` (${scene})` : ""}: ${f.what}`,
30952
+ ...scene ? { scene } : {}
30953
+ };
30954
+ });
30948
30955
  if (anatomy.length > 0) {
30949
30956
  const at = [...new Set(anatomy.flatMap((f) => f.at))].sort((a, b) => a - b);
30950
30957
  findings.push({
30951
30958
  severity: "blocking",
30952
- what: `Malformed hands or limbs in ${at.length} of ${frames.length} sampled frames (${at.map((s) => `${s}s`).join(", ")}): ${anatomy.map((f) => f.what).join(" ")} Re-render those beats \u2014 keep hands out of frame, or away from the object being held.`
30959
+ what: `Malformed hands or limbs in ${at.length} of ${frames.length} sampled frames (${at.map((s) => `${s}s${sceneAt(windows, s) ? ` \u2192 ${sceneAt(windows, s)}` : ""}`).join(", ")}): ${anatomy.map((f) => f.what).join(" ")} Re-render those beats \u2014 keep hands out of frame, or away from the object being held.`
30953
30960
  });
30954
30961
  }
30955
30962
  return findings;
@@ -31074,7 +31081,7 @@ async function reviewRenderedVideo(opts) {
31074
31081
  }));
31075
31082
  const unread = perFrame.filter((f) => f.json === null).length;
31076
31083
  if (unread === perFrame.length && reel === null) return null;
31077
- const findings = [...classifyFrameFindings(perFrame), ...classifyReelFindings(reel)];
31084
+ const findings = [...classifyFrameFindings(perFrame, opts.windows ?? []), ...classifyReelFindings(reel)];
31078
31085
  if (unread > 0) {
31079
31086
  findings.push({
31080
31087
  severity: "warning",
@@ -31086,6 +31093,24 @@ async function reviewRenderedVideo(opts) {
31086
31093
  await rm5(dir, { recursive: true, force: true });
31087
31094
  }
31088
31095
  }
31096
+ async function sceneWindowsBeside(canvasPath) {
31097
+ const dir = path15.join(path15.dirname(canvasPath), "scenes");
31098
+ const files = await readdir6(dir).catch(() => null);
31099
+ if (!files) return [];
31100
+ const windows = [];
31101
+ for (const name of files.filter((f) => f.endsWith(".json")).sort()) {
31102
+ const raw = await readFile11(path15.join(dir, name), "utf-8").catch(() => null);
31103
+ if (!raw) continue;
31104
+ try {
31105
+ const scene = JSON.parse(raw);
31106
+ if (typeof scene.start_s === "number" && typeof scene.end_s === "number") {
31107
+ windows.push({ file: `scenes/${name}`, startS: scene.start_s, endS: scene.end_s });
31108
+ }
31109
+ } catch {
31110
+ }
31111
+ }
31112
+ return windows;
31113
+ }
31089
31114
 
31090
31115
  // src/commands/canvas/run.ts
31091
31116
  var runCommand = defineCommand100({
@@ -31373,10 +31398,13 @@ ${describeRewrites(healed.rewrites)}
31373
31398
  `));
31374
31399
  }
31375
31400
  const reviewable = videoPathFromOutput(result.output);
31376
- const review = reviewable ? await reviewRenderedVideo({ video: reviewable }) : null;
31401
+ const review = reviewable ? await reviewRenderedVideo({ video: reviewable, windows: await sceneWindowsBeside(filePath) }) : null;
31377
31402
  const hints = reviewable ? review === null ? [RENDER_REVIEW_UNAVAILABLE] : review.length === 0 ? ["Reviewed the finished video frame by frame \u2014 nothing to report."] : [
31378
31403
  `Reviewed the finished video and found ${review.length} thing${review.length === 1 ? "" : "s"} to fix. Show the user what is wrong before you show them the ad.`,
31379
- ...review.map((f) => `${f.severity === "blocking" ? "MUST FIX" : "SHOULD FIX"} \u2014 ${f.what}`)
31404
+ ...review.map((f) => `${f.severity === "blocking" ? "MUST FIX" : "SHOULD FIX"} \u2014 ${f.what}`),
31405
+ ...review.some((f) => f.scene) ? [
31406
+ `Fix them, then re-run: edit the \`show\` of the scene files named above \u2014 a shot with a person handling equipment is where limbs and objects go wrong, so widen it, move the hands out of frame, or drop the person from that beat. Re-running re-renders ONLY the scenes you edited; everything else comes from the cache (this run: ${result.stats?.cached_nodes ?? 0} of ${result.stats?.total_nodes ?? 0} nodes were cached).`
31407
+ ] : []
31380
31408
  ] : [];
31381
31409
  process.stdout.write(
31382
31410
  `${JSON.stringify(
@@ -31921,7 +31949,7 @@ function isValidScaffoldSlug(slug) {
31921
31949
  }
31922
31950
 
31923
31951
  // src/commands/canvas/sync-definition.ts
31924
- import { readdir as readdir6, readFile as readFile13, stat as stat3 } from "fs/promises";
31952
+ import { readdir as readdir7, readFile as readFile13, stat as stat3 } from "fs/promises";
31925
31953
  import path20 from "path";
31926
31954
  import { defineCommand as defineCommand102 } from "citty";
31927
31955
 
@@ -32027,7 +32055,7 @@ async function resolveCanvasPath(inputPath) {
32027
32055
  }
32028
32056
  let entries;
32029
32057
  try {
32030
- entries = await readdir6(dir);
32058
+ entries = await readdir7(dir);
32031
32059
  } catch {
32032
32060
  return null;
32033
32061
  }
@@ -32692,7 +32720,7 @@ function adSpecToBlueprint(spec) {
32692
32720
  const market = marketFor(spec);
32693
32721
  const place = market ? ` Set in ${market}: the architecture, streets and styling are ${market}'s.` : "";
32694
32722
  const currency = market ? CURRENCY_BY_MARKET[market.toLowerCase()] : void 0;
32695
- const physics = " Everything obeys real-world physics: paper, card and screens are OPAQUE with nothing showing through from behind, every object is at believable real-world scale next to the people handling it, and every object has its real-world form and construction \u2014 a phone has ONE screen and it is on the front. NO readable text or numbers anywhere in frame \u2014 phone screens, documents and signage stay illegible or out of focus, because any figure the model invents will contradict the script." + (currency ? ` If a currency is unavoidably visible it is ${currency}.` : "");
32723
+ const physics = " Everything obeys real-world physics: paper, card and screens are OPAQUE with nothing showing through from behind, every object is at believable real-world scale next to the people handling it, and every object has its real-world form and construction \u2014 a phone has ONE screen and it is on the front. NO readable text or numbers anywhere in frame \u2014 phone screens, documents and signage stay illegible or out of focus, because any figure the model invents will contradict the script. Hands are kept simple: no close-up of fingers manipulating small parts, no hand gripping the edge of an object, and each person has exactly TWO arms and TWO legs, all attached and all visible or all out of frame. Anyone working does so the way the trade actually does it: nobody stands or kneels on the equipment being installed, nothing is fitted overhanging an edge or floating unsupported, and every part rests on the structure that would really carry it." + (currency ? ` If a currency is unavoidably visible it is ${currency}.` : "");
32696
32724
  const closesOnCard = endCardWanted(spec);
32697
32725
  let clock = 0;
32698
32726
  const scenes = spec.beats.map((beat, i) => {
@@ -32877,7 +32905,7 @@ function injectAdBrandOverlay(indexHtml, overlayHtml) {
32877
32905
  }
32878
32906
 
32879
32907
  // src/engine/scaffold/ad-brand-source.ts
32880
- import { readdir as readdir7, readFile as readFile15 } from "fs/promises";
32908
+ import { readdir as readdir8, readFile as readFile15 } from "fs/promises";
32881
32909
  import path22 from "path";
32882
32910
  var BRAND_DIR = "src/brand";
32883
32911
  function paletteFromBrandDoc(markdown) {
@@ -32917,7 +32945,7 @@ async function readBrandFromWorkspace(root = ".") {
32917
32945
  if (palette.length > 0) found.palette = palette;
32918
32946
  }
32919
32947
  const dir = path22.join(root, BRAND_DIR, "logos");
32920
- const files = await readdir7(dir).catch(() => null);
32948
+ const files = await readdir8(dir).catch(() => null);
32921
32949
  if (files) {
32922
32950
  const mark = pickBrandMark(files);
32923
32951
  if (mark) found.logo = path22.join(BRAND_DIR, "logos", mark);
@@ -33199,7 +33227,7 @@ function routeVideoModel(input) {
33199
33227
 
33200
33228
  // src/engine/nodes/local/lib/sceneDetect.ts
33201
33229
  import { execFile as execFile3 } from "child_process";
33202
- import { mkdtemp as mkdtemp2, readdir as readdir8, readFile as readFile17, rm as rm6 } from "fs/promises";
33230
+ import { mkdtemp as mkdtemp2, readdir as readdir9, readFile as readFile17, rm as rm6 } from "fs/promises";
33203
33231
  import { tmpdir as tmpdir2 } from "os";
33204
33232
  import { join as join2 } from "path";
33205
33233
  import { promisify as promisify3 } from "util";
@@ -33273,7 +33301,7 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
33273
33301
  ],
33274
33302
  { encoding: "utf-8", maxBuffer: 32 * 1024 * 1024, timeout: timeoutMs }
33275
33303
  );
33276
- const csvName = (await readdir8(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
33304
+ const csvName = (await readdir9(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
33277
33305
  if (!csvName) return [];
33278
33306
  return parsePySceneDetectCsvCuts(await readFile17(join2(outDir, csvName), "utf-8"));
33279
33307
  } finally {
@@ -42332,7 +42360,7 @@ Full guide: __tooling__/docs/tools/baker/images.md`
42332
42360
  import { defineCommand as defineCommand167 } from "citty";
42333
42361
 
42334
42362
  // src/commands/landing/critique.ts
42335
- import { readdir as readdir10, stat as stat6 } from "fs/promises";
42363
+ import { readdir as readdir11, stat as stat6 } from "fs/promises";
42336
42364
  import path31 from "path";
42337
42365
  import { defineCommand as defineCommand157 } from "citty";
42338
42366
 
@@ -43319,7 +43347,7 @@ async function writeCritiqueSnapshot(projectRoot, snapshot) {
43319
43347
  }
43320
43348
 
43321
43349
  // src/commands/landing/source-version.ts
43322
- import { readdir as readdir9, readFile as readFile24, stat as stat5 } from "fs/promises";
43350
+ import { readdir as readdir10, readFile as readFile24, stat as stat5 } from "fs/promises";
43323
43351
  import path30 from "path";
43324
43352
  async function landingSourceRelPaths(landingDir) {
43325
43353
  const rel = [];
@@ -43360,7 +43388,7 @@ async function isFile(p) {
43360
43388
  async function walkAstro(dir) {
43361
43389
  let entries;
43362
43390
  try {
43363
- entries = await readdir9(dir, { withFileTypes: true });
43391
+ entries = await readdir10(dir, { withFileTypes: true });
43364
43392
  } catch {
43365
43393
  return [];
43366
43394
  }
@@ -43486,7 +43514,7 @@ async function critiqueOne(projectRoot, slug, brand) {
43486
43514
  }
43487
43515
  async function listLandingSlugs(projectRoot) {
43488
43516
  try {
43489
- const entries = await readdir10(path31.join(projectRoot, "src", "pages"), { withFileTypes: true });
43517
+ const entries = await readdir11(path31.join(projectRoot, "src", "pages"), { withFileTypes: true });
43490
43518
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
43491
43519
  } catch {
43492
43520
  return [];