From ff94647af2b92f39d00ea7b0a29c89567ec5ef7d Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Sun, 17 May 2026 05:11:10 -0700 Subject: [PATCH 1/2] fix(pipeline): register pipeline-volume-cover-concepts stage The per-season volume cover-concept LLM step shipped in d802eb18 added the prompt template but missed the stage-config entry and a migration for existing installs, so upgraded installs hit "Stage pipeline-volume-cover-concepts not found" when arc planner tries to generate covers. - Add the stage-config entry to data.sample/prompts/stage-config.json - Add migration 017 that seeds the template + config entry on upgrade --- .changelog/NEXT.md | 4 + data.sample/prompts/stage-config.json | 7 ++ .../017-volume-cover-concepts-stage.js | 84 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 scripts/migrations/017-volume-cover-concepts-stage.js diff --git a/.changelog/NEXT.md b/.changelog/NEXT.md index 25eac05ac..b284e7258 100644 --- a/.changelog/NEXT.md +++ b/.changelog/NEXT.md @@ -4,3 +4,7 @@ - **Universe canon now lives inside Universe Builder.** Characters, places, and objects are managed inline on the universe page — no separate canon page to navigate to. The old canon URL still works as a redirect, and the Series Pipeline link lands you on the same combined view. Pending edits to other universe fields are no longer lost when canon changes are saved. - **Locking a canon entry now also blocks new reference renders.** Locked characters/places/objects already prevented AI rewrites; they now prevent new reference and clean-plate image renders too, so a locked entry's identity stays frozen across both text and visuals. Disabled buttons explain the lock in their tooltips. + +## Fixed + +- **Volume cover-concept generation no longer 500s on upgraded installs.** The per-season "generate volume cover concepts" LLM step was shipped with its prompt template but missing from `stage-config.json`, so existing installs hit `Stage pipeline-volume-cover-concepts not found`. Added the config entry and a migration that seeds both the template and the config on next launch — fresh installs were already fine. diff --git a/data.sample/prompts/stage-config.json b/data.sample/prompts/stage-config.json index 9f4c0ffa9..23de2e690 100644 --- a/data.sample/prompts/stage-config.json +++ b/data.sample/prompts/stage-config.json @@ -249,6 +249,13 @@ "returnsJson": true, "variables": [] }, + "pipeline-volume-cover-concepts": { + "name": "Pipeline — Volume Cover Concepts", + "description": "Generate FRONT and BACK cover-art concepts for one volume (season) of a comic series. Returns a {coverConcept, backCoverConcept} JSON pair — pure scene descriptions for the image-gen renderer (no masthead, logo, or typography). Lightweight per-season call; seeds blank cover scripts only and never clobbers a user edit.", + "model": "default", + "returnsJson": true, + "variables": [] + }, "pipeline-comic-panel-image-prompt": { "name": "Pipeline — Comic Panel Image Prompt", "description": "Refine a single comic panel's description into a richer image-gen-ready prompt. Uses caption / dialogue / sfx context + neighboring-panel continuity to elaborate without re-imagining the scene.", diff --git a/scripts/migrations/017-volume-cover-concepts-stage.js b/scripts/migrations/017-volume-cover-concepts-stage.js new file mode 100644 index 000000000..5b4dc8e5c --- /dev/null +++ b/scripts/migrations/017-volume-cover-concepts-stage.js @@ -0,0 +1,84 @@ +/** + * Seed the `pipeline-volume-cover-concepts` stage into existing installs. + * + * Commit d802eb18 ("feat(pipeline): issue back covers + volume covers + + * trade-paperback PDF") shipped the per-season cover-concept LLM step + * (`arcPlanner.generateVolumeCoverConcepts` → `runStagedLLM('pipeline-volume-cover-concepts', …)`) + * and added `data.sample/prompts/stages/pipeline-volume-cover-concepts.md` + * but forgot two things existing installs need: + * + * 1. A `stage-config.json` entry — `setup-data.js` only merges + * `JSON_MERGE_TARGETS` on fresh setup, so existing installs that + * upgrade-and-restart never get the new entry and `prompts.getStage()` + * throws "Stage pipeline-volume-cover-concepts not found". + * 2. The `.md` template — `ensureSampleContent` copies *missing* prompt + * files on next run so a fresh install gets it, but an upgrade that + * skips re-running setup-data leaves it absent. + * + * This migration fixes both for existing installs. Models on the same + * idempotent pattern as `015-importer-stage-prompts.js`. + */ + +import { access, copyFile, mkdir, readFile, writeFile, constants } from 'fs/promises'; +import { dirname, join } from 'path'; + +const FILENAME = 'pipeline-volume-cover-concepts.md'; +const STAGE_KEY = 'pipeline-volume-cover-concepts'; + +export default { + async up({ rootDir }) { + const stagesDir = join(rootDir, 'data', 'prompts', 'stages'); + await mkdir(stagesDir, { recursive: true }); + + const dataPath = join(stagesDir, FILENAME); + const samplePath = join(rootDir, 'data.sample', 'prompts', 'stages', FILENAME); + + const exists = await access(dataPath, constants.F_OK).then(() => true, () => false); + if (exists) { + console.log(`📝 volume-cover-concepts prompt: already present`); + } else { + const sampleExists = await access(samplePath, constants.F_OK).then(() => true, () => false); + if (!sampleExists) { + console.warn(`⚠️ volume-cover-concepts: sample missing for ${FILENAME} — skipping copy`); + } else { + try { + await copyFile(samplePath, dataPath); + console.log(`✅ seeded ${FILENAME}`); + } catch (err) { + console.warn(`⚠️ volume-cover-concepts: copy failed for ${FILENAME}: ${err.message}`); + } + } + } + + const installedConfigPath = join(rootDir, 'data', 'prompts', 'stage-config.json'); + const sampleConfigPath = join(rootDir, 'data.sample', 'prompts', 'stage-config.json'); + const sampleConfigExists = await access(sampleConfigPath, constants.F_OK).then(() => true, () => false); + if (!sampleConfigExists) { + console.warn('⚠️ volume-cover-concepts: data.sample stage-config.json missing — cannot resolve entry; skipping config write'); + return; + } + try { + const sample = JSON.parse(await readFile(sampleConfigPath, 'utf8')); + const installedExists = await access(installedConfigPath, constants.F_OK).then(() => true, () => false); + const installed = installedExists + ? JSON.parse(await readFile(installedConfigPath, 'utf8')) + : { stages: {} }; + installed.stages = installed.stages || {}; + if (installed.stages[STAGE_KEY]) { + console.log(`📝 volume-cover-concepts stage-config: already present`); + return; + } + if (!sample?.stages?.[STAGE_KEY]) { + console.warn(`⚠️ volume-cover-concepts: sample stage-config missing ${STAGE_KEY} — skipping`); + return; + } + installed.stages[STAGE_KEY] = sample.stages[STAGE_KEY]; + await mkdir(dirname(installedConfigPath), { recursive: true }); + await writeFile(installedConfigPath, JSON.stringify(installed, null, 2) + '\n', 'utf8'); + const action = installedExists ? 'merged' : 'created'; + console.log(`📝 volume-cover-concepts stage-config (${action}): 1 added`); + } catch (err) { + console.warn(`⚠️ volume-cover-concepts: stage-config merge failed: ${err.message}`); + } + }, +}; From cc63bffbca35167221b1da5ad6d87e62077d9f7f Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Sun, 17 May 2026 05:17:02 -0700 Subject: [PATCH 2/2] address review: fix typo in migration 017 header comment --- scripts/migrations/017-volume-cover-concepts-stage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/migrations/017-volume-cover-concepts-stage.js b/scripts/migrations/017-volume-cover-concepts-stage.js index 5b4dc8e5c..7c4f174d7 100644 --- a/scripts/migrations/017-volume-cover-concepts-stage.js +++ b/scripts/migrations/017-volume-cover-concepts-stage.js @@ -15,7 +15,7 @@ * files on next run so a fresh install gets it, but an upgrade that * skips re-running setup-data leaves it absent. * - * This migration fixes both for existing installs. Models on the same + * This migration fixes both for existing installs. Modeled on the same * idempotent pattern as `015-importer-stage-prompts.js`. */