@mcptoolshop/style-dataset-lab 3.3.0 → 3.4.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.
- package/CHANGELOG.md +33 -0
- package/README.es.md +35 -25
- package/README.fr.md +38 -28
- package/README.hi.md +36 -26
- package/README.it.md +37 -27
- package/README.ja.md +40 -30
- package/README.md +15 -5
- package/README.pt-BR.md +35 -25
- package/README.zh.md +33 -23
- package/bin/sdlab.js +51 -4
- package/lib/adapters/ai-toolkit.js +37 -5
- package/lib/adapters/comfyui-runner.js +79 -26
- package/lib/adapters/comfyui-workflows.js +10 -2
- package/lib/adapters/diffusers-lora.js +37 -5
- package/lib/adapters/generic-image-caption.js +43 -5
- package/lib/args.js +16 -1
- package/lib/batch-modes.js +29 -8
- package/lib/batch-runs.js +29 -1
- package/lib/batch-sheet-render.js +13 -3
- package/lib/brief-compiler.js +19 -0
- package/lib/candidate-sheet.js +327 -0
- package/lib/canon-build/build.js +146 -93
- package/lib/captions.js +57 -0
- package/lib/card.js +74 -2
- package/lib/comfyui.js +116 -18
- package/lib/config.js +116 -12
- package/lib/critique-engine.js +36 -2
- package/lib/eligibility.js +121 -9
- package/lib/eval-pack.js +14 -14
- package/lib/eval-runs.js +33 -0
- package/lib/export.js +122 -34
- package/lib/freeze-gate.js +13 -3
- package/lib/freeze-stamp.js +28 -3
- package/lib/generated-provenance.js +16 -1
- package/lib/implementation-packs.js +95 -17
- package/lib/ingest.js +396 -0
- package/lib/judgment-provenance.js +220 -0
- package/lib/measure.js +422 -0
- package/lib/paths.js +196 -7
- package/lib/reingest-selected.js +98 -16
- package/lib/run-manifest.js +102 -16
- package/lib/run-summary.js +12 -6
- package/lib/runtime-runs.js +45 -4
- package/lib/snapshot.js +114 -15
- package/lib/split.js +364 -48
- package/lib/training-manifests.js +114 -21
- package/lib/training-packages.js +147 -15
- package/lib/workflow-profiles.js +24 -3
- package/package.json +3 -2
- package/runtime/comfyui/txt2img-qwen.json +38 -0
- package/runtime/comfyui/txt2img-standard.json +21 -0
- package/scripts/_resume.js +62 -0
- package/scripts/_shared-args.js +44 -0
- package/scripts/brief-compile.js +2 -1
- package/scripts/brief-show.js +2 -1
- package/scripts/canon-bind.js +2 -2
- package/scripts/canon-build.js +27 -20
- package/scripts/canon-drift.js +16 -7
- package/scripts/canon-freeze-status.js +14 -6
- package/scripts/canon-freeze.js +27 -11
- package/scripts/canon-unfreeze.js +17 -9
- package/scripts/card.js +2 -2
- package/scripts/compare.js +2 -2
- package/scripts/critique.js +16 -1
- package/scripts/curate.js +2 -2
- package/scripts/doctor.js +181 -55
- package/scripts/eligibility.js +42 -2
- package/scripts/eval-pack.js +2 -2
- package/scripts/eval-run.js +2 -2
- package/scripts/export.js +2 -2
- package/scripts/generate-controlnet.js +45 -25
- package/scripts/generate-identity.js +53 -23
- package/scripts/generate-ipadapter.js +44 -23
- package/scripts/generate.js +48 -5
- package/scripts/implementation-pack.js +2 -2
- package/scripts/ingest.js +145 -0
- package/scripts/init.js +73 -13
- package/scripts/measure.js +170 -0
- package/scripts/measure_image.py +458 -0
- package/scripts/migrate-records.js +142 -0
- package/scripts/painterly-test.js +4 -3
- package/scripts/painterly.js +7 -3
- package/scripts/qwen_generate.py +138 -20
- package/scripts/reingest.js +2 -2
- package/scripts/sheet.js +142 -0
- package/scripts/snapshot.js +2 -2
- package/scripts/split.js +2 -2
- package/scripts/training-manifest.js +2 -2
- package/scripts/training-package.js +2 -2
- package/scripts/training-profile.js +2 -2
- package/scripts/workflow-list.js +2 -1
- package/scripts/workflow-show.js +2 -1
package/scripts/init.js
CHANGED
|
@@ -12,10 +12,16 @@
|
|
|
12
12
|
import { mkdir, readdir, readFile, writeFile, copyFile } from 'node:fs/promises';
|
|
13
13
|
import { join, basename } from 'node:path';
|
|
14
14
|
import { existsSync } from 'node:fs';
|
|
15
|
-
import { REPO_ROOT } from '../lib/paths.js';
|
|
15
|
+
import { REPO_ROOT, getPackageRoot } from '../lib/paths.js';
|
|
16
16
|
import { inputError, handleCliError } from '../lib/errors.js';
|
|
17
|
+
import { takeFlagValue } from '../lib/args.js';
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// Two DIFFERENT roots — see getPackageRoot() in lib/paths.js.
|
|
20
|
+
// `templates/` ships inside the npm tarball, so it is package-relative;
|
|
21
|
+
// `projects/` is the user's own work, so it is workspace-relative. In a repo
|
|
22
|
+
// checkout these coincide, which is why resolving both against REPO_ROOT
|
|
23
|
+
// passed every in-repo test while breaking `init` for every installed user.
|
|
24
|
+
const TEMPLATES_DIR = join(getPackageRoot(), 'templates');
|
|
19
25
|
const PROJECTS_DIR = join(REPO_ROOT, 'projects');
|
|
20
26
|
|
|
21
27
|
const PROJECT_DIRS = [
|
|
@@ -64,11 +70,15 @@ async function listDomains() {
|
|
|
64
70
|
export async function run(argv = process.argv.slice(2)) {
|
|
65
71
|
const projectName = argv.find(a => !a.startsWith('--'));
|
|
66
72
|
|
|
67
|
-
// Parse --domain
|
|
73
|
+
// Parse --domain. H4: previously a hand-rolled `argv.indexOf('--domain')`
|
|
74
|
+
// that only recognized the space-separated form — `sdlab init foo
|
|
75
|
+
// --domain=game-art` silently fell through to the 'generic' default
|
|
76
|
+
// because no argv token was EXACTLY "--domain". takeFlagValue() (already
|
|
77
|
+
// hardened for both `--flag value` and `--flag=value`) closes that gap.
|
|
68
78
|
let domain = 'generic';
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
71
|
-
domain =
|
|
79
|
+
const domainValue = takeFlagValue(argv, 'domain');
|
|
80
|
+
if (domainValue !== undefined) {
|
|
81
|
+
domain = domainValue;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
if (!projectName) {
|
|
@@ -133,6 +143,34 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
133
143
|
}
|
|
134
144
|
console.log(` \x1b[32m✓\x1b[0m Created ${PROJECT_DIRS.length} directories`);
|
|
135
145
|
|
|
146
|
+
// SDL-M6: the generate → curate → reingest loop fills inbox/generated/ and
|
|
147
|
+
// the outputs/ subtrees with PNGs (see lib/reingest-selected.js), but this
|
|
148
|
+
// repo's OWN .gitignore (which happens to cover those paths in dev) is not
|
|
149
|
+
// in the npm `files` allow-list and ships to nobody. Without a per-project
|
|
150
|
+
// .gitignore, every downstream `sdlab init` user stages regenerable blobs
|
|
151
|
+
// by construction — measured at 55 MB of untracked PNGs in this repo before
|
|
152
|
+
// this fix. Never clobber a .gitignore the user already created by hand.
|
|
153
|
+
const gitignorePath = join(projectDir, '.gitignore');
|
|
154
|
+
if (!existsSync(gitignorePath)) {
|
|
155
|
+
const gitignoreLines = [
|
|
156
|
+
'# Generated by `sdlab init` — regenerable blobs for this project.',
|
|
157
|
+
'# Records/config JSON are tracked; only pixels and derived artifacts are not.',
|
|
158
|
+
'',
|
|
159
|
+
'inbox/**/*.png',
|
|
160
|
+
'inbox/**/*.jpg',
|
|
161
|
+
'inbox/**/*.jpeg',
|
|
162
|
+
'inbox/**/*.webp',
|
|
163
|
+
'outputs/**',
|
|
164
|
+
'exports/',
|
|
165
|
+
'snapshots/',
|
|
166
|
+
'splits/',
|
|
167
|
+
'canon-build/*/',
|
|
168
|
+
'',
|
|
169
|
+
];
|
|
170
|
+
await writeFile(gitignorePath, gitignoreLines.join('\n'));
|
|
171
|
+
console.log(` \x1b[32m✓\x1b[0m .gitignore (regenerable outputs/inbox/exports/snapshots/splits)`);
|
|
172
|
+
}
|
|
173
|
+
|
|
136
174
|
// Copy config files from domain template or generate generic ones
|
|
137
175
|
const configFiles = ['project.json', 'constitution.json', 'lanes.json', 'rubric.json', 'terminology.json'];
|
|
138
176
|
|
|
@@ -331,21 +369,43 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
331
369
|
console.log('');
|
|
332
370
|
console.log(`\x1b[32m✓\x1b[0m Project "${projectName}" initialized`);
|
|
333
371
|
console.log('');
|
|
372
|
+
// Two DIFFERENT path bases are printed in this block, for two DIFFERENT
|
|
373
|
+
// audiences — do not collapse them back into one helper:
|
|
374
|
+
// - "Edit <file>" / "Full quick-start" lines are opened by the user in
|
|
375
|
+
// an editor/file-browser from wherever they ran `init` (the workspace
|
|
376
|
+
// root), so they need workspace-relative paths: projects/<name>/....
|
|
377
|
+
// - The `sdlab generate <pack>` line's argument is consumed by
|
|
378
|
+
// generate.js via resolveSafeProjectPath(GAME_ROOT, packPath) — GAME_ROOT
|
|
379
|
+
// IS projects/<name>, so that path must be PROJECT-relative
|
|
380
|
+
// (inputs/prompts/...), not workspace-relative. Printing the
|
|
381
|
+
// workspace-relative form here doubles the path
|
|
382
|
+
// (projects/<name>/projects/<name>/...) and generate.js 404s with
|
|
383
|
+
// INPUT_FILE_NOT_FOUND. Regression introduced in 27c42c6 while fixing
|
|
384
|
+
// the sibling "Edit" lines (which legitimately needed the workspace-
|
|
385
|
+
// relative switch) — see tests/cli-scripts/init-generate-path.test.js.
|
|
386
|
+
const rel = (...segments) => ['projects', projectName, ...segments].join('/');
|
|
387
|
+
const projectRel = (...segments) => segments.join('/');
|
|
388
|
+
|
|
334
389
|
console.log('Next steps:');
|
|
335
390
|
let step = 1;
|
|
336
391
|
if (wroteCanon['constitution.md']) {
|
|
337
|
-
console.log(` ${step++}. Edit ${
|
|
392
|
+
console.log(` ${step++}. Edit ${rel('canon', 'constitution.md')}`);
|
|
393
|
+
console.log(` Describe your visual rules in prose — this is the canon the pipeline binds to.`);
|
|
338
394
|
}
|
|
339
|
-
console.log(` ${step++}. Edit ${
|
|
395
|
+
console.log(` ${step++}. Edit ${rel('constitution.json')}`);
|
|
396
|
+
console.log(` Encode those rules as machine-checkable entries for canon-bind.`);
|
|
340
397
|
if (wroteExamplePack) {
|
|
341
|
-
console.log(` ${step++}.
|
|
398
|
+
console.log(` ${step++}. Generate your first candidates:`);
|
|
399
|
+
console.log(` sdlab generate ${projectRel('inputs', 'prompts', 'example-wave.json')} --project ${projectName}`);
|
|
342
400
|
} else {
|
|
343
|
-
console.log(` ${step++}.
|
|
344
|
-
console.log(` ${step++}.
|
|
401
|
+
console.log(` ${step++}. Add a prompt pack under ${rel('inputs', 'prompts')}/`);
|
|
402
|
+
console.log(` ${step++}. Generate your first candidates:`);
|
|
403
|
+
console.log(` sdlab generate <pack> --project ${projectName}`);
|
|
345
404
|
}
|
|
346
|
-
console.log(` ${step++}.
|
|
405
|
+
console.log(` ${step++}. Check the project is wired correctly:`);
|
|
406
|
+
console.log(` sdlab project doctor --project ${projectName}`);
|
|
347
407
|
console.log('');
|
|
348
|
-
console.log(`
|
|
408
|
+
console.log(`Full quick-start: ${rel('README.md')}`);
|
|
349
409
|
}
|
|
350
410
|
|
|
351
411
|
// Direct execution guard
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* measure.js — Attach deterministic pixel measurements to candidate records.
|
|
5
|
+
*
|
|
6
|
+
* Until now nothing in this tool ever opened an image. `lib/critique-engine.js`
|
|
7
|
+
* builds its entire drift report from brief and workflow TEXT — keyword
|
|
8
|
+
* matching against prompt strings — and stamps every candidate
|
|
9
|
+
* `overall_fit: 'usable'` without looking at a single pixel. Every question
|
|
10
|
+
* about what an image actually looks like was answered by eyeballing it.
|
|
11
|
+
*
|
|
12
|
+
* The instruments here are a port, not an invention. They come from the
|
|
13
|
+
* measurement scripts written for the salt-road project
|
|
14
|
+
* (`projects/salt-road/inputs/prompts/measure-palette.py`, `measure-texture.py`),
|
|
15
|
+
* which were built and validated against real production waves and carry
|
|
16
|
+
* their own written rationale for why simpler proxies were rejected.
|
|
17
|
+
*
|
|
18
|
+
* MEASUREMENT, NOT VERDICT. This command attaches numbers. It never sets
|
|
19
|
+
* `judgment`, never sets `overall_fit`, never approves or rejects. That line
|
|
20
|
+
* is deliberate and load-bearing: this repo deleted three scripts in its
|
|
21
|
+
* Stage A health pass for minting judgments nobody made, and 948 of
|
|
22
|
+
* star-freight's 1,182 records still carry that damage. Numbers inform a
|
|
23
|
+
* human; they do not replace one.
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* sdlab measure <dir-or-record-glob> --project <name>
|
|
27
|
+
* sdlab measure outputs/approved --project salt-road
|
|
28
|
+
* sdlab measure "styleset_p*" --project salt-road --anchors canon/anchors.json
|
|
29
|
+
* sdlab measure outputs/candidates --project salt-road --dry-run
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
33
|
+
import { getProjectRoot } from '../lib/paths.js';
|
|
34
|
+
import { handleCliError } from '../lib/errors.js';
|
|
35
|
+
import { warn, result } from '../lib/log.js';
|
|
36
|
+
import {
|
|
37
|
+
assertMeasurementPrereqs,
|
|
38
|
+
resolveMeasureTargets,
|
|
39
|
+
loadAnchors,
|
|
40
|
+
runPythonMeasurement,
|
|
41
|
+
applyMeasurements,
|
|
42
|
+
} from '../lib/measure.js';
|
|
43
|
+
|
|
44
|
+
const USAGE = `Usage: sdlab measure <dir-or-record-glob> --project <name> [--anchors <path>] [--dry-run] [--json]
|
|
45
|
+
|
|
46
|
+
Measure candidate images (palette conformance, texture character) and attach
|
|
47
|
+
the numbers to their records. Measurement only — never sets a judgment.
|
|
48
|
+
|
|
49
|
+
Positional:
|
|
50
|
+
dir-or-record-glob A directory of images, or a record-id glob
|
|
51
|
+
(quote globs so your shell does not expand them)
|
|
52
|
+
|
|
53
|
+
Flags:
|
|
54
|
+
--project <name> Project to operate on
|
|
55
|
+
--anchors <path> Palette anchor definitions, relative to the project
|
|
56
|
+
root. Without it, palette measures that need anchors
|
|
57
|
+
are skipped rather than guessed.
|
|
58
|
+
--dry-run Measure and report, write nothing
|
|
59
|
+
--json Emit the raw result as JSON
|
|
60
|
+
|
|
61
|
+
Requires Python 3.9+ with Pillow, numpy and scipy (see scripts/measure_image.py).
|
|
62
|
+
Set SDLAB_PYTHON to pin an explicit interpreter.`;
|
|
63
|
+
|
|
64
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
65
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
66
|
+
flags: {
|
|
67
|
+
project: { type: 'string' },
|
|
68
|
+
anchors: { type: 'string' },
|
|
69
|
+
'dry-run': { type: 'boolean' },
|
|
70
|
+
json: { type: 'boolean' },
|
|
71
|
+
help: { type: 'boolean' },
|
|
72
|
+
},
|
|
73
|
+
deprecated: { game: 'project' },
|
|
74
|
+
allowUnknown: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (flags.help) {
|
|
78
|
+
console.log(USAGE);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const projectName = flags.project || getProjectName(argv);
|
|
83
|
+
const projectRoot = getProjectRoot(projectName);
|
|
84
|
+
const dryRun = Boolean(flags['dry-run']);
|
|
85
|
+
|
|
86
|
+
// Fail on a missing interpreter or package BEFORE resolving targets, so an
|
|
87
|
+
// operator without the Python side gets one clear actionable error rather
|
|
88
|
+
// than a successful scan followed by a confusing failure.
|
|
89
|
+
const python = assertMeasurementPrereqs();
|
|
90
|
+
|
|
91
|
+
const { targets, skipped } = resolveMeasureTargets(projectRoot, positionals[0]);
|
|
92
|
+
|
|
93
|
+
console.log(`\x1b[1msdlab measure\x1b[0m — ${projectName}`);
|
|
94
|
+
console.log(` Target: ${positionals[0]}`);
|
|
95
|
+
console.log(` Images: ${targets.length}${dryRun ? ' (dry run — nothing will be written)' : ''}`);
|
|
96
|
+
|
|
97
|
+
const anchorSpec = loadAnchors(flags.anchors, projectRoot);
|
|
98
|
+
if (anchorSpec.source) {
|
|
99
|
+
console.log(` Anchors: ${anchorSpec.anchors.length} from ${flags.anchors}`);
|
|
100
|
+
} else {
|
|
101
|
+
// Say so plainly. A palette measure with no anchors is not a failed
|
|
102
|
+
// measurement, but it is a narrower one, and silently returning fewer
|
|
103
|
+
// fields than the operator expected is how a tool loses trust.
|
|
104
|
+
console.log(` Anchors: none — pass --anchors <path> for palette-conformance measures`);
|
|
105
|
+
}
|
|
106
|
+
console.log('');
|
|
107
|
+
|
|
108
|
+
if (skipped.length > 0) {
|
|
109
|
+
warn(`${skipped.length} target(s) skipped: ${skipped.slice(0, 3).map((s) => `${s.id} (${s.reason})`).join(', ')}${skipped.length > 3 ? ', …' : ''}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (targets.length === 0) {
|
|
113
|
+
console.log(' Nothing to measure.');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const pyResult = runPythonMeasurement(python, {
|
|
118
|
+
images: targets.map((t) => ({ id: t.id, path: t.imagePath })),
|
|
119
|
+
anchors: anchorSpec.anchors,
|
|
120
|
+
hue_tolerance_deg: anchorSpec.hue_tolerance_deg,
|
|
121
|
+
sat_min: anchorSpec.sat_min,
|
|
122
|
+
val_min: anchorSpec.val_min,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const { applied, failed } = await applyMeasurements(targets, pyResult, {
|
|
126
|
+
anchorsSource: anchorSpec.source,
|
|
127
|
+
dryRun,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (flags.json) {
|
|
131
|
+
result(JSON.stringify({ applied, failed, skipped }, null, 2));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log(` \x1b[32m✓\x1b[0m Measured: ${applied.length}`);
|
|
136
|
+
if (failed.length > 0) {
|
|
137
|
+
console.log(` \x1b[31m✗\x1b[0m Failed: ${failed.length}`);
|
|
138
|
+
for (const f of failed.slice(0, 5)) {
|
|
139
|
+
console.log(` ${f.id}: ${f.error}`);
|
|
140
|
+
}
|
|
141
|
+
if (failed.length > 5) console.log(` … and ${failed.length - 5} more`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (applied.length > 0) {
|
|
145
|
+
// Show one real measurement rather than only a count — a number the
|
|
146
|
+
// operator can sanity-check against the image in front of them is worth
|
|
147
|
+
// more than a success tally.
|
|
148
|
+
const sample = applied[0];
|
|
149
|
+
const keys = Object.keys(sample.measurements || {}).filter((k) => !k.startsWith('_'));
|
|
150
|
+
if (keys.length > 0) {
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log(` Sample (${sample.id}):`);
|
|
153
|
+
for (const k of keys.slice(0, 6)) {
|
|
154
|
+
const v = sample.measurements[k];
|
|
155
|
+
const shown = typeof v === 'number' ? v.toFixed(4) : JSON.stringify(v);
|
|
156
|
+
console.log(` ${k.padEnd(28)} ${shown}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (dryRun) {
|
|
162
|
+
console.log('');
|
|
163
|
+
console.log(' (dry run — no records were modified)');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Direct execution guard
|
|
168
|
+
if (process.argv[1] && (process.argv[1].endsWith('measure.js') || process.argv[1].endsWith('measure'))) {
|
|
169
|
+
run().catch(handleCliError);
|
|
170
|
+
}
|