@jokerized/decksmith 0.9.6 → 0.9.7

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/cli.js CHANGED
@@ -23630,15 +23630,26 @@ function isScene(beat2, design2) {
23630
23630
  return pictured(p.backdrop);
23631
23631
  }
23632
23632
  }
23633
+ function shapesFor(storyboard, i, offered, images) {
23634
+ const beats = storyboard.beats;
23635
+ const others = beats.filter((_, k) => k !== i);
23636
+ const kinetic2 = others.filter((b) => b.archetype === "kinetic").length;
23637
+ const panels = others.filter((b) => PANEL_ARCHETYPES.has(b.archetype)).length;
23638
+ const panelCap = panelsAllowed(beats.length, images);
23639
+ return offered.filter(
23640
+ (a) => a !== beats[i - 1]?.archetype && a !== beats[i + 1]?.archetype && (a !== "title" || i === 0) && (a !== "kinetic" || kinetic2 < KINETIC_MAX) && (!PANEL_ARCHETYPES.has(a) || panels < panelCap)
23641
+ );
23642
+ }
23633
23643
  function varietyFindings(storyboard, images, design2) {
23634
23644
  const beats = storyboard.beats;
23635
23645
  const out = [];
23646
+ const avoid = (beat2, other) => [beat2.archetype, other?.archetype].filter((x, k, xs) => x !== void 0 && xs.indexOf(x) === k).map((x) => `\`${x}\``).join(" or ");
23636
23647
  for (let i = 1; i < beats.length; i++) {
23637
23648
  const a = beats[i - 1];
23638
23649
  const b = beats[i];
23639
23650
  if (a.archetype === b.archetype) {
23640
23651
  out.push(
23641
- `${a.id} and ${b.id} are both \`${a.archetype}\`, next to each other. Draw one of them as a different shape.`
23652
+ `${a.id} and ${b.id} are both \`${a.archetype}\`, next to each other. Redraw ${a.id} as anything but ${avoid(a, beats[i - 2])}, or ${b.id} as anything but ${avoid(b, beats[i + 1])}.`
23642
23653
  );
23643
23654
  }
23644
23655
  }
@@ -25011,7 +25022,8 @@ async function codexPlanner(source, opts = {}) {
25011
25022
  try {
25012
25023
  const schemaPath = join9(dir, "storyboard.schema.json");
25013
25024
  const outPath = join9(dir, "storyboard.json");
25014
- await writeFile7(schemaPath, JSON.stringify(schemaFor(prefs, source)));
25025
+ const schema = schemaFor(prefs, source);
25026
+ await writeFile7(schemaPath, JSON.stringify(schema));
25015
25027
  const ask = async (prompt2) => {
25016
25028
  await rm3(outPath, { force: true });
25017
25029
  await (opts.run ?? runCodex)({
@@ -25155,6 +25167,46 @@ ${left.map((m) => ` ${m}`).join("\n")}`
25155
25167
  );
25156
25168
  return plan;
25157
25169
  };
25170
+ const beatSchemaPath = join9(dir, "beat.schema.json");
25171
+ const offered = beatVariants(schema).map((v) => v.properties.archetype.const);
25172
+ const reshape2 = async (deck, i) => {
25173
+ const beat2 = deck.beats[i];
25174
+ const shapes = shapesFor(deck, i, offered, prefs.images);
25175
+ if (!shapes.length) return deck;
25176
+ await writeFile7(beatSchemaPath, JSON.stringify(beatSchemaFor(schema, shapes)));
25177
+ let problems = [];
25178
+ for (let t2 = 1; t2 <= SCENE_REPAIRS; t2++) {
25179
+ opts.onReshape?.(beat2.id, shapes);
25180
+ const out = join9(dir, `beat-${beat2.id}-${t2}.json`);
25181
+ try {
25182
+ await (opts.run ?? runCodex)({
25183
+ prompt: beatPrompt(prompt, deck, beat2, shapes, problems),
25184
+ schemaPath: beatSchemaPath,
25185
+ outPath: out,
25186
+ ...opts.model === void 0 ? {} : { model: opts.model },
25187
+ timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS
25188
+ });
25189
+ const got = stripNulls(JSON.parse(await readFile8(out, "utf8")))?.beat;
25190
+ const redrawn = {
25191
+ ...got,
25192
+ id: beat2.id,
25193
+ ...beat2.literal ? { literal: beat2.literal } : {}
25194
+ };
25195
+ const beats = deck.beats.map((b, k) => k === i ? redrawn : b);
25196
+ return withStockLabels(checkPlan({ ...deck, beats }, source, prefs), source);
25197
+ } catch (err) {
25198
+ problems = err instanceof PlanRejected ? err.problems : [`the ask failed: ${err instanceof Error ? err.message : err}`];
25199
+ }
25200
+ }
25201
+ return deck;
25202
+ };
25203
+ const neighbours = async (planned) => {
25204
+ let plan = planned;
25205
+ for (let i = 1; i < plan.beats.length; i++)
25206
+ if (plan.beats[i]?.archetype === plan.beats[i - 1]?.archetype)
25207
+ plan = await reshape2(plan, i);
25208
+ return plan;
25209
+ };
25158
25210
  let answer = await attempt2(prompt);
25159
25211
  if (answer.plan && listOf(answer.broken).length === 0) return await scenes(answer.plan);
25160
25212
  const firstBeats = beatCount(answer.json);
@@ -25174,7 +25226,13 @@ ${left.map((m) => ` ${m}`).join("\n")}`
25174
25226
  advice: []
25175
25227
  };
25176
25228
  if (next.plan && listOf(still).length === 0) return await scenes(next.plan);
25177
- if (round8 === REPAIRS) throw unrepaired(next, still, round8);
25229
+ if (round8 === REPAIRS) {
25230
+ if (!next.plan || !listOf(still).every(isNeighbour)) throw unrepaired(next, still, round8);
25231
+ const plan = await neighbours(next.plan);
25232
+ const left = { ...findings(plan), advice: [] };
25233
+ if (listOf(left).length) throw unrepaired(next, left, round8);
25234
+ return await scenes(plan);
25235
+ }
25178
25236
  merging ||= still.size.length > 0;
25179
25237
  answer = { ...next, broken: still };
25180
25238
  }
@@ -25229,6 +25287,37 @@ ${list2}
25229
25287
 
25230
25288
  Return {"literal": ...}: beat ${id2}'s scene, redrawn so none of these hold \u2014 every number, word and figure one the source states, every slot its verb needs filled. Keep its verb unless a finding names another. Return a scene: this ask has no way to leave it out. No other beat is yours to change.
25231
25289
 
25290
+ THE STORYBOARD:
25291
+ ${JSON.stringify(plan, null, 2)}`;
25292
+ }
25293
+ function isNeighbour(finding2) {
25294
+ return /^\S+ and \S+ are both `[^`]+`, next to each other\./.test(finding2);
25295
+ }
25296
+ function beatVariants(schema) {
25297
+ return schema.properties.beats.items.anyOf;
25298
+ }
25299
+ function beatSchemaFor(schema, shapes) {
25300
+ const anyOf = beatVariants(schema).filter((v) => shapes.includes(v.properties.archetype.const)).map((v) => hideFromPlanner(v, /* @__PURE__ */ new Set(["literal"])));
25301
+ return {
25302
+ type: "object",
25303
+ properties: { beat: { anyOf } },
25304
+ required: ["beat"],
25305
+ additionalProperties: false
25306
+ };
25307
+ }
25308
+ function beatPrompt(prompt, plan, beat2, shapes, problems) {
25309
+ const refused = problems.length ? `
25310
+ Your last answer was refused for:
25311
+ ${problems.map((m) => ` - ${m}`).join("\n")}
25312
+ ` : "";
25313
+ return `${prompt}
25314
+
25315
+ THE DECK IS PLANNED. ONE BEAT'S SHAPE IS LEFT.
25316
+
25317
+ The storyboard below is settled, but beat ${beat2.id} is \`${beat2.archetype}\` beside a beat of the same shape, and no two beats in a row may share an archetype.
25318
+ ${refused}
25319
+ Return {"beat": ...}: beat ${beat2.id} redrawn as one of ${shapes.map((s) => `\`${s}\``).join(", ")} \u2014 whichever shows its point best \u2014 with the params that shape needs, every number, word and figure one the source states. Keep its id, its point, its evidence, its narration and its part. No other beat is yours to change.
25320
+
25232
25321
  THE STORYBOARD:
25233
25322
  ${JSON.stringify(plan, null, 2)}`;
25234
25323
  }
@@ -25326,7 +25415,9 @@ async function readPlan(outPath, source, prefs) {
25326
25415
  ${raw2.slice(0, 200)}`
25327
25416
  );
25328
25417
  }
25329
- const plan = stripNulls(parsed);
25418
+ return checkPlan(stripNulls(parsed), source, prefs);
25419
+ }
25420
+ function checkPlan(plan, source, prefs) {
25330
25421
  const result = storyboardSchema.safeParse(plan);
25331
25422
  if (!result.success) {
25332
25423
  const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
@@ -38201,6 +38292,7 @@ imageFlags(
38201
38292
  step(`plan: redrawing ${beat2}'s scene on its own \u2014`);
38202
38293
  for (const m of broken) step(`plan: ${m}`);
38203
38294
  },
38295
+ onReshape: (beat2, shapes) => step(`plan: redrawing ${beat2} on its own, as one of: ${shapes.join(", ")}`),
38204
38296
  onDropScenes: (beats, why3) => {
38205
38297
  step(
38206
38298
  `plan: WARNING: ${beats.join(", ")} drawn by their archetype instead; their scenes stay broken \u2014`
package/dist/index.js CHANGED
@@ -25977,15 +25977,26 @@ function isScene(beat2, design2) {
25977
25977
  return pictured(p.backdrop);
25978
25978
  }
25979
25979
  }
25980
+ function shapesFor(storyboard, i, offered, images) {
25981
+ const beats = storyboard.beats;
25982
+ const others = beats.filter((_, k) => k !== i);
25983
+ const kinetic2 = others.filter((b) => b.archetype === "kinetic").length;
25984
+ const panels = others.filter((b) => PANEL_ARCHETYPES.has(b.archetype)).length;
25985
+ const panelCap = panelsAllowed(beats.length, images);
25986
+ return offered.filter(
25987
+ (a) => a !== beats[i - 1]?.archetype && a !== beats[i + 1]?.archetype && (a !== "title" || i === 0) && (a !== "kinetic" || kinetic2 < KINETIC_MAX) && (!PANEL_ARCHETYPES.has(a) || panels < panelCap)
25988
+ );
25989
+ }
25980
25990
  function varietyFindings(storyboard, images, design2) {
25981
25991
  const beats = storyboard.beats;
25982
25992
  const out = [];
25993
+ const avoid = (beat2, other) => [beat2.archetype, other?.archetype].filter((x, k, xs) => x !== void 0 && xs.indexOf(x) === k).map((x) => `\`${x}\``).join(" or ");
25983
25994
  for (let i = 1; i < beats.length; i++) {
25984
25995
  const a = beats[i - 1];
25985
25996
  const b = beats[i];
25986
25997
  if (a.archetype === b.archetype) {
25987
25998
  out.push(
25988
- `${a.id} and ${b.id} are both \`${a.archetype}\`, next to each other. Draw one of them as a different shape.`
25999
+ `${a.id} and ${b.id} are both \`${a.archetype}\`, next to each other. Redraw ${a.id} as anything but ${avoid(a, beats[i - 2])}, or ${b.id} as anything but ${avoid(b, beats[i + 1])}.`
25989
26000
  );
25990
26001
  }
25991
26002
  }
@@ -27358,7 +27369,8 @@ async function codexPlanner(source, opts = {}) {
27358
27369
  try {
27359
27370
  const schemaPath = join12(dir, "storyboard.schema.json");
27360
27371
  const outPath = join12(dir, "storyboard.json");
27361
- await writeFile10(schemaPath, JSON.stringify(schemaFor(prefs, source)));
27372
+ const schema = schemaFor(prefs, source);
27373
+ await writeFile10(schemaPath, JSON.stringify(schema));
27362
27374
  const ask = async (prompt2) => {
27363
27375
  await rm5(outPath, { force: true });
27364
27376
  await (opts.run ?? runCodex)({
@@ -27502,6 +27514,46 @@ ${left.map((m) => ` ${m}`).join("\n")}`
27502
27514
  );
27503
27515
  return plan;
27504
27516
  };
27517
+ const beatSchemaPath = join12(dir, "beat.schema.json");
27518
+ const offered = beatVariants(schema).map((v) => v.properties.archetype.const);
27519
+ const reshape2 = async (deck, i) => {
27520
+ const beat2 = deck.beats[i];
27521
+ const shapes = shapesFor(deck, i, offered, prefs.images);
27522
+ if (!shapes.length) return deck;
27523
+ await writeFile10(beatSchemaPath, JSON.stringify(beatSchemaFor(schema, shapes)));
27524
+ let problems = [];
27525
+ for (let t2 = 1; t2 <= SCENE_REPAIRS; t2++) {
27526
+ opts.onReshape?.(beat2.id, shapes);
27527
+ const out = join12(dir, `beat-${beat2.id}-${t2}.json`);
27528
+ try {
27529
+ await (opts.run ?? runCodex)({
27530
+ prompt: beatPrompt(prompt, deck, beat2, shapes, problems),
27531
+ schemaPath: beatSchemaPath,
27532
+ outPath: out,
27533
+ ...opts.model === void 0 ? {} : { model: opts.model },
27534
+ timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2
27535
+ });
27536
+ const got = stripNulls(JSON.parse(await readFile12(out, "utf8")))?.beat;
27537
+ const redrawn = {
27538
+ ...got,
27539
+ id: beat2.id,
27540
+ ...beat2.literal ? { literal: beat2.literal } : {}
27541
+ };
27542
+ const beats = deck.beats.map((b, k) => k === i ? redrawn : b);
27543
+ return withStockLabels(checkPlan({ ...deck, beats }, source, prefs), source);
27544
+ } catch (err) {
27545
+ problems = err instanceof PlanRejected ? err.problems : [`the ask failed: ${err instanceof Error ? err.message : err}`];
27546
+ }
27547
+ }
27548
+ return deck;
27549
+ };
27550
+ const neighbours = async (planned) => {
27551
+ let plan = planned;
27552
+ for (let i = 1; i < plan.beats.length; i++)
27553
+ if (plan.beats[i]?.archetype === plan.beats[i - 1]?.archetype)
27554
+ plan = await reshape2(plan, i);
27555
+ return plan;
27556
+ };
27505
27557
  let answer = await attempt2(prompt);
27506
27558
  if (answer.plan && listOf(answer.broken).length === 0) return await scenes(answer.plan);
27507
27559
  const firstBeats = beatCount(answer.json);
@@ -27521,7 +27573,13 @@ ${left.map((m) => ` ${m}`).join("\n")}`
27521
27573
  advice: []
27522
27574
  };
27523
27575
  if (next.plan && listOf(still).length === 0) return await scenes(next.plan);
27524
- if (round8 === REPAIRS) throw unrepaired(next, still, round8);
27576
+ if (round8 === REPAIRS) {
27577
+ if (!next.plan || !listOf(still).every(isNeighbour)) throw unrepaired(next, still, round8);
27578
+ const plan = await neighbours(next.plan);
27579
+ const left = { ...findings(plan), advice: [] };
27580
+ if (listOf(left).length) throw unrepaired(next, left, round8);
27581
+ return await scenes(plan);
27582
+ }
27525
27583
  merging ||= still.size.length > 0;
27526
27584
  answer = { ...next, broken: still };
27527
27585
  }
@@ -27576,6 +27634,37 @@ ${list2}
27576
27634
 
27577
27635
  Return {"literal": ...}: beat ${id2}'s scene, redrawn so none of these hold \u2014 every number, word and figure one the source states, every slot its verb needs filled. Keep its verb unless a finding names another. Return a scene: this ask has no way to leave it out. No other beat is yours to change.
27578
27636
 
27637
+ THE STORYBOARD:
27638
+ ${JSON.stringify(plan, null, 2)}`;
27639
+ }
27640
+ function isNeighbour(finding2) {
27641
+ return /^\S+ and \S+ are both `[^`]+`, next to each other\./.test(finding2);
27642
+ }
27643
+ function beatVariants(schema) {
27644
+ return schema.properties.beats.items.anyOf;
27645
+ }
27646
+ function beatSchemaFor(schema, shapes) {
27647
+ const anyOf = beatVariants(schema).filter((v) => shapes.includes(v.properties.archetype.const)).map((v) => hideFromPlanner(v, /* @__PURE__ */ new Set(["literal"])));
27648
+ return {
27649
+ type: "object",
27650
+ properties: { beat: { anyOf } },
27651
+ required: ["beat"],
27652
+ additionalProperties: false
27653
+ };
27654
+ }
27655
+ function beatPrompt(prompt, plan, beat2, shapes, problems) {
27656
+ const refused = problems.length ? `
27657
+ Your last answer was refused for:
27658
+ ${problems.map((m) => ` - ${m}`).join("\n")}
27659
+ ` : "";
27660
+ return `${prompt}
27661
+
27662
+ THE DECK IS PLANNED. ONE BEAT'S SHAPE IS LEFT.
27663
+
27664
+ The storyboard below is settled, but beat ${beat2.id} is \`${beat2.archetype}\` beside a beat of the same shape, and no two beats in a row may share an archetype.
27665
+ ${refused}
27666
+ Return {"beat": ...}: beat ${beat2.id} redrawn as one of ${shapes.map((s) => `\`${s}\``).join(", ")} \u2014 whichever shows its point best \u2014 with the params that shape needs, every number, word and figure one the source states. Keep its id, its point, its evidence, its narration and its part. No other beat is yours to change.
27667
+
27579
27668
  THE STORYBOARD:
27580
27669
  ${JSON.stringify(plan, null, 2)}`;
27581
27670
  }
@@ -27673,7 +27762,9 @@ async function readPlan(outPath, source, prefs) {
27673
27762
  ${raw2.slice(0, 200)}`
27674
27763
  );
27675
27764
  }
27676
- const plan = stripNulls(parsed);
27765
+ return checkPlan(stripNulls(parsed), source, prefs);
27766
+ }
27767
+ function checkPlan(plan, source, prefs) {
27677
27768
  const result = storyboardSchema.safeParse(plan);
27678
27769
  if (!result.success) {
27679
27770
  const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
package/dist/mcp.js CHANGED
@@ -24489,15 +24489,26 @@ function isScene(beat, design2) {
24489
24489
  return pictured(p.backdrop);
24490
24490
  }
24491
24491
  }
24492
+ function shapesFor(storyboard, i, offered, images) {
24493
+ const beats = storyboard.beats;
24494
+ const others = beats.filter((_, k) => k !== i);
24495
+ const kinetic2 = others.filter((b) => b.archetype === "kinetic").length;
24496
+ const panels = others.filter((b) => PANEL_ARCHETYPES.has(b.archetype)).length;
24497
+ const panelCap = panelsAllowed(beats.length, images);
24498
+ return offered.filter(
24499
+ (a) => a !== beats[i - 1]?.archetype && a !== beats[i + 1]?.archetype && (a !== "title" || i === 0) && (a !== "kinetic" || kinetic2 < KINETIC_MAX) && (!PANEL_ARCHETYPES.has(a) || panels < panelCap)
24500
+ );
24501
+ }
24492
24502
  function varietyFindings(storyboard, images, design2) {
24493
24503
  const beats = storyboard.beats;
24494
24504
  const out = [];
24505
+ const avoid = (beat, other) => [beat.archetype, other?.archetype].filter((x, k, xs) => x !== void 0 && xs.indexOf(x) === k).map((x) => `\`${x}\``).join(" or ");
24495
24506
  for (let i = 1; i < beats.length; i++) {
24496
24507
  const a = beats[i - 1];
24497
24508
  const b = beats[i];
24498
24509
  if (a.archetype === b.archetype) {
24499
24510
  out.push(
24500
- `${a.id} and ${b.id} are both \`${a.archetype}\`, next to each other. Draw one of them as a different shape.`
24511
+ `${a.id} and ${b.id} are both \`${a.archetype}\`, next to each other. Redraw ${a.id} as anything but ${avoid(a, beats[i - 2])}, or ${b.id} as anything but ${avoid(b, beats[i + 1])}.`
24501
24512
  );
24502
24513
  }
24503
24514
  }
@@ -25828,7 +25839,8 @@ async function codexPlanner(source, opts = {}) {
25828
25839
  try {
25829
25840
  const schemaPath = join11(dir, "storyboard.schema.json");
25830
25841
  const outPath = join11(dir, "storyboard.json");
25831
- await writeFile8(schemaPath, JSON.stringify(schemaFor(prefs, source)));
25842
+ const schema2 = schemaFor(prefs, source);
25843
+ await writeFile8(schemaPath, JSON.stringify(schema2));
25832
25844
  const ask = async (prompt2) => {
25833
25845
  await rm5(outPath, { force: true });
25834
25846
  await (opts.run ?? runCodex)({
@@ -25972,6 +25984,46 @@ ${left.map((m) => ` ${m}`).join("\n")}`
25972
25984
  );
25973
25985
  return plan;
25974
25986
  };
25987
+ const beatSchemaPath = join11(dir, "beat.schema.json");
25988
+ const offered = beatVariants(schema2).map((v) => v.properties.archetype.const);
25989
+ const reshape2 = async (deck, i) => {
25990
+ const beat = deck.beats[i];
25991
+ const shapes = shapesFor(deck, i, offered, prefs.images);
25992
+ if (!shapes.length) return deck;
25993
+ await writeFile8(beatSchemaPath, JSON.stringify(beatSchemaFor(schema2, shapes)));
25994
+ let problems = [];
25995
+ for (let t2 = 1; t2 <= SCENE_REPAIRS; t2++) {
25996
+ opts.onReshape?.(beat.id, shapes);
25997
+ const out = join11(dir, `beat-${beat.id}-${t2}.json`);
25998
+ try {
25999
+ await (opts.run ?? runCodex)({
26000
+ prompt: beatPrompt(prompt, deck, beat, shapes, problems),
26001
+ schemaPath: beatSchemaPath,
26002
+ outPath: out,
26003
+ ...opts.model === void 0 ? {} : { model: opts.model },
26004
+ timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2
26005
+ });
26006
+ const got = stripNulls(JSON.parse(await readFile9(out, "utf8")))?.beat;
26007
+ const redrawn = {
26008
+ ...got,
26009
+ id: beat.id,
26010
+ ...beat.literal ? { literal: beat.literal } : {}
26011
+ };
26012
+ const beats = deck.beats.map((b, k) => k === i ? redrawn : b);
26013
+ return withStockLabels(checkPlan({ ...deck, beats }, source, prefs), source);
26014
+ } catch (err) {
26015
+ problems = err instanceof PlanRejected ? err.problems : [`the ask failed: ${err instanceof Error ? err.message : err}`];
26016
+ }
26017
+ }
26018
+ return deck;
26019
+ };
26020
+ const neighbours = async (planned) => {
26021
+ let plan = planned;
26022
+ for (let i = 1; i < plan.beats.length; i++)
26023
+ if (plan.beats[i]?.archetype === plan.beats[i - 1]?.archetype)
26024
+ plan = await reshape2(plan, i);
26025
+ return plan;
26026
+ };
25975
26027
  let answer = await attempt2(prompt);
25976
26028
  if (answer.plan && listOf(answer.broken).length === 0) return await scenes(answer.plan);
25977
26029
  const firstBeats = beatCount(answer.json);
@@ -25991,7 +26043,13 @@ ${left.map((m) => ` ${m}`).join("\n")}`
25991
26043
  advice: []
25992
26044
  };
25993
26045
  if (next.plan && listOf(still).length === 0) return await scenes(next.plan);
25994
- if (round6 === REPAIRS) throw unrepaired(next, still, round6);
26046
+ if (round6 === REPAIRS) {
26047
+ if (!next.plan || !listOf(still).every(isNeighbour)) throw unrepaired(next, still, round6);
26048
+ const plan = await neighbours(next.plan);
26049
+ const left = { ...findings(plan), advice: [] };
26050
+ if (listOf(left).length) throw unrepaired(next, left, round6);
26051
+ return await scenes(plan);
26052
+ }
25995
26053
  merging ||= still.size.length > 0;
25996
26054
  answer = { ...next, broken: still };
25997
26055
  }
@@ -26046,6 +26104,37 @@ ${list2}
26046
26104
 
26047
26105
  Return {"literal": ...}: beat ${id2}'s scene, redrawn so none of these hold \u2014 every number, word and figure one the source states, every slot its verb needs filled. Keep its verb unless a finding names another. Return a scene: this ask has no way to leave it out. No other beat is yours to change.
26048
26106
 
26107
+ THE STORYBOARD:
26108
+ ${JSON.stringify(plan, null, 2)}`;
26109
+ }
26110
+ function isNeighbour(finding) {
26111
+ return /^\S+ and \S+ are both `[^`]+`, next to each other\./.test(finding);
26112
+ }
26113
+ function beatVariants(schema2) {
26114
+ return schema2.properties.beats.items.anyOf;
26115
+ }
26116
+ function beatSchemaFor(schema2, shapes) {
26117
+ const anyOf = beatVariants(schema2).filter((v) => shapes.includes(v.properties.archetype.const)).map((v) => hideFromPlanner(v, /* @__PURE__ */ new Set(["literal"])));
26118
+ return {
26119
+ type: "object",
26120
+ properties: { beat: { anyOf } },
26121
+ required: ["beat"],
26122
+ additionalProperties: false
26123
+ };
26124
+ }
26125
+ function beatPrompt(prompt, plan, beat, shapes, problems) {
26126
+ const refused = problems.length ? `
26127
+ Your last answer was refused for:
26128
+ ${problems.map((m) => ` - ${m}`).join("\n")}
26129
+ ` : "";
26130
+ return `${prompt}
26131
+
26132
+ THE DECK IS PLANNED. ONE BEAT'S SHAPE IS LEFT.
26133
+
26134
+ The storyboard below is settled, but beat ${beat.id} is \`${beat.archetype}\` beside a beat of the same shape, and no two beats in a row may share an archetype.
26135
+ ${refused}
26136
+ Return {"beat": ...}: beat ${beat.id} redrawn as one of ${shapes.map((s) => `\`${s}\``).join(", ")} \u2014 whichever shows its point best \u2014 with the params that shape needs, every number, word and figure one the source states. Keep its id, its point, its evidence, its narration and its part. No other beat is yours to change.
26137
+
26049
26138
  THE STORYBOARD:
26050
26139
  ${JSON.stringify(plan, null, 2)}`;
26051
26140
  }
@@ -26143,7 +26232,9 @@ async function readPlan(outPath, source, prefs) {
26143
26232
  ${raw2.slice(0, 200)}`
26144
26233
  );
26145
26234
  }
26146
- const plan = stripNulls(parsed);
26235
+ return checkPlan(stripNulls(parsed), source, prefs);
26236
+ }
26237
+ function checkPlan(plan, source, prefs) {
26147
26238
  const result = storyboardSchema.safeParse(plan);
26148
26239
  if (!result.success) {
26149
26240
  const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
@@ -31,6 +31,8 @@ export interface CodexOptions {
31
31
  onScene?: (beat: string, broken: readonly string[]) => void;
32
32
  /** Told the beats whose scene was dropped (still broken after its own repairs, or broken only by a symbolic quantity), and why. */
33
33
  onDropScenes?: (beats: readonly string[], why: readonly string[]) => void;
34
+ /** Told a beat still beside its own shape after the last repair is redrawn on its own, as one of `shapes`. */
35
+ onReshape?: (beat: string, shapes: readonly string[]) => void;
34
36
  /** Each source figure's panel count as the build will cut it, when the caller read them. */
35
37
  panels?: Readonly<Record<string, number>>;
36
38
  }
@@ -12,7 +12,8 @@
12
12
  * So the rule is a minimum, not an invitation, and it is CHECKED: the prompt
13
13
  * (src/plan/prompt.ts) states these numbers by importing them from here, and
14
14
  * `codexPlanner` (src/plan/codex.ts) runs `varietyFindings` on what comes back,
15
- * asks once for a repair, and refuses a plan that still breaks it. One table,
15
+ * asks for up to three repairs, redraws a beat still beside its own shape on
16
+ * its own (`shapesFor`), and refuses a plan that still breaks it. One table,
16
17
  * read by both, so the sentence the model is given and the check it is held to
17
18
  * cannot drift apart.
18
19
  *
@@ -90,6 +91,16 @@ export declare function scenesRequired(beats: number, images: Prefs["images"]):
90
91
  * the cards-on-paper the share exists to refuse.
91
92
  */
92
93
  export declare function isScene(beat: Beat, design?: Design): boolean;
94
+ /**
95
+ * The shapes, of those `offered`, that beat `i` may be redrawn as: not either
96
+ * neighbour's, not a title past the first beat, and not one that tips the
97
+ * kinetic or panel cap. What the last-resort redraw of one beat
98
+ * (src/plan/codex.ts) offers in its schema, so the shape cannot be wrong.
99
+ *
100
+ * ponytail: the A/B/A/B and scene-share rules are not foreseen here; the plan
101
+ * is checked whole after the redraw, and refused if one of them broke.
102
+ */
103
+ export declare function shapesFor(storyboard: Storyboard, i: number, offered: readonly Archetype[], images: Prefs["images"]): Archetype[];
93
104
  /**
94
105
  * Every way `storyboard` breaks the variety rule, as sentences the planner can
95
106
  * act on. Empty is a pass.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jokerized/decksmith",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
4
4
  "description": "Turn a source document into an animated explanation deck.",
5
5
  "type": "module",
6
6
  "license": "MIT",