@mcptoolshop/style-dataset-lab 2.3.0 → 3.0.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 (51) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +36 -8
  3. package/bin/sdlab.js +129 -0
  4. package/lib/adapters/comfyui-runner.js +170 -0
  5. package/lib/adapters/comfyui-workflows.js +163 -0
  6. package/lib/batch-compiler.js +118 -0
  7. package/lib/batch-modes.js +111 -0
  8. package/lib/batch-runs.js +168 -0
  9. package/lib/batch-sheet-render.js +206 -0
  10. package/lib/brief-compiler.js +598 -0
  11. package/lib/brief-render.js +205 -0
  12. package/lib/critique-engine.js +413 -0
  13. package/lib/critique-render.js +171 -0
  14. package/lib/generated-provenance.js +87 -0
  15. package/lib/paths.js +31 -0
  16. package/lib/refine-briefs.js +329 -0
  17. package/lib/reingest-selected.js +173 -0
  18. package/lib/run-summary.js +131 -0
  19. package/lib/runtime-runs.js +168 -0
  20. package/lib/selections.js +323 -0
  21. package/lib/workflow-profiles.js +161 -0
  22. package/package.json +6 -3
  23. package/scripts/batch-generate.js +167 -0
  24. package/scripts/batch-sheet.js +46 -0
  25. package/scripts/batch-show.js +86 -0
  26. package/scripts/brief-compile.js +134 -0
  27. package/scripts/brief-show.js +52 -0
  28. package/scripts/critique-show.js +50 -0
  29. package/scripts/critique.js +70 -0
  30. package/scripts/doctor.js +169 -1
  31. package/scripts/init.js +22 -0
  32. package/scripts/refine.js +119 -0
  33. package/scripts/reingest-selected.js +78 -0
  34. package/scripts/reingest.js +7 -1
  35. package/scripts/run-generate.js +76 -0
  36. package/scripts/run-list.js +58 -0
  37. package/scripts/run-show.js +63 -0
  38. package/scripts/select.js +118 -0
  39. package/scripts/selection-show.js +88 -0
  40. package/scripts/workflow-list.js +61 -0
  41. package/scripts/workflow-show.js +96 -0
  42. package/templates/domains/architecture/workflows/profiles/environment-moodboard.json +62 -0
  43. package/templates/domains/architecture/workflows/profiles/interior-variant-pack.json +60 -0
  44. package/templates/domains/character-design/workflows/profiles/character-portrait-set.json +61 -0
  45. package/templates/domains/character-design/workflows/profiles/expression-sheet.json +59 -0
  46. package/templates/domains/creature-design/workflows/profiles/creature-portrait-set.json +61 -0
  47. package/templates/domains/creature-design/workflows/profiles/silhouette-exploration.json +58 -0
  48. package/templates/domains/game-art/workflows/profiles/character-portrait-set.json +62 -0
  49. package/templates/domains/game-art/workflows/profiles/environment-moodboard.json +61 -0
  50. package/templates/domains/vehicle-mech/workflows/profiles/variant-pack.json +60 -0
  51. package/templates/domains/vehicle-mech/workflows/profiles/vehicle-silhouette-pack.json +59 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,131 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.6.0] - 2026-07-01
6
+
7
+ ### Added
8
+
9
+ - **Batch production modes (Phase 4D)** — coordinated multi-slot generation with reviewable sheet output
10
+ - `sdlab batch generate --mode <id>` — execute a batch production mode (expression sheets, environment boards, etc.)
11
+ - `sdlab batch show [batch-id]` — list all batches or show batch details
12
+ - `sdlab batch sheet <batch-id>` — re-render batch sheet from saved manifest
13
+ - `--dry-run` flag prepares batch directory and briefs without submitting to ComfyUI
14
+ - **Batch modes** (`lib/batch-modes.js`) — load, validate, and list batch mode definitions from `workflows/batch-modes/`
15
+ - **Batch compiler** (`lib/batch-compiler.js`) — expand one batch mode into multiple slot briefs with controlled per-slot deltas
16
+ - **Batch runs** (`lib/batch-runs.js`) — batch directory creation (`batch_YYYY-MM-DD_NNN`), coordinated slot execution, manifest save/load
17
+ - **Batch sheet renderer** (`lib/batch-sheet-render.js`) — HTML review surfaces with CSS grid layout, dark theme, zero dependencies
18
+ - **4 starter batch modes** for Star Freight: expression-sheet, environment-board, silhouette-pack, continuity-variants
19
+ - **JSON schemas** for batch mode definitions and batch manifests (`schemas/`)
20
+ - Doctor validates batch mode definitions (batch_type, subject_mode, slot uniqueness, layout, workflow references)
21
+ - Init scaffolds `workflows/batch-modes/` and `batches/` directories
22
+ ## [3.0.0] - 2026-04-16
23
+
24
+ ### Added
25
+
26
+ - **Selection + Reintegration (Phase 4E)** — close the production loop from generation back to corpus
27
+ - `sdlab select --run <id> --approve <files>` — select approved outputs from a run
28
+ - `sdlab select --batch <id> --approve <slot:file,...>` — select approved outputs from a batch
29
+ - `sdlab selection show [selection-id]` — list selections or show details
30
+ - `sdlab reingest selected --selection <id>` — re-ingest selected outputs as candidate records
31
+ - `--reason` and `--tags` flags for selection context
32
+ - `--dry-run` and `--json` output support
33
+ - **Selection engine** (`lib/selections.js`) — create selection artifacts from runs or batches
34
+ - Selection ID generation (`selection_YYYY-MM-DD_NNN`)
35
+ - Run and batch selection with output validation
36
+ - Chosen files copied into `selections/<id>/chosen/`
37
+ - Manifest, summary.json, and summary.md written per selection
38
+ - **Generated provenance** (`lib/generated-provenance.js`) — build provenance blocks for re-ingested records
39
+ - Traces back through run → brief → workflow → seed → config fingerprint
40
+ - Normalizes run and batch source context into a single shape
41
+ - **Re-ingest from selections** (`lib/reingest-selected.js`) — create candidate records from selections
42
+ - Records carry `source: generated` provenance with full generation provenance block
43
+ - Images copied to `inbox/generated/`
44
+ - Records tagged: `generated`, `selected`, workflow ID, subject ID
45
+ - No auto-bypass around review — records are staged for normal curation
46
+ - Provenance log written as `provenance.jsonl`
47
+ - **JSON schemas** for selections and generated records (`schemas/`)
48
+ - Doctor validates new project directories (`selections/`, `inbox/generated/`)
49
+ - Init scaffolds `selections/` and `inbox/generated/` directories
50
+
51
+ ### Changed
52
+
53
+ - **README** rewritten to describe the full production loop: canon → dataset → training → brief → run → critique → batch → select → re-ingest
54
+ - **Landing page** updated: new headline, description, 4-tab preview (Start, Dataset, Produce, Close the loop), 7 feature cards including production workflow and selection
55
+ - **Handbook** updated: index shows production loop diagram, new [Production Loop](handbook/production-loop/) page with complete end-to-end walkthrough
56
+ - **Package metadata** updated: description, keywords (`generation-workflow`, `batch-production`, `reintegration`), version badge
57
+ - Version bumped to **v3.0.0** — the repo is now a full production workbench, not just a dataset preparation system
58
+ ## [2.5.0] - 2026-04-16
59
+
60
+ ### Added
61
+
62
+ - **Critique + Refine loop (Phase 4C)** — inspect run outputs and generate focused next-pass briefs
63
+ - `sdlab critique --run <id>` — generate structured critique of a completed run
64
+ - `sdlab critique show --run <id>` — display saved critique (text, JSON, markdown)
65
+ - `sdlab refine --run <id> --pick <file>` — generate refined next-pass brief from critique
66
+ - CLI override flags: `--preserve`, `--push`, `--suppress` for manual delta control
67
+ - **Critique engine** (`lib/critique-engine.js`) — brief-driven candidate analysis
68
+ - 10 critique dimensions activated by workflow canon focus and drift guards
69
+ - Per-candidate structured notes: strengths, drift issues, preserve/correct next pass
70
+ - Run-level recommendation: accept_one, refine_from_one, rerun_broader, discard_run
71
+ - Workflow-specific emphasis by output mode (portrait, expression, moodboard, etc.)
72
+ - **Refine briefs** (`lib/refine-briefs.js`) — delta-layered brief generation
73
+ - Preserve/push/suppress instructions derived from critique + CLI overrides
74
+ - Prompt refinement: original prompt preserved, delta instructions appended
75
+ - Negative prompt extension: suppress items added without replacing existing negatives
76
+ - Refined briefs point back to parent brief + source run + picked candidate
77
+ - **JSON schemas** for critique reports and refined briefs (`schemas/`)
78
+ - Critique and refine artifacts stored in run directory: `runs/<id>/critique.json`, `runs/<id>/refine/`
79
+
80
+ ## [2.4.0] - 2026-04-16
81
+
82
+ ### Added
83
+
84
+ - **ComfyUI runtime adapter (Phase 4B)** — execute compiled briefs through ComfyUI and capture run artifacts
85
+ - `sdlab run generate --brief <id>` — execute a brief through ComfyUI, capture outputs to runs/
86
+ - `sdlab run show <id>` — show run manifest and results
87
+ - `sdlab run list` — list all runs for a project
88
+ - `--dry-run` flag prepares run directory without submitting to ComfyUI
89
+ - `--seed <n>` override for reproducible generation
90
+ - **Run management** (`lib/runtime-runs.js`) — run ID generation (`run_YYYY-MM-DD_NNN`), seed plans (fixed/increment/random), run directory preparation, manifest save/load
91
+ - **ComfyUI workflow adapter** (`lib/adapters/comfyui-workflows.js`) — builds ComfyUI prompt graphs from compiled briefs, template resolution by output mode
92
+ - **ComfyUI runner** (`lib/adapters/comfyui-runner.js`) — orchestrates seed plan → graph build → submit → poll → download → manifest for each image
93
+ - **Run summaries** (`lib/run-summary.js`) — generates summary.json and summary.md in each run directory
94
+ - **Runtime templates** (`runtime/comfyui/txt2img-standard.json`) — declarative workflow template metadata
95
+ - Doctor validates runtime templates (Section 10)
96
+ - `runs/` added to project scaffold
97
+
98
+ ## [2.3.0] - 2026-04-15
99
+
100
+ ### Added
101
+
102
+ - **Production workbench spine (Phase 4A)** — workflow profiles and brief compiler
103
+ - `sdlab workflow list` — list workflow profiles for a project
104
+ - `sdlab workflow show <id>` — show workflow profile details
105
+ - `sdlab brief compile` — compile a deterministic generation brief from project truth
106
+ - `sdlab brief show <id>` — show compiled brief (text, JSON, or markdown)
107
+ - **Workflow profiles** — production recipes defining output shape, canon constraints, drift guards, and runtime defaults
108
+ - 6 output modes: portrait_set, expression_sheet, variant_pack, moodboard, silhouette_sheet, turnaround
109
+ - 12 canon focus categories: silhouette, material_language, palette, anatomy, costume_logic, era_logic, scale_logic, lighting, surface_wear, gesture, composition, faction_read
110
+ - Subject mode enforcement: required, optional, forbidden
111
+ - **Brief compiler** — merges 6 layers of project truth into a frozen generation contract:
112
+ - Project canon → lane config → subject identity → training asset hints → workflow profile → CLI overrides
113
+ - Structured prompt assembly (no freeform prompt soup)
114
+ - Deterministic output: same inputs produce identical briefs
115
+ - Config fingerprint (SHA-256) for change detection
116
+ - Outputs JSON + human-readable Markdown
117
+ - **3 starter workflows for Star Freight**: character-portrait-set, expression-sheet, environment-moodboard
118
+ - **10 domain template workflows** across 5 domains (game-art, character-design, creature-design, architecture, vehicle-mech)
119
+ - **New library modules:** `lib/workflow-profiles.js`, `lib/brief-compiler.js`, `lib/brief-render.js`
120
+ - **Doctor validates workflow profiles** — checks schema, lane references, subject mode, runtime defaults
121
+ - **Init scaffolds `workflows/profiles/` and `briefs/`** directories and copies domain workflow templates
122
+
123
+ ### Key properties
124
+
125
+ - Workflow profiles are NOT training profiles — they drive generation, not training
126
+ - Briefs are frozen contracts — the creative runtime boundary between planning and execution
127
+ - Subject constraints are hard when subject_mode = required (fail loudly if missing)
128
+ - Drift guards appear as explicit warnings, never hidden in metadata
129
+
5
130
  ## [2.2.0] - 2026-04-16
6
131
 
7
132
  ### Added
package/README.md CHANGED
@@ -11,11 +11,11 @@
11
11
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License"></a>
12
12
  </p>
13
13
 
14
- Write your visual rules. Generate art. Judge every image against those rules. Ship the results as versioned, auditable training data.
14
+ Write your visual rules. Generate art. Judge every image against those rules. Ship the results as versioned, auditable training data — then put trained models to work in real production workflows and feed the best outputs back into your corpus.
15
15
 
16
- Style Dataset Lab connects the thing you wrote down about your art style to the dataset you actually train from. You define a constitution — silhouette rules, palette constraints, material language, whatever matters for your project. The pipeline generates candidates, scores them against those rules, and packages the approved work into reproducible datasets where every record explains why it was included.
16
+ Style Dataset Lab connects the thing you wrote down about your art style to the dataset you actually train from, and then closes the loop all the way through production. You define a constitution — silhouette rules, palette constraints, material language, whatever matters for your project. The pipeline generates candidates, scores them against those rules, and packages the approved work into reproducible datasets where every record explains why it was included.
17
17
 
18
- The loop closes: train a model, generate new outputs, score them against the same canon, re-ingest what passes. The dataset grows and the rules hold.
18
+ Then the production workbench takes over: compile generation briefs from project truth, run them through ComfyUI, critique the outputs, batch-produce expression sheets and environment boards, select the best results, and re-ingest them as new candidates. The loop closes: produce, select, review, strengthen.
19
19
 
20
20
  ## The pipeline
21
21
 
@@ -35,18 +35,29 @@ sdlab snapshot create --project my-project
35
35
  sdlab split build
36
36
  sdlab export build
37
37
 
38
- # Build a training package and close the loop
38
+ # Build a training package
39
39
  sdlab training-manifest create --profile character-style-lora
40
40
  sdlab training-package build
41
- sdlab eval-run create && sdlab eval-run score <id> --outputs results.jsonl
42
- sdlab reingest generated --source ./outputs --manifest <id>
41
+
42
+ # Compile a production brief and run it
43
+ sdlab brief compile --workflow character-portrait-set --subject kael_maren
44
+ sdlab run generate --brief brief_2026-04-16_001
45
+
46
+ # Critique, refine, batch-produce
47
+ sdlab critique --run run_2026-04-16_001
48
+ sdlab refine --run run_2026-04-16_001 --pick 001.png
49
+ sdlab batch generate --mode expression-sheet --subject kael_maren
50
+
51
+ # Select the best outputs and bring them back
52
+ sdlab select --run run_2026-04-16_001 --approve 001.png,003.png
53
+ sdlab reingest selected --selection selection_2026-04-16_001
43
54
  ```
44
55
 
45
- That last command is the point. Generated outputs come back through the same review process as everything else. The loop closes.
56
+ That last command is the point. Selected outputs come back through the same review process as everything else. The corpus grows and the rules hold.
46
57
 
47
58
  ## What it produces
48
59
 
49
- Seven versioned, checksummed artifacts. Each links to its predecessors so you can trace any training record back to the rule that approved it.
60
+ Seven dataset artifacts and a full production workbench. Each artifact links to its predecessors so you can trace any training record back to the rule that approved it.
50
61
 
51
62
  | Artifact | What it is |
52
63
  |----------|-----------|
@@ -58,6 +69,17 @@ Seven versioned, checksummed artifacts. Each links to its predecessors so you ca
58
69
  | **Eval scorecard** | Per-task pass/fail from scoring generated outputs against eval packs. |
59
70
  | **Implementation pack** | Prompt examples, known failures, continuity tests, and re-ingest guidance. |
60
71
 
72
+ The production workbench adds:
73
+
74
+ | Surface | What it does |
75
+ |---------|-------------|
76
+ | **Compiled brief** | Deterministic generation instruction from workflow profile + project truth. |
77
+ | **Run** | Frozen execution artifact: brief + seeds + ComfyUI outputs + manifest. |
78
+ | **Critique** | Structured multi-dimension evaluation of run outputs against canon. |
79
+ | **Batch** | Coordinated multi-slot production (expression sheets, environment boards, silhouette packs). |
80
+ | **Selection** | Creative decision artifact: which outputs were chosen, why, and where they came from. |
81
+ | **Re-ingest** | Selected outputs return as candidate records with full generation provenance. |
82
+
61
83
  ## Why this exists
62
84
 
63
85
  Training data is the highest-leverage artifact in any visual AI pipeline. But most training data is a folder of images with no history, no judgment trail, and no connection to the style rules it was supposed to follow.
@@ -94,6 +116,12 @@ projects/my-project/
94
116
  splits/ Train/val/test partitions
95
117
  exports/ Versioned export packages
96
118
  training/ Profiles, manifests, packages, eval runs, implementations
119
+ workflows/ Workflow profiles + batch mode definitions
120
+ briefs/ Compiled generation briefs
121
+ runs/ Execution artifacts (brief + outputs + manifest)
122
+ batches/ Coordinated multi-slot productions
123
+ selections/ Chosen outputs with reasons and provenance
124
+ inbox/generated/ Re-ingested images awaiting review
97
125
  ```
98
126
 
99
127
  ## Trust properties
package/bin/sdlab.js CHANGED
@@ -43,6 +43,42 @@ const PROJECT_COMMANDS = {
43
43
  'migrate': '../scripts/migrate-records.js',
44
44
  };
45
45
 
46
+ // Two-word commands under "workflow" namespace
47
+ const WORKFLOW_COMMANDS = {
48
+ 'list': '../scripts/workflow-list.js',
49
+ 'show': '../scripts/workflow-show.js',
50
+ };
51
+
52
+ // Two-word commands under "brief" namespace
53
+ const BRIEF_COMMANDS = {
54
+ 'compile': '../scripts/brief-compile.js',
55
+ 'show': '../scripts/brief-show.js',
56
+ };
57
+
58
+ // Two-word commands under "run" namespace
59
+ const RUN_COMMANDS = {
60
+ 'generate': '../scripts/run-generate.js',
61
+ 'show': '../scripts/run-show.js',
62
+ 'list': '../scripts/run-list.js',
63
+ };
64
+
65
+ // Two-word commands under "critique" namespace
66
+ const CRITIQUE_COMMANDS = {
67
+ 'show': '../scripts/critique-show.js',
68
+ };
69
+
70
+ // Two-word commands under "batch" namespace
71
+ const BATCH_COMMANDS = {
72
+ 'generate': '../scripts/batch-generate.js',
73
+ 'show': '../scripts/batch-show.js',
74
+ 'sheet': '../scripts/batch-sheet.js',
75
+ };
76
+
77
+ // Two-word commands under "selection" namespace
78
+ const SELECTION_COMMANDS = {
79
+ 'show': '../scripts/selection-show.js',
80
+ };
81
+
46
82
  function printHelp() {
47
83
  console.log(`\x1b[1msdlab\x1b[0m — Style Dataset Lab CLI\n`);
48
84
  console.log('Usage: sdlab <command> [options]\n');
@@ -99,6 +135,25 @@ function printHelp() {
99
135
  console.log(' reingest generated --source <dir> Re-ingest generated outputs');
100
136
  console.log(' reingest audit Audit re-ingested record status');
101
137
  console.log('');
138
+ console.log('Production:');
139
+ console.log(' workflow list List workflow profiles');
140
+ console.log(' workflow show <id> Show workflow profile details');
141
+ console.log(' brief compile Compile generation brief from project truth');
142
+ console.log(' brief show <id> Show compiled brief');
143
+ console.log(' run generate --brief <id> Execute brief through ComfyUI');
144
+ console.log(' run show <id> Show run details');
145
+ console.log(' run list List all runs');
146
+ console.log(' critique --run <id> Critique a completed run');
147
+ console.log(' critique show --run <id> Show saved critique');
148
+ console.log(' refine --run <id> --pick <file> Generate refined next-pass brief');
149
+ console.log(' batch generate --mode <id> Execute a batch production mode');
150
+ console.log(' batch show [batch-id] List batches or show details');
151
+ console.log(' batch sheet <batch-id> Re-render batch sheet from manifest');
152
+ console.log(' select --run <id> --approve <files> Select approved outputs from a run');
153
+ console.log(' select --batch <id> --approve ... Select approved outputs from a batch');
154
+ console.log(' selection show [selection-id] List selections or show details');
155
+ console.log(' reingest selected --selection <id> Re-ingest selected outputs as records');
156
+ console.log('');
102
157
  console.log('Options:');
103
158
  console.log(' --project <name> Project to operate on (default: star-freight)');
104
159
  console.log(' --game <name> Deprecated alias for --project');
@@ -140,6 +195,80 @@ async function main() {
140
195
  return;
141
196
  }
142
197
 
198
+ // Handle "workflow <subcommand>" two-word form
199
+ if (command === 'workflow' && args[1] && WORKFLOW_COMMANDS[args[1]]) {
200
+ commandArgs = args.slice(2);
201
+ const modulePath = WORKFLOW_COMMANDS[args[1]];
202
+ const mod = await import(modulePath);
203
+ await mod.run(commandArgs);
204
+ return;
205
+ }
206
+
207
+ // Handle "brief <subcommand>" two-word form
208
+ if (command === 'brief' && args[1] && BRIEF_COMMANDS[args[1]]) {
209
+ commandArgs = args.slice(2);
210
+ const modulePath = BRIEF_COMMANDS[args[1]];
211
+ const mod = await import(modulePath);
212
+ await mod.run(commandArgs);
213
+ return;
214
+ }
215
+
216
+ // Handle "run <subcommand>" two-word form
217
+ if (command === 'run' && args[1] && RUN_COMMANDS[args[1]]) {
218
+ commandArgs = args.slice(2);
219
+ const modulePath = RUN_COMMANDS[args[1]];
220
+ const mod = await import(modulePath);
221
+ await mod.run(commandArgs);
222
+ return;
223
+ }
224
+
225
+ // Handle "critique" — bare or "critique show" two-word form
226
+ if (command === 'critique') {
227
+ if (args[1] && CRITIQUE_COMMANDS[args[1]]) {
228
+ commandArgs = args.slice(2);
229
+ const modulePath = CRITIQUE_COMMANDS[args[1]];
230
+ const mod = await import(modulePath);
231
+ await mod.run(commandArgs);
232
+ return;
233
+ }
234
+ // Bare "critique" → generate critique
235
+ const mod = await import('../scripts/critique.js');
236
+ await mod.run(commandArgs);
237
+ return;
238
+ }
239
+
240
+ // Handle "refine" — single command
241
+ if (command === 'refine') {
242
+ const mod = await import('../scripts/refine.js');
243
+ await mod.run(commandArgs);
244
+ return;
245
+ }
246
+
247
+ // Handle "batch <subcommand>" two-word form
248
+ if (command === 'batch' && args[1] && BATCH_COMMANDS[args[1]]) {
249
+ commandArgs = args.slice(2);
250
+ const modulePath = BATCH_COMMANDS[args[1]];
251
+ const mod = await import(modulePath);
252
+ await mod.run(commandArgs);
253
+ return;
254
+ }
255
+
256
+ // Handle "select" — single command
257
+ if (command === 'select') {
258
+ const mod = await import('../scripts/select.js');
259
+ await mod.run(commandArgs);
260
+ return;
261
+ }
262
+
263
+ // Handle "selection <subcommand>" two-word form
264
+ if (command === 'selection' && args[1] && SELECTION_COMMANDS[args[1]]) {
265
+ commandArgs = args.slice(2);
266
+ const modulePath = SELECTION_COMMANDS[args[1]];
267
+ const mod = await import(modulePath);
268
+ await mod.run(commandArgs);
269
+ return;
270
+ }
271
+
143
272
  const modulePath = COMMANDS[command];
144
273
  if (!modulePath) {
145
274
  throw inputError('INPUT_UNKNOWN_COMMAND', `Unknown command: ${command}`, 'Run "sdlab --help" for available commands.');
@@ -0,0 +1,170 @@
1
+ /**
2
+ * ComfyUI runner — executes a brief through ComfyUI and captures outputs.
3
+ *
4
+ * Orchestrates: seed plan → graph build → submit → poll → download → manifest.
5
+ * Each image in the seed plan becomes one submission to ComfyUI.
6
+ */
7
+
8
+ import { writeFile, mkdir } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+ import { comfyHealth, submitAndWait, downloadImage } from '../comfyui.js';
11
+ import { buildWorkflowGraph, resolveTemplateId } from './comfyui-workflows.js';
12
+ import { buildSeedPlan, prepareRunDir, saveRunManifest } from '../runtime-runs.js';
13
+ import { loadProjectConfig } from '../config.js';
14
+ import { log, info, warn } from '../log.js';
15
+ import { runtimeError } from '../errors.js';
16
+
17
+ /**
18
+ * Execute a brief: build graphs, submit to ComfyUI, capture outputs.
19
+ *
20
+ * @param {Object} opts
21
+ * @param {string} opts.projectRoot — absolute project path
22
+ * @param {string} opts.projectName — project name
23
+ * @param {Object} opts.brief — compiled brief object
24
+ * @param {number} [opts.baseSeed] — override base seed
25
+ * @param {boolean} [opts.dryRun] — if true, prepare run dir but do not submit
26
+ * @returns {Promise<Object>} — manifest
27
+ */
28
+ export async function executeRun({ projectRoot, projectName, brief, baseSeed, dryRun = false }) {
29
+ const config = loadProjectConfig(projectRoot);
30
+ const defaults = config.meta?.defaults ?? {};
31
+ const comfyUrl = defaults.comfy_url || process.env.COMFY_URL || 'http://127.0.0.1:8188';
32
+
33
+ // Resolve template
34
+ const outputMode = brief.expected_outputs?.output_mode || 'portrait_set';
35
+ const templateId = resolveTemplateId(outputMode);
36
+ const outputCount = brief.expected_outputs?.output_count || brief.runtime_plan?.output_count || 4;
37
+
38
+ // Build seed plan
39
+ const seedMode = brief.runtime_plan?.seed_mode || 'increment';
40
+ const seedPlan = buildSeedPlan({ seedMode, outputCount, baseSeed });
41
+
42
+ // Prepare run directory
43
+ const { runId, runDir } = await prepareRunDir(projectRoot, brief);
44
+
45
+ info(`Run ${runId} — ${outputCount} images, seed mode: ${seedMode}, base: ${seedPlan.base_seed}`);
46
+ info(`Template: ${templateId}, ComfyUI: ${comfyUrl}`);
47
+
48
+ // Health check
49
+ if (!dryRun) {
50
+ const healthy = await comfyHealth(comfyUrl);
51
+ if (!healthy) {
52
+ throw runtimeError(
53
+ 'COMFYUI_OFFLINE',
54
+ `ComfyUI not reachable at ${comfyUrl}`,
55
+ 'Start ComfyUI or set COMFY_URL / project defaults.comfy_url'
56
+ );
57
+ }
58
+ info('ComfyUI online');
59
+ }
60
+
61
+ const outputs = [];
62
+ const errors = [];
63
+
64
+ for (let i = 0; i < outputCount; i++) {
65
+ const seed = seedPlan.seeds[i];
66
+ const imageIndex = String(i + 1).padStart(3, '0');
67
+
68
+ log(` [${i + 1}/${outputCount}] seed: ${seed}`);
69
+
70
+ if (dryRun) {
71
+ outputs.push({
72
+ index: i,
73
+ seed,
74
+ status: 'dry_run',
75
+ filename: `${imageIndex}.png`,
76
+ });
77
+ continue;
78
+ }
79
+
80
+ const startMs = Date.now();
81
+
82
+ try {
83
+ // Build graph
84
+ const { nodes } = buildWorkflowGraph({
85
+ brief,
86
+ projectDefaults: defaults,
87
+ seed,
88
+ });
89
+
90
+ // Submit and wait
91
+ const result = await submitAndWait(nodes, comfyUrl, {
92
+ clientPrefix: `sdl-run-${runId}`,
93
+ });
94
+
95
+ const elapsed = Date.now() - startMs;
96
+
97
+ // Extract output image
98
+ const nodeOutputs = result.outputs || {};
99
+ let imageFile = null;
100
+ let imageSubfolder = '';
101
+ for (const nodeOut of Object.values(nodeOutputs)) {
102
+ if (nodeOut.images && nodeOut.images.length > 0) {
103
+ imageFile = nodeOut.images[0].filename;
104
+ imageSubfolder = nodeOut.images[0].subfolder || '';
105
+ break;
106
+ }
107
+ }
108
+
109
+ if (!imageFile) {
110
+ warn(` No output image for seed ${seed}`);
111
+ outputs.push({ index: i, seed, status: 'no_output', elapsed_ms: elapsed });
112
+ continue;
113
+ }
114
+
115
+ // Download and save
116
+ const imgData = await downloadImage(imageFile, imageSubfolder, comfyUrl);
117
+ const destFilename = `${imageIndex}.png`;
118
+ await writeFile(join(runDir, 'outputs', destFilename), imgData);
119
+
120
+ outputs.push({
121
+ index: i,
122
+ seed,
123
+ status: 'ok',
124
+ filename: destFilename,
125
+ bytes: imgData.length,
126
+ elapsed_ms: elapsed,
127
+ comfy_filename: imageFile,
128
+ });
129
+
130
+ log(` ✓ ${destFilename} (${imgData.length} bytes, ${(elapsed / 1000).toFixed(1)}s)`);
131
+ } catch (err) {
132
+ const elapsed = Date.now() - startMs;
133
+ warn(` ✗ seed ${seed}: ${err.message}`);
134
+ errors.push({ index: i, seed, error: err.message });
135
+ outputs.push({
136
+ index: i,
137
+ seed,
138
+ status: 'error',
139
+ error: err.message,
140
+ elapsed_ms: elapsed,
141
+ });
142
+ }
143
+ }
144
+
145
+ // Build manifest
146
+ const manifest = {
147
+ run_id: runId,
148
+ brief_id: brief.brief_id,
149
+ project_id: brief.project_id,
150
+ workflow_template_id: templateId,
151
+ adapter_target: 'comfyui',
152
+ output_mode: outputMode,
153
+ output_count: outputCount,
154
+ created_at: new Date().toISOString(),
155
+ seed_plan: seedPlan,
156
+ comfy_url: comfyUrl,
157
+ checkpoint: defaults.checkpoint,
158
+ loras: defaults.loras || [],
159
+ runtime_plan: brief.runtime_plan,
160
+ outputs,
161
+ errors,
162
+ dry_run: dryRun,
163
+ success_count: outputs.filter(o => o.status === 'ok' || o.status === 'dry_run').length,
164
+ error_count: errors.length,
165
+ };
166
+
167
+ await saveRunManifest(runDir, manifest);
168
+
169
+ return manifest;
170
+ }