@mcptoolshop/style-dataset-lab 3.0.0 → 3.0.1

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 (88) hide show
  1. package/CHANGELOG.md +68 -15
  2. package/CODE_OF_CONDUCT.md +9 -0
  3. package/CONTRIBUTING.md +71 -0
  4. package/README.md +46 -1
  5. package/SECURITY.md +6 -5
  6. package/bin/sdlab.js +281 -62
  7. package/lib/adapters/diffusers-lora.js +7 -2
  8. package/lib/adapters/generic-image-caption.js +6 -1
  9. package/lib/args.js +200 -13
  10. package/lib/batch-compiler.js +7 -3
  11. package/lib/batch-runs.js +100 -31
  12. package/lib/batch-sheet-render.js +5 -2
  13. package/lib/brief-compiler.js +38 -11
  14. package/lib/card.js +11 -4
  15. package/lib/comfyui.js +100 -6
  16. package/lib/config.js +68 -3
  17. package/lib/deprecation.js +1 -1
  18. package/lib/eval-pack.js +94 -4
  19. package/lib/eval-runs.js +42 -3
  20. package/lib/export.js +144 -34
  21. package/lib/implementation-packs.js +15 -1
  22. package/lib/log.js +41 -4
  23. package/lib/paths.js +41 -3
  24. package/lib/refine-briefs.js +16 -9
  25. package/lib/reingest-selected.js +14 -3
  26. package/lib/reingest.js +15 -1
  27. package/lib/run-summary.js +3 -2
  28. package/lib/runtime-runs.js +59 -9
  29. package/lib/selections.js +54 -2
  30. package/lib/snapshot.js +100 -7
  31. package/lib/split.js +175 -17
  32. package/lib/training-manifests.js +17 -3
  33. package/lib/training-packages.js +28 -14
  34. package/package.json +8 -2
  35. package/scripts/batch-generate.js +16 -7
  36. package/scripts/batch-sheet.js +3 -3
  37. package/scripts/batch-show.js +3 -3
  38. package/scripts/brief-compile.js +8 -8
  39. package/scripts/brief-show.js +4 -3
  40. package/scripts/canon-bind.js +7 -9
  41. package/scripts/card.js +22 -25
  42. package/scripts/compare.js +38 -33
  43. package/scripts/critique-show.js +3 -3
  44. package/scripts/critique.js +3 -3
  45. package/scripts/curate.js +91 -46
  46. package/scripts/doctor.js +3 -5
  47. package/scripts/eligibility.js +17 -14
  48. package/scripts/eval-pack.js +13 -11
  49. package/scripts/eval-run.js +30 -29
  50. package/scripts/export.js +27 -31
  51. package/scripts/generate-controlnet.js +31 -17
  52. package/scripts/generate-identity.js +37 -20
  53. package/scripts/generate-ipadapter.js +22 -16
  54. package/scripts/generate.js +68 -13
  55. package/scripts/implementation-pack.js +19 -14
  56. package/scripts/init.js +115 -17
  57. package/scripts/painterly.js +8 -10
  58. package/scripts/refine.js +3 -3
  59. package/scripts/reingest-selected.js +3 -3
  60. package/scripts/reingest.js +33 -20
  61. package/scripts/run-generate.js +4 -4
  62. package/scripts/run-list.js +3 -3
  63. package/scripts/run-show.js +3 -3
  64. package/scripts/select.js +5 -4
  65. package/scripts/selection-show.js +3 -3
  66. package/scripts/snapshot.js +22 -24
  67. package/scripts/split.js +26 -31
  68. package/scripts/training-manifest.js +29 -24
  69. package/scripts/training-package.js +24 -20
  70. package/scripts/training-profile.js +13 -8
  71. package/scripts/workflow-list.js +2 -2
  72. package/scripts/workflow-show.js +4 -3
  73. package/templates/domains/architecture/inputs/prompts/example-wave.json +32 -0
  74. package/templates/domains/architecture/terminology.json +10 -8
  75. package/templates/domains/character-design/inputs/prompts/example-wave.json +32 -0
  76. package/templates/domains/character-design/project.json +4 -2
  77. package/templates/domains/creature-design/inputs/prompts/example-wave.json +32 -0
  78. package/templates/domains/creature-design/project.json +4 -2
  79. package/templates/domains/creature-design/terminology.json +1 -0
  80. package/templates/domains/game-art/inputs/prompts/example-wave.json +37 -0
  81. package/templates/domains/game-art/project.json +4 -2
  82. package/templates/domains/vehicle-mech/inputs/prompts/example-wave.json +37 -0
  83. package/templates/domains/vehicle-mech/project.json +4 -2
  84. package/scripts/bulk-curate-wave2-5.js +0 -138
  85. package/scripts/bulk-curate-waves11-18.js +0 -413
  86. package/scripts/curate-wave25.js +0 -209
  87. package/scripts/migrate-records.js +0 -141
  88. package/templates/inputs/prompts/example-wave.json +0 -27
@@ -1,209 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Curation for wave 25 (expanded universe).
4
- *
5
- * Based on visual review of all 60 candidates:
6
- * - REJECT25* subjects → rejected (anti-examples)
7
- * - Strong hits → approved with high scores
8
- * - Partial hits → approved with moderate scores + notes
9
- * - Misses → rejected with explanations of what went wrong
10
- *
11
- * Usage:
12
- * node scripts/curate-wave25.js
13
- * node scripts/curate-wave25.js --dry-run
14
- */
15
-
16
- import { readFileSync, writeFileSync, readdirSync, renameSync, existsSync, mkdirSync } from 'fs';
17
- import { join, basename } from 'path';
18
-
19
- const REPO_ROOT = new URL("..", import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1");
20
- const GAME = process.argv.find((a, i) => process.argv[i - 1] === '--game') || 'star-freight';
21
- const GAME_ROOT = join(REPO_ROOT, 'projects', GAME);
22
-
23
- const RECORDS_DIR = join(GAME_ROOT, 'records');
24
- const CANDIDATES_DIR = join(GAME_ROOT, 'outputs', 'candidates');
25
- const APPROVED_DIR = join(GAME_ROOT, 'outputs', 'approved');
26
- const REJECTED_DIR = join(GAME_ROOT, 'outputs', 'rejected');
27
- const DRY_RUN = process.argv.includes('--dry-run');
28
-
29
- for (const dir of [APPROVED_DIR, REJECTED_DIR]) {
30
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
31
- }
32
-
33
- // ─── Scoring profiles ──────────────────────────────────────────
34
-
35
- const STRONG_HIT = {
36
- silhouette_clarity: 0.85, palette_adherence: 0.80, material_fidelity: 0.80,
37
- faction_read: 0.75, wear_level: 0.85, style_consistency: 0.85,
38
- clothing_logic: 0.70, composition: 0.85
39
- };
40
-
41
- const PARTIAL_HIT = {
42
- silhouette_clarity: 0.70, palette_adherence: 0.70, material_fidelity: 0.70,
43
- faction_read: 0.50, wear_level: 0.75, style_consistency: 0.75,
44
- clothing_logic: 0.55, composition: 0.75
45
- };
46
-
47
- // ─── Subject classifications ──────────────────────────────────────────
48
-
49
- // Strong hits — approved with high scores
50
- const STRONG_HITS = new Set([
51
- 'keth_molt_before', 'keth_molt_emerging', 'keth_hivesong', 'keth_pheromone_library',
52
- 'veshan_forge_trial', 'veshan_forge_trial_judgment',
53
- 'orryn_sealed_mantle', 'orryn_crystal_inheritance',
54
- 'thresh_young',
55
- 'mire_at_work',
56
- 'vaelk_proxy_market',
57
- 'architect_jump_gate', 'architect_archive_node', 'architect_ruin_surface',
58
- 'drift_kelp_bed',
59
- 'translator_rig'
60
- ]);
61
-
62
- // Partial hits — approved with moderate scores
63
- const PARTIAL_HITS = new Set([
64
- 'veshan_battle_opera', 'veshan_scale_reader',
65
- 'orryn_drift_parliament', 'orryn_drift_parliament_failed',
66
- 'drift_kelp_ambush',
67
- 'vaelk_proxy_trade',
68
- 'station_climate_border',
69
- 'pidgin_trade_sign'
70
- ]);
71
-
72
- // Misses — rejected with explanations
73
- const MISS_EXPLANATIONS = {
74
- thresh_old_station: {
75
- explanation: "Thresh crystalline organism not visible — rendered as standard station corridor with human. Need to foreground the crystalline growth formation.",
76
- failures: ["species_not_rendered", "concept_miss"],
77
- scores: { silhouette_clarity: 0.6, palette_adherence: 0.5, material_fidelity: 0.6, faction_read: 0.1, wear_level: 0.7, style_consistency: 0.7, clothing_logic: 0.3, composition: 0.6 }
78
- },
79
- smuggler_cant_meeting: {
80
- explanation: "All figures rendered as human — no multi-species element. The cross-species communication concept requires at least 2 different species visible.",
81
- failures: ["species_not_rendered", "concept_miss"],
82
- scores: { silhouette_clarity: 0.7, palette_adherence: 0.6, material_fidelity: 0.7, faction_read: 0.1, wear_level: 0.7, style_consistency: 0.7, clothing_logic: 0.5, composition: 0.7 }
83
- },
84
- mire_reforming: {
85
- explanation: "Green-tinted corridor with organic matter but no clear colonial organism reforming. The aggregation process needs to be the focal point.",
86
- failures: ["concept_miss"],
87
- scores: { silhouette_clarity: 0.5, palette_adherence: 0.6, material_fidelity: 0.6, faction_read: 0.3, wear_level: 0.7, style_consistency: 0.7, clothing_logic: 0.3, composition: 0.6 }
88
- },
89
- mire_hull_breach: {
90
- explanation: "Rendered as military cockpit scene — no Mire organism flowing through hull breach. Complete concept miss.",
91
- failures: ["species_not_rendered", "concept_miss"],
92
- scores: { silhouette_clarity: 0.6, palette_adherence: 0.4, material_fidelity: 0.5, faction_read: 0.0, wear_level: 0.6, style_consistency: 0.5, clothing_logic: 0.3, composition: 0.6 }
93
- }
94
- };
95
-
96
- // Reject examples — always rejected
97
- const REJECT_EXPLANATIONS = {
98
- REJECT25_clean_alien: {
99
- explanation: "Clean sterile alien civilization — Star Trek utopia aesthetic. Violates every grit principle.",
100
- failures: ["too_clean", "generic_scifi"],
101
- scores: { silhouette_clarity: 0.7, palette_adherence: 0.1, material_fidelity: 0.2, faction_read: 0.0, wear_level: 0.0, style_consistency: 0.3, clothing_logic: 0.2, composition: 0.7 }
102
- },
103
- REJECT25_fantasy_alien: {
104
- explanation: "Fantasy wizard alien with robes and magic staff — high fantasy, not grounded sci-fi.",
105
- failures: ["wrong_genre", "costume_not_clothes"],
106
- scores: { silhouette_clarity: 0.6, palette_adherence: 0.1, material_fidelity: 0.2, faction_read: 0.0, wear_level: 0.1, style_consistency: 0.1, clothing_logic: 0.1, composition: 0.6 }
107
- }
108
- };
109
-
110
- // ─── Process ──────────────────────────────────────────
111
-
112
- const wave25Files = readdirSync(CANDIDATES_DIR).filter(f =>
113
- f.endsWith('.png') && readdirSync(RECORDS_DIR).some(r => {
114
- try {
115
- const rec = JSON.parse(readFileSync(join(RECORDS_DIR, r), 'utf-8'));
116
- return rec.file === f && rec.lane === 'wave25-expanded-universe';
117
- } catch { return false; }
118
- })
119
- );
120
-
121
- // Fallback: match by known subject IDs
122
- const ALL_SUBJECTS = [
123
- ...STRONG_HITS, ...PARTIAL_HITS,
124
- ...Object.keys(MISS_EXPLANATIONS),
125
- ...Object.keys(REJECT_EXPLANATIONS)
126
- ];
127
-
128
- // Find wave25 records directly by filename match
129
- const recordFiles = readdirSync(RECORDS_DIR).filter(r => r.endsWith('.json'));
130
- const candidates = recordFiles.filter(rf => {
131
- return ALL_SUBJECTS.some(subj => rf.startsWith(subj));
132
- });
133
-
134
- let approved = 0, rejected = 0, skipped = 0;
135
-
136
- for (const recordFile of candidates) {
137
- let record;
138
- try {
139
- record = JSON.parse(readFileSync(join(RECORDS_DIR, recordFile), 'utf-8'));
140
- } catch { continue; }
141
-
142
- const file = basename(record.asset_path || '');
143
- const subjectId = record.id?.replace(/_v[12]$/, '') || file.replace(/_v[12]\.png$/, '');
144
-
145
- // Already curated?
146
- if (record.verdict) {
147
- console.log(` ⏭ ${file} already curated (${record.verdict})`);
148
- skipped++;
149
- continue;
150
- }
151
-
152
- let verdict, scores, explanation, failures;
153
-
154
- if (REJECT_EXPLANATIONS[subjectId]) {
155
- const r = REJECT_EXPLANATIONS[subjectId];
156
- verdict = 'rejected';
157
- scores = r.scores;
158
- explanation = r.explanation;
159
- failures = r.failures;
160
- } else if (MISS_EXPLANATIONS[subjectId]) {
161
- const m = MISS_EXPLANATIONS[subjectId];
162
- verdict = 'rejected';
163
- scores = m.scores;
164
- explanation = m.explanation;
165
- failures = m.failures;
166
- } else if (STRONG_HITS.has(subjectId)) {
167
- verdict = 'approved';
168
- scores = { ...STRONG_HIT };
169
- explanation = `Strong hit — concept reads clearly, style consistent, gritty lived-in aesthetic.`;
170
- failures = [];
171
- } else if (PARTIAL_HITS.has(subjectId)) {
172
- verdict = 'approved';
173
- scores = { ...PARTIAL_HIT };
174
- explanation = `Partial hit — atmosphere and style correct, some species anatomy or concept details missed.`;
175
- failures = ['partial_species_accuracy'];
176
- } else {
177
- console.log(` ⚠ Unknown subject ${subjectId} for ${file}, skipping`);
178
- skipped++;
179
- continue;
180
- }
181
-
182
- // Update record
183
- record.verdict = verdict;
184
- record.scores = scores;
185
- record.explanation = explanation;
186
- record.failures = failures;
187
- record.curated_at = new Date().toISOString();
188
- record.curator = 'wave25-script';
189
-
190
- // Move file
191
- const srcPath = join(CANDIDATES_DIR, file);
192
- const dstDir = verdict === 'approved' ? APPROVED_DIR : REJECTED_DIR;
193
- const dstPath = join(dstDir, file);
194
-
195
- if (!DRY_RUN) {
196
- writeFileSync(join(RECORDS_DIR, recordFile), JSON.stringify(record, null, 2));
197
- if (existsSync(srcPath)) {
198
- renameSync(srcPath, dstPath);
199
- }
200
- }
201
-
202
- const icon = verdict === 'approved' ? '✓' : '✗';
203
- console.log(` ${icon} ${file} → ${verdict}`);
204
- if (verdict === 'approved') approved++;
205
- else rejected++;
206
- }
207
-
208
- console.log(`\nWave 25 curation: ${approved} approved, ${rejected} rejected, ${skipped} skipped`);
209
- if (DRY_RUN) console.log(' (dry run — no files moved)');
@@ -1,141 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * migrate-records.js — One-time migration for curator-divergent records (Shape 2 + Shape 4)
4
- *
5
- * Normalizes verdict/scores/explanation/failures/curated_at/curator at top level
6
- * into the standard judgment.{status,criteria_scores,explanation,failure_modes,reviewed_at,reviewer} shape.
7
- *
8
- * Also fixes asset_path from outputs/candidates/ to outputs/approved/ when the image exists in approved/.
9
- *
10
- * DOES NOT TOUCH Shape 3 (identity packet) records — those have judgment=null by design.
11
- *
12
- * Usage:
13
- * node scripts/migrate-records.js --dry-run # Preview changes
14
- * node scripts/migrate-records.js # Apply changes
15
- * sdlab project migrate --dry-run --project star-freight
16
- */
17
-
18
- import { readdir, readFile, writeFile, access } from 'node:fs/promises';
19
- import { join, basename } from 'node:path';
20
- import { getProjectName } from '../lib/args.js';
21
- import { REPO_ROOT } from '../lib/paths.js';
22
-
23
- async function fileExists(p) {
24
- try { await access(p); return true; } catch { return false; }
25
- }
26
-
27
- function isCuratorDivergent(rec) {
28
- return rec.judgment === null && typeof rec.verdict === 'string' && !rec.identity;
29
- }
30
-
31
- function isIdentityRecord(rec) {
32
- return rec.identity != null;
33
- }
34
-
35
- async function migrateRecord(filePath, GAME_ROOT, DRY_RUN) {
36
- const raw = await readFile(filePath, 'utf-8');
37
- const rec = JSON.parse(raw);
38
-
39
- if (isIdentityRecord(rec)) return { file: basename(filePath), action: 'skip-identity' };
40
-
41
- if (!isCuratorDivergent(rec)) {
42
- if (rec.asset_path && rec.asset_path.includes('outputs/candidates/')) {
43
- const approvedPath = rec.asset_path.replace('outputs/candidates/', 'outputs/approved/');
44
- const fullApproved = join(GAME_ROOT, approvedPath);
45
- if (await fileExists(fullApproved)) {
46
- rec.asset_path = approvedPath;
47
- if (!DRY_RUN) await writeFile(filePath, JSON.stringify(rec, null, 2) + '\n');
48
- return { file: basename(filePath), action: 'path-fix-only', from: 'candidates/', to: 'approved/' };
49
- }
50
- }
51
- return { file: basename(filePath), action: 'skip-standard' };
52
- }
53
-
54
- const changes = [];
55
-
56
- rec.judgment = {
57
- status: rec.verdict || 'unknown',
58
- reviewer: rec.curator || 'wave25-script',
59
- reviewed_at: rec.curated_at || new Date().toISOString(),
60
- explanation: rec.explanation || '',
61
- criteria_scores: rec.scores || {},
62
- failure_modes: rec.failures || [],
63
- improvement_notes: null,
64
- confidence: 0.8,
65
- };
66
- changes.push(`verdict="${rec.verdict}" → judgment.status`);
67
-
68
- delete rec.verdict;
69
- delete rec.scores;
70
- delete rec.explanation;
71
- delete rec.failures;
72
- delete rec.curated_at;
73
- delete rec.curator;
74
- changes.push('removed top-level verdict/scores/explanation/failures/curated_at/curator');
75
-
76
- if (rec.asset_path && rec.asset_path.includes('outputs/candidates/')) {
77
- const approvedPath = rec.asset_path.replace('outputs/candidates/', 'outputs/approved/');
78
- const fullApproved = join(GAME_ROOT, approvedPath);
79
- if (await fileExists(fullApproved)) {
80
- rec.asset_path = approvedPath;
81
- changes.push(`asset_path: candidates/ → approved/`);
82
- } else {
83
- changes.push(`asset_path: candidates/ (image not in approved/, kept as-is)`);
84
- }
85
- }
86
-
87
- if (!DRY_RUN) {
88
- await writeFile(filePath, JSON.stringify(rec, null, 2) + '\n');
89
- }
90
-
91
- return { file: basename(filePath), action: 'migrated', changes };
92
- }
93
-
94
- export async function run(argv = process.argv.slice(2)) {
95
- const projectName = getProjectName(argv);
96
- const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
97
- const RECORDS_DIR = join(GAME_ROOT, 'records');
98
- const DRY_RUN = argv.includes('--dry-run');
99
-
100
- console.log(DRY_RUN ? '=== DRY RUN ===' : '=== APPLYING MIGRATION ===');
101
-
102
- const files = (await readdir(RECORDS_DIR)).filter(f => f.endsWith('.json'));
103
- console.log(`Found ${files.length} record files\n`);
104
-
105
- const results = { skipped: 0, migrated: 0, pathFixed: 0, identity: 0, errors: 0 };
106
-
107
- for (const file of files) {
108
- try {
109
- const result = await migrateRecord(join(RECORDS_DIR, file), GAME_ROOT, DRY_RUN);
110
- switch (result.action) {
111
- case 'skip-standard': results.skipped++; break;
112
- case 'skip-identity': results.identity++; break;
113
- case 'migrated':
114
- results.migrated++;
115
- console.log(`MIGRATE: ${result.file}`);
116
- result.changes.forEach(c => console.log(` → ${c}`));
117
- break;
118
- case 'path-fix-only':
119
- results.pathFixed++;
120
- console.log(`PATH-FIX: ${result.file} (${result.from} → ${result.to})`);
121
- break;
122
- }
123
- } catch (err) {
124
- results.errors++;
125
- console.error(`ERROR: ${file}: ${err.message}`);
126
- }
127
- }
128
-
129
- console.log(`\n=== Summary ===`);
130
- console.log(`Standard (unchanged): ${results.skipped}`);
131
- console.log(`Identity (untouched): ${results.identity}`);
132
- console.log(`Migrated (schema fix): ${results.migrated}`);
133
- console.log(`Path-fixed only: ${results.pathFixed}`);
134
- console.log(`Errors: ${results.errors}`);
135
- console.log(`Total: ${files.length}`);
136
- }
137
-
138
- // Direct execution guard
139
- if (process.argv[1] && (process.argv[1].endsWith('migrate-records.js') || process.argv[1].endsWith('migrate-records'))) {
140
- run().catch(err => { console.error(err.message || err); process.exit(1); });
141
- }
@@ -1,27 +0,0 @@
1
- {
2
- "wave": "wave1",
3
- "description": "First generation wave — core subjects for your visual style",
4
- "defaults": {
5
- "checkpoint": "dreamshaperXL_v21TurboDPMSDE.safetensors",
6
- "lora": { "name": "classipeintxl_v21.safetensors", "weight": 1.0 },
7
- "width": 1024,
8
- "height": 1024,
9
- "steps": 8,
10
- "cfg": 2.0,
11
- "sampler": "dpmpp_sde",
12
- "scheduler": "karras",
13
- "negative": "photorealistic, photograph, 3d render, smooth CG, sharp photo detail"
14
- },
15
- "subjects": [
16
- {
17
- "id": "subject_a",
18
- "variations": 2,
19
- "prompt": "oil painting, visible brushstrokes, [DESCRIBE YOUR SUBJECT HERE], painterly concept art, canvas grain"
20
- },
21
- {
22
- "id": "subject_b",
23
- "variations": 2,
24
- "prompt": "oil painting, visible brushstrokes, [DESCRIBE YOUR SUBJECT HERE], painterly concept art, canvas grain"
25
- }
26
- ]
27
- }