@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.
- package/CHANGELOG.md +68 -15
- package/CODE_OF_CONDUCT.md +9 -0
- package/CONTRIBUTING.md +71 -0
- package/README.md +46 -1
- package/SECURITY.md +6 -5
- package/bin/sdlab.js +281 -62
- package/lib/adapters/diffusers-lora.js +7 -2
- package/lib/adapters/generic-image-caption.js +6 -1
- package/lib/args.js +200 -13
- package/lib/batch-compiler.js +7 -3
- package/lib/batch-runs.js +100 -31
- package/lib/batch-sheet-render.js +5 -2
- package/lib/brief-compiler.js +38 -11
- package/lib/card.js +11 -4
- package/lib/comfyui.js +100 -6
- package/lib/config.js +68 -3
- package/lib/deprecation.js +1 -1
- package/lib/eval-pack.js +94 -4
- package/lib/eval-runs.js +42 -3
- package/lib/export.js +144 -34
- package/lib/implementation-packs.js +15 -1
- package/lib/log.js +41 -4
- package/lib/paths.js +41 -3
- package/lib/refine-briefs.js +16 -9
- package/lib/reingest-selected.js +14 -3
- package/lib/reingest.js +15 -1
- package/lib/run-summary.js +3 -2
- package/lib/runtime-runs.js +59 -9
- package/lib/selections.js +54 -2
- package/lib/snapshot.js +100 -7
- package/lib/split.js +175 -17
- package/lib/training-manifests.js +17 -3
- package/lib/training-packages.js +28 -14
- package/package.json +8 -2
- package/scripts/batch-generate.js +16 -7
- package/scripts/batch-sheet.js +3 -3
- package/scripts/batch-show.js +3 -3
- package/scripts/brief-compile.js +8 -8
- package/scripts/brief-show.js +4 -3
- package/scripts/canon-bind.js +7 -9
- package/scripts/card.js +22 -25
- package/scripts/compare.js +38 -33
- package/scripts/critique-show.js +3 -3
- package/scripts/critique.js +3 -3
- package/scripts/curate.js +91 -46
- package/scripts/doctor.js +3 -5
- package/scripts/eligibility.js +17 -14
- package/scripts/eval-pack.js +13 -11
- package/scripts/eval-run.js +30 -29
- package/scripts/export.js +27 -31
- package/scripts/generate-controlnet.js +31 -17
- package/scripts/generate-identity.js +37 -20
- package/scripts/generate-ipadapter.js +22 -16
- package/scripts/generate.js +68 -13
- package/scripts/implementation-pack.js +19 -14
- package/scripts/init.js +115 -17
- package/scripts/painterly.js +8 -10
- package/scripts/refine.js +3 -3
- package/scripts/reingest-selected.js +3 -3
- package/scripts/reingest.js +33 -20
- package/scripts/run-generate.js +4 -4
- package/scripts/run-list.js +3 -3
- package/scripts/run-show.js +3 -3
- package/scripts/select.js +5 -4
- package/scripts/selection-show.js +3 -3
- package/scripts/snapshot.js +22 -24
- package/scripts/split.js +26 -31
- package/scripts/training-manifest.js +29 -24
- package/scripts/training-package.js +24 -20
- package/scripts/training-profile.js +13 -8
- package/scripts/workflow-list.js +2 -2
- package/scripts/workflow-show.js +4 -3
- package/templates/domains/architecture/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/architecture/terminology.json +10 -8
- package/templates/domains/character-design/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/character-design/project.json +4 -2
- package/templates/domains/creature-design/inputs/prompts/example-wave.json +32 -0
- package/templates/domains/creature-design/project.json +4 -2
- package/templates/domains/creature-design/terminology.json +1 -0
- package/templates/domains/game-art/inputs/prompts/example-wave.json +37 -0
- package/templates/domains/game-art/project.json +4 -2
- package/templates/domains/vehicle-mech/inputs/prompts/example-wave.json +37 -0
- package/templates/domains/vehicle-mech/project.json +4 -2
- package/scripts/bulk-curate-wave2-5.js +0 -138
- package/scripts/bulk-curate-waves11-18.js +0 -413
- package/scripts/curate-wave25.js +0 -209
- package/scripts/migrate-records.js +0 -141
- package/templates/inputs/prompts/example-wave.json +0 -27
|
@@ -12,8 +12,9 @@ import { existsSync } from 'node:fs';
|
|
|
12
12
|
import { createHash, randomBytes } from 'node:crypto';
|
|
13
13
|
import { loadExport } from './export.js';
|
|
14
14
|
import { loadTrainingProfile } from './training-profiles.js';
|
|
15
|
-
import { computeConfigFingerprint } from './snapshot.js';
|
|
15
|
+
import { computeConfigFingerprint, SCHEMA_VERSION, checkManifestVersion } from './snapshot.js';
|
|
16
16
|
import { listEvalPacks } from './eval-pack.js';
|
|
17
|
+
import { inputError } from './errors.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Generate a training manifest ID: tm-YYYYMMDD-HHMMSS-XXXX
|
|
@@ -64,6 +65,8 @@ export async function createTrainingManifest(projectRoot, exportId, profileId, o
|
|
|
64
65
|
|
|
65
66
|
const manifestId = generateManifestId();
|
|
66
67
|
const manifest = {
|
|
68
|
+
// DB-001 / DB-006: stamp manifest schema version
|
|
69
|
+
schema_version: SCHEMA_VERSION,
|
|
67
70
|
training_manifest_id: manifestId,
|
|
68
71
|
created_at: new Date().toISOString(),
|
|
69
72
|
created_by: 'sdlab-training-manifest-v1',
|
|
@@ -89,7 +92,16 @@ export async function createTrainingManifest(projectRoot, exportId, profileId, o
|
|
|
89
92
|
// Write manifest
|
|
90
93
|
const dir = join(projectRoot, 'training', 'manifests');
|
|
91
94
|
await mkdir(dir, { recursive: true });
|
|
92
|
-
|
|
95
|
+
const manifestPath = join(dir, `${manifestId}.json`);
|
|
96
|
+
// D-005: refuse to silently overwrite on ID collision.
|
|
97
|
+
if (existsSync(manifestPath)) {
|
|
98
|
+
throw inputError(
|
|
99
|
+
'TRAINING_MANIFEST_ID_COLLISION',
|
|
100
|
+
`Training manifest file already exists: ${manifestPath}`,
|
|
101
|
+
'Retry to generate a fresh manifest ID.',
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
93
105
|
|
|
94
106
|
return { manifestId, exportId, profileId };
|
|
95
107
|
}
|
|
@@ -139,7 +151,9 @@ export async function loadTrainingManifest(projectRoot, manifestId) {
|
|
|
139
151
|
if (!existsSync(path)) {
|
|
140
152
|
throw new Error(`Training manifest "${manifestId}" not found at ${path}`);
|
|
141
153
|
}
|
|
142
|
-
|
|
154
|
+
const manifest = JSON.parse(await readFile(path, 'utf-8'));
|
|
155
|
+
checkManifestVersion(manifest, 'training-manifest');
|
|
156
|
+
return manifest;
|
|
143
157
|
}
|
|
144
158
|
|
|
145
159
|
/**
|
package/lib/training-packages.js
CHANGED
|
@@ -15,6 +15,8 @@ import { loadTrainingProfile } from './training-profiles.js';
|
|
|
15
15
|
import { loadSplitPartition } from './split.js';
|
|
16
16
|
import { loadRecord } from './records.js';
|
|
17
17
|
import { loadProjectConfig, detectLane, detectGroup } from './config.js';
|
|
18
|
+
import { inputError } from './errors.js';
|
|
19
|
+
import { SCHEMA_VERSION, checkManifestVersion } from './snapshot.js';
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Generate a training package ID: tp-YYYYMMDD-HHMMSS-XXXX
|
|
@@ -63,6 +65,14 @@ export async function buildTrainingPackage(projectRoot, manifestId, options = {}
|
|
|
63
65
|
const recordsDir = join(projectRoot, 'records');
|
|
64
66
|
const packageId = generatePackageId();
|
|
65
67
|
const packageDir = join(projectRoot, 'training', 'packages', packageId);
|
|
68
|
+
// D-005: refuse to silently overwrite on ID collision.
|
|
69
|
+
if (existsSync(packageDir)) {
|
|
70
|
+
throw inputError(
|
|
71
|
+
'TRAINING_PACKAGE_ID_COLLISION',
|
|
72
|
+
`Training package directory already exists: ${packageDir}`,
|
|
73
|
+
'Retry to generate a fresh package ID.',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
66
76
|
|
|
67
77
|
// Load split partitions
|
|
68
78
|
const partitions = {};
|
|
@@ -105,19 +115,10 @@ export async function buildTrainingPackage(projectRoot, manifestId, options = {}
|
|
|
105
115
|
|
|
106
116
|
totalImages = adapterResult.imageCount || 0;
|
|
107
117
|
|
|
108
|
-
//
|
|
109
|
-
const checksumEntries = [];
|
|
110
|
-
const allFiles = await collectFiles(packageDir);
|
|
111
|
-
for (const filePath of allFiles.sort()) {
|
|
112
|
-
const relPath = relative(packageDir, filePath);
|
|
113
|
-
if (relPath === 'checksums.txt') continue;
|
|
114
|
-
const hash = await sha256File(filePath);
|
|
115
|
-
checksumEntries.push(`SHA256 (${relPath}) = ${hash}`);
|
|
116
|
-
}
|
|
117
|
-
await writeFile(join(packageDir, 'checksums.txt'), checksumEntries.join('\n') + '\n');
|
|
118
|
-
|
|
119
|
-
// Write package manifest (last — references everything)
|
|
118
|
+
// D-015: Write manifest + README FIRST so they are covered by checksums.
|
|
120
119
|
const packageManifest = {
|
|
120
|
+
// DB-001 / DB-006: stamp manifest schema version
|
|
121
|
+
schema_version: SCHEMA_VERSION,
|
|
121
122
|
training_package_id: packageId,
|
|
122
123
|
created_at: new Date().toISOString(),
|
|
123
124
|
created_by: 'sdlab-training-package-v1',
|
|
@@ -134,7 +135,7 @@ export async function buildTrainingPackage(projectRoot, manifestId, options = {}
|
|
|
134
135
|
val: partitions.val.length,
|
|
135
136
|
test: partitions.test.length,
|
|
136
137
|
images: totalImages,
|
|
137
|
-
checksums:
|
|
138
|
+
checksums: 0, // updated after checksum pass below
|
|
138
139
|
},
|
|
139
140
|
};
|
|
140
141
|
await writeFile(join(packageDir, 'manifest.json'), JSON.stringify(packageManifest, null, 2) + '\n');
|
|
@@ -143,6 +144,17 @@ export async function buildTrainingPackage(projectRoot, manifestId, options = {}
|
|
|
143
144
|
const readme = generatePackageReadme(packageManifest, profile, manifest, adapterTarget);
|
|
144
145
|
await writeFile(join(packageDir, 'README.md'), readme);
|
|
145
146
|
|
|
147
|
+
// Generate checksums AFTER manifest+README are on disk so they are included.
|
|
148
|
+
const checksumEntries = [];
|
|
149
|
+
const allFiles = await collectFiles(packageDir);
|
|
150
|
+
for (const filePath of allFiles.sort()) {
|
|
151
|
+
const relPath = relative(packageDir, filePath);
|
|
152
|
+
if (relPath === 'checksums.txt') continue;
|
|
153
|
+
const hash = await sha256File(filePath);
|
|
154
|
+
checksumEntries.push(`SHA256 (${relPath}) = ${hash}`);
|
|
155
|
+
}
|
|
156
|
+
await writeFile(join(packageDir, 'checksums.txt'), checksumEntries.join('\n') + '\n');
|
|
157
|
+
|
|
146
158
|
return { packageId, records: totalRecords, images: totalImages };
|
|
147
159
|
}
|
|
148
160
|
|
|
@@ -237,5 +249,7 @@ export async function loadTrainingPackage(projectRoot, packageId) {
|
|
|
237
249
|
if (!existsSync(path)) {
|
|
238
250
|
throw new Error(`Training package "${packageId}" not found at ${path}`);
|
|
239
251
|
}
|
|
240
|
-
|
|
252
|
+
const manifest = JSON.parse(await readFile(path, 'utf-8'));
|
|
253
|
+
checkManifestVersion(manifest, 'training-package');
|
|
254
|
+
return manifest;
|
|
241
255
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcptoolshop/style-dataset-lab",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Canon-aligned dataset production and generation workbench — define visual rules, build versioned training data, compile production briefs, run local workflows, batch-produce, select winners, and re-ingest into your corpus",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"sdlab": "./bin/sdlab.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
+
"test": "node --test tests/**/*.test.js",
|
|
10
11
|
"verify": "node bin/sdlab.js --help && node bin/sdlab.js project doctor --project star-freight",
|
|
11
12
|
"generate": "node scripts/generate.js",
|
|
12
13
|
"curate": "node scripts/curate.js",
|
|
@@ -19,11 +20,16 @@
|
|
|
19
20
|
"bin/",
|
|
20
21
|
"lib/",
|
|
21
22
|
"scripts/",
|
|
23
|
+
"!scripts/bulk-curate-*.js",
|
|
24
|
+
"!scripts/curate-wave*.js",
|
|
25
|
+
"!scripts/migrate-records.js",
|
|
22
26
|
"templates/",
|
|
23
27
|
"README.md",
|
|
24
28
|
"LICENSE",
|
|
25
29
|
"SECURITY.md",
|
|
26
|
-
"CHANGELOG.md"
|
|
30
|
+
"CHANGELOG.md",
|
|
31
|
+
"CONTRIBUTING.md",
|
|
32
|
+
"CODE_OF_CONDUCT.md"
|
|
27
33
|
],
|
|
28
34
|
"keywords": [
|
|
29
35
|
"dataset",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* sdlab batch generate --mode silhouette-pack --project star-freight --dry-run
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { parseArgs } from '../lib/args.js';
|
|
12
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
13
13
|
import { getProjectRoot, getRunsDir } from '../lib/paths.js';
|
|
14
14
|
import { compileBatch } from '../lib/batch-compiler.js';
|
|
15
15
|
import { getBatchMode } from '../lib/batch-modes.js';
|
|
@@ -19,14 +19,14 @@ import {
|
|
|
19
19
|
saveBatchManifest,
|
|
20
20
|
} from '../lib/batch-runs.js';
|
|
21
21
|
import { renderSheetHTML, saveSheet } from '../lib/batch-sheet-render.js';
|
|
22
|
-
import { info } from '../lib/log.js';
|
|
22
|
+
import { info, result } from '../lib/log.js';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
24
|
import { writeFile } from 'node:fs/promises';
|
|
25
25
|
|
|
26
26
|
export async function run(argv = process.argv.slice(2)) {
|
|
27
27
|
const { flags } = parseArgs(argv, {
|
|
28
28
|
flags: {
|
|
29
|
-
project: { type: 'string'
|
|
29
|
+
project: { type: 'string' },
|
|
30
30
|
mode: { type: 'string' },
|
|
31
31
|
subject: { type: 'string' },
|
|
32
32
|
theme: { type: 'string' },
|
|
@@ -50,7 +50,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const projectName = flags.project;
|
|
53
|
+
const projectName = flags.project || getProjectName(argv);
|
|
54
54
|
const projectRoot = getProjectRoot(projectName);
|
|
55
55
|
const dryRun = flags['dry-run'] || false;
|
|
56
56
|
|
|
@@ -79,9 +79,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
79
79
|
|
|
80
80
|
// 2. Create batch directory
|
|
81
81
|
const { batchId, batchDir } = await createBatchDir(projectRoot);
|
|
82
|
-
info(`Batch: ${batchId}`);
|
|
82
|
+
info('batch-generate', `Batch: ${batchId}`);
|
|
83
83
|
|
|
84
84
|
// 3. Execute runs
|
|
85
|
+
const batchStartMs = Date.now();
|
|
85
86
|
const results = await executeBatchRuns({
|
|
86
87
|
projectRoot,
|
|
87
88
|
projectName,
|
|
@@ -90,6 +91,12 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
90
91
|
slotBriefs,
|
|
91
92
|
dryRun,
|
|
92
93
|
});
|
|
94
|
+
const batchElapsedMs = Date.now() - batchStartMs;
|
|
95
|
+
if (!dryRun && results.length > 0) {
|
|
96
|
+
const avgSec = Math.round(batchElapsedMs / results.length / 1000);
|
|
97
|
+
const totalSec = Math.round(batchElapsedMs / 1000);
|
|
98
|
+
info('batch-generate', `Completed ${results.length} slot(s) in ${totalSec}s (avg ${avgSec}s/slot)`);
|
|
99
|
+
}
|
|
93
100
|
|
|
94
101
|
// 4. Build manifest
|
|
95
102
|
const manifest = {
|
|
@@ -161,7 +168,9 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
161
168
|
console.log(` ${status} ${r.label.padEnd(24)} → ${r.run_id} ${r.selected_output || '(dry run)'}`);
|
|
162
169
|
}
|
|
163
170
|
console.log('');
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
// Primary artifact paths — always print (survives --quiet) so pipelines
|
|
172
|
+
// can parse the output location.
|
|
173
|
+
result(`Sheet: batches/${batchId}/sheets/primary-sheet.html`);
|
|
174
|
+
result(`Manifest: batches/${batchId}/manifest.json`);
|
|
166
175
|
}
|
|
167
176
|
}
|
package/scripts/batch-sheet.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* sdlab batch sheet batch_2025-06-30_001 --project star-freight
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { parseArgs } from '../lib/args.js';
|
|
10
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
11
11
|
import { getProjectRoot } from '../lib/paths.js';
|
|
12
12
|
import { loadBatchManifest } from '../lib/batch-runs.js';
|
|
13
13
|
import { getBatchMode } from '../lib/batch-modes.js';
|
|
@@ -20,13 +20,13 @@ import { info } from '../lib/log.js';
|
|
|
20
20
|
export async function run(argv = process.argv.slice(2)) {
|
|
21
21
|
const { flags, positionals } = parseArgs(argv, {
|
|
22
22
|
flags: {
|
|
23
|
-
project: { type: 'string'
|
|
23
|
+
project: { type: 'string' },
|
|
24
24
|
batch: { type: 'string' },
|
|
25
25
|
},
|
|
26
26
|
deprecated: { game: 'project' },
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
const projectName = flags.project;
|
|
29
|
+
const projectName = flags.project || getProjectName(argv);
|
|
30
30
|
const projectRoot = getProjectRoot(projectName);
|
|
31
31
|
const batchId = positionals[0] || flags.batch;
|
|
32
32
|
|
package/scripts/batch-show.js
CHANGED
|
@@ -9,21 +9,21 @@
|
|
|
9
9
|
* sdlab batch show batch_2025-06-30_001 --json # as JSON
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { parseArgs } from '../lib/args.js';
|
|
12
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
13
13
|
import { getProjectRoot } from '../lib/paths.js';
|
|
14
14
|
import { listBatches, loadBatchManifest } from '../lib/batch-runs.js';
|
|
15
15
|
|
|
16
16
|
export async function run(argv = process.argv.slice(2)) {
|
|
17
17
|
const { flags, positionals } = parseArgs(argv, {
|
|
18
18
|
flags: {
|
|
19
|
-
project: { type: 'string'
|
|
19
|
+
project: { type: 'string' },
|
|
20
20
|
batch: { type: 'string' },
|
|
21
21
|
json: { type: 'boolean' },
|
|
22
22
|
},
|
|
23
23
|
deprecated: { game: 'project' },
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
const projectName = flags.project;
|
|
26
|
+
const projectName = flags.project || getProjectName(argv);
|
|
27
27
|
const projectRoot = getProjectRoot(projectName);
|
|
28
28
|
const batchId = positionals[0] || flags.batch;
|
|
29
29
|
|
package/scripts/brief-compile.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* sdlab brief compile --project star-freight --workflow character-portrait-set --subject kael_maren --output-count 8 --json
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { parseArgs } from '../lib/args.js';
|
|
13
|
+
import { parseArgs, getProjectName, parseNumberFlag } from '../lib/args.js';
|
|
14
14
|
import { getProjectRoot } from '../lib/paths.js';
|
|
15
15
|
import { compileBrief, saveCompiledBrief } from '../lib/brief-compiler.js';
|
|
16
16
|
import { info } from '../lib/log.js';
|
|
@@ -18,7 +18,7 @@ import { info } from '../lib/log.js';
|
|
|
18
18
|
export async function run(argv = process.argv.slice(2)) {
|
|
19
19
|
const { flags } = parseArgs(argv, {
|
|
20
20
|
flags: {
|
|
21
|
-
project: { type: 'string'
|
|
21
|
+
project: { type: 'string' },
|
|
22
22
|
workflow: { type: 'string' },
|
|
23
23
|
subject: { type: 'string' },
|
|
24
24
|
asset: { type: 'string' },
|
|
@@ -58,7 +58,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const projectName = flags.project;
|
|
61
|
+
const projectName = flags.project || getProjectName(argv);
|
|
62
62
|
const projectRoot = getProjectRoot(projectName);
|
|
63
63
|
|
|
64
64
|
// Parse reference IDs
|
|
@@ -68,11 +68,11 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
68
68
|
|
|
69
69
|
// Parse overrides
|
|
70
70
|
const overrides = {};
|
|
71
|
-
if (flags['output-count']) overrides.output_count =
|
|
72
|
-
if (flags.width) overrides.width =
|
|
73
|
-
if (flags.height) overrides.height =
|
|
74
|
-
if (flags.steps) overrides.steps =
|
|
75
|
-
if (flags.cfg) overrides.cfg =
|
|
71
|
+
if (flags['output-count']) overrides.output_count = parseNumberFlag('output-count', flags['output-count'], { int: true, min: 1 });
|
|
72
|
+
if (flags.width) overrides.width = parseNumberFlag('width', flags.width, { int: true, min: 1 });
|
|
73
|
+
if (flags.height) overrides.height = parseNumberFlag('height', flags.height, { int: true, min: 1 });
|
|
74
|
+
if (flags.steps) overrides.steps = parseNumberFlag('steps', flags.steps, { int: true, min: 1 });
|
|
75
|
+
if (flags.cfg) overrides.cfg = parseNumberFlag('cfg', flags.cfg, { min: 0 });
|
|
76
76
|
if (flags.sampler) overrides.sampler = flags.sampler;
|
|
77
77
|
if (flags['seed-mode']) overrides.seed_mode = flags['seed-mode'];
|
|
78
78
|
|
package/scripts/brief-show.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* sdlab brief show brief_2026-04-15_001 --md
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { parseArgs } from '../lib/args.js';
|
|
12
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
13
13
|
import { getProjectRoot } from '../lib/paths.js';
|
|
14
14
|
import { loadCompiledBrief } from '../lib/brief-compiler.js';
|
|
15
15
|
import { renderBriefText, renderBriefMarkdown } from '../lib/brief-render.js';
|
|
@@ -17,7 +17,7 @@ import { renderBriefText, renderBriefMarkdown } from '../lib/brief-render.js';
|
|
|
17
17
|
export async function run(argv = process.argv.slice(2)) {
|
|
18
18
|
const { flags, positionals } = parseArgs(argv, {
|
|
19
19
|
flags: {
|
|
20
|
-
project: { type: 'string'
|
|
20
|
+
project: { type: 'string' },
|
|
21
21
|
json: { type: 'boolean' },
|
|
22
22
|
md: { type: 'boolean' },
|
|
23
23
|
},
|
|
@@ -31,7 +31,8 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const
|
|
34
|
+
const projectName = flags.project || getProjectName(argv);
|
|
35
|
+
const projectRoot = getProjectRoot(projectName);
|
|
35
36
|
const brief = await loadCompiledBrief(projectRoot, briefId);
|
|
36
37
|
|
|
37
38
|
if (flags.json) {
|
package/scripts/canon-bind.js
CHANGED
|
@@ -21,6 +21,7 @@ import { join } from 'path';
|
|
|
21
21
|
import { getProjectName } from '../lib/args.js';
|
|
22
22
|
import { REPO_ROOT } from '../lib/paths.js';
|
|
23
23
|
import { loadConstitution, loadLanes, loadRubric, loadTerminology, detectLane, detectGroup } from '../lib/config.js';
|
|
24
|
+
import { inputError, handleCliError } from '../lib/errors.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Build rationale string from rule's template, interpolating variables.
|
|
@@ -162,10 +163,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
162
163
|
|
|
163
164
|
// Load project config — fail loudly if missing
|
|
164
165
|
if (!existsSync(join(PROJECT_ROOT, 'constitution.json'))) {
|
|
165
|
-
throw
|
|
166
|
-
|
|
167
|
-
`
|
|
168
|
-
`Run: sdlab init ${projectName} --domain <domain>`
|
|
166
|
+
throw inputError(
|
|
167
|
+
'INPUT_MISSING_CONFIG',
|
|
168
|
+
`No constitution.json found in ${PROJECT_ROOT}.`,
|
|
169
|
+
`Canon binding requires per-project config files. Run: sdlab init ${projectName} --domain <domain>`
|
|
169
170
|
);
|
|
170
171
|
}
|
|
171
172
|
|
|
@@ -175,7 +176,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
175
176
|
const termConfig = loadTerminology(PROJECT_ROOT);
|
|
176
177
|
|
|
177
178
|
if (!constitutionConfig.rules || constitutionConfig.rules.length === 0) {
|
|
178
|
-
throw
|
|
179
|
+
throw inputError('INPUT_EMPTY_CONFIG', `constitution.json has no rules — cannot bind.`);
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
const constitutionRules = constitutionConfig.rules;
|
|
@@ -280,8 +281,5 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
280
281
|
|
|
281
282
|
// Direct execution guard
|
|
282
283
|
if (process.argv[1] && (process.argv[1].endsWith('canon-bind.js') || process.argv[1].endsWith('canon-bind'))) {
|
|
283
|
-
run().catch(
|
|
284
|
-
console.error(err.message || err);
|
|
285
|
-
process.exit(1);
|
|
286
|
-
});
|
|
284
|
+
run().catch(handleCliError);
|
|
287
285
|
}
|
package/scripts/card.js
CHANGED
|
@@ -4,43 +4,44 @@
|
|
|
4
4
|
* card.js — Generate dataset cards.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* sdlab card generate --snapshot <id> --split <id> [--project <name>]
|
|
7
|
+
* sdlab card generate [--snapshot <id>] [--split <id>] [--project <name>]
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { writeFile } from 'node:fs/promises';
|
|
12
|
-
import { getProjectName } from '../lib/args.js';
|
|
12
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
13
13
|
import { REPO_ROOT } from '../lib/paths.js';
|
|
14
|
+
import { inputError, handleCliError } from '../lib/errors.js';
|
|
14
15
|
import { listSnapshots } from '../lib/snapshot.js';
|
|
15
16
|
import { listSplits } from '../lib/split.js';
|
|
16
17
|
import { generateCard } from '../lib/card.js';
|
|
17
18
|
|
|
18
19
|
export async function run(argv = process.argv.slice(2)) {
|
|
19
|
-
const
|
|
20
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
21
|
+
flags: {
|
|
22
|
+
project: { type: 'string' },
|
|
23
|
+
snapshot: { type: 'string' },
|
|
24
|
+
split: { type: 'string' },
|
|
25
|
+
},
|
|
26
|
+
deprecated: { game: 'project' },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const projectName = flags.project || getProjectName(argv);
|
|
20
30
|
const projectRoot = join(REPO_ROOT, 'projects', projectName);
|
|
21
|
-
const subcommand =
|
|
22
|
-
const args = argv.filter(a => a !== subcommand);
|
|
31
|
+
const subcommand = positionals[0] || 'generate';
|
|
23
32
|
|
|
24
33
|
if (subcommand === 'generate') {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const snapIdx = args.indexOf('--snapshot');
|
|
28
|
-
if (snapIdx >= 0) {
|
|
29
|
-
snapshotId = args[snapIdx + 1];
|
|
30
|
-
} else {
|
|
34
|
+
let snapshotId = flags.snapshot;
|
|
35
|
+
if (!snapshotId) {
|
|
31
36
|
const snapshots = await listSnapshots(projectRoot);
|
|
32
|
-
if (snapshots.length === 0) throw
|
|
37
|
+
if (snapshots.length === 0) throw inputError('INPUT_NO_SNAPSHOT', 'No snapshots found. Run: sdlab snapshot create');
|
|
33
38
|
snapshotId = snapshots[snapshots.length - 1].id;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const splitIdx = args.indexOf('--split');
|
|
39
|
-
if (splitIdx >= 0) {
|
|
40
|
-
splitId = args[splitIdx + 1];
|
|
41
|
-
} else {
|
|
41
|
+
let splitId = flags.split;
|
|
42
|
+
if (!splitId) {
|
|
42
43
|
const splits = await listSplits(projectRoot);
|
|
43
|
-
if (splits.length === 0) throw
|
|
44
|
+
if (splits.length === 0) throw inputError('INPUT_NO_SPLIT', 'No splits found. Run: sdlab split build');
|
|
44
45
|
splitId = splits[splits.length - 1].id;
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -51,7 +52,6 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
51
52
|
|
|
52
53
|
const { markdown, json } = await generateCard(projectRoot, snapshotId, splitId);
|
|
53
54
|
|
|
54
|
-
// Write to project root
|
|
55
55
|
const mdPath = join(projectRoot, 'dataset-card.md');
|
|
56
56
|
const jsonPath = join(projectRoot, 'dataset-card.json');
|
|
57
57
|
await writeFile(mdPath, markdown);
|
|
@@ -61,13 +61,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
61
61
|
console.log(` \x1b[32m✓\x1b[0m dataset-card.json`);
|
|
62
62
|
|
|
63
63
|
} else {
|
|
64
|
-
throw
|
|
64
|
+
throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: generate`);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
if (process.argv[1] && (process.argv[1].endsWith('card.js') || process.argv[1].endsWith('card'))) {
|
|
69
|
-
run().catch(
|
|
70
|
-
console.error(err.message || err);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
});
|
|
69
|
+
run().catch(handleCliError);
|
|
73
70
|
}
|
package/scripts/compare.js
CHANGED
|
@@ -8,62 +8,70 @@
|
|
|
8
8
|
* sdlab compare iron_sword_base iron_sword_icon_mid a "Better silhouette" --project star-freight
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { writeFile, readdir, mkdir, readFile } from "node:fs/promises";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
-
import { getProjectName } from "../lib/args.js";
|
|
13
|
+
import { parseArgs, getProjectName } from "../lib/args.js";
|
|
14
14
|
import { REPO_ROOT } from "../lib/paths.js";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const prefix = `--${flag}=`;
|
|
18
|
-
for (const a of args) {
|
|
19
|
-
if (a.startsWith(prefix)) return a.slice(prefix.length);
|
|
20
|
-
}
|
|
21
|
-
const idx = args.indexOf(`--${flag}`);
|
|
22
|
-
if (idx !== -1 && idx + 1 < args.length) return args[idx + 1];
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
15
|
+
import { readJsonFile } from "../lib/config.js";
|
|
16
|
+
import { inputError, handleCliError } from "../lib/errors.js";
|
|
25
17
|
|
|
26
18
|
export async function run(argv = process.argv.slice(2)) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (i > 0 && (argv[i - 1] === '--project' || argv[i - 1] === '--game')) return false;
|
|
34
|
-
return true;
|
|
19
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
20
|
+
flags: {
|
|
21
|
+
project: { type: 'string' },
|
|
22
|
+
scores: { type: 'string' },
|
|
23
|
+
},
|
|
24
|
+
deprecated: { game: 'project' },
|
|
35
25
|
});
|
|
36
26
|
|
|
37
|
-
const
|
|
27
|
+
const projectName = flags.project || getProjectName(argv);
|
|
28
|
+
const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
|
|
29
|
+
|
|
30
|
+
const [assetAId, assetBId, winner, ...reasonParts] = positionals;
|
|
38
31
|
|
|
39
32
|
if (!assetAId || !assetBId || !winner) {
|
|
40
|
-
throw
|
|
33
|
+
throw inputError(
|
|
34
|
+
'INPUT_MISSING_ARGS',
|
|
35
|
+
'Usage: sdlab compare <asset_a_id> <asset_b_id> <a|b|tie> <reasoning>'
|
|
36
|
+
);
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
if (!["a", "b", "tie"].includes(winner)) {
|
|
44
|
-
throw
|
|
40
|
+
throw inputError(
|
|
41
|
+
'INPUT_BAD_WINNER',
|
|
42
|
+
`Invalid winner: ${winner}. Must be a, b, or tie.`
|
|
43
|
+
);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
const reasoning = reasonParts.
|
|
46
|
+
const reasoning = reasonParts.join(" ") || null;
|
|
48
47
|
|
|
49
48
|
// Load records to get paths
|
|
50
49
|
const recordA = await loadRecord(GAME_ROOT, assetAId);
|
|
51
50
|
const recordB = await loadRecord(GAME_ROOT, assetBId);
|
|
52
51
|
|
|
53
52
|
if (!recordA || !recordB) {
|
|
54
|
-
throw
|
|
53
|
+
throw inputError('INPUT_UNKNOWN_RECORD', 'Could not load one or both records.');
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
// Parse optional scores
|
|
58
|
-
const scoresStr =
|
|
57
|
+
const scoresStr = flags.scores;
|
|
59
58
|
const criteria_scores = {};
|
|
60
59
|
if (scoresStr) {
|
|
61
60
|
for (const pair of scoresStr.split(",")) {
|
|
62
61
|
const [k, v] = pair.split(":");
|
|
63
62
|
// Format: "silhouette:0.9/0.6" → { silhouette: { a: 0.9, b: 0.6 } }
|
|
64
63
|
if (k && v) {
|
|
65
|
-
const [
|
|
66
|
-
|
|
64
|
+
const [aStr, bStr] = v.split("/");
|
|
65
|
+
const aScore = parseFloat(aStr);
|
|
66
|
+
const bScore = parseFloat(bStr);
|
|
67
|
+
if (!Number.isFinite(aScore) || !Number.isFinite(bScore)) {
|
|
68
|
+
throw inputError(
|
|
69
|
+
'INPUT_BAD_SCORE',
|
|
70
|
+
`Score for "${k.trim()}" is not numeric: "${v}"`,
|
|
71
|
+
'Use the form --scores silhouette:0.9/0.6,palette:0.8/0.7'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
criteria_scores[k.trim()] = { a: aScore, b: bScore };
|
|
67
75
|
}
|
|
68
76
|
}
|
|
69
77
|
}
|
|
@@ -101,7 +109,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
101
109
|
async function loadRecord(GAME_ROOT, id) {
|
|
102
110
|
try {
|
|
103
111
|
const path = join(GAME_ROOT, `records/${id}.json`);
|
|
104
|
-
return
|
|
112
|
+
return await readJsonFile(path);
|
|
105
113
|
} catch {
|
|
106
114
|
console.error(`Record not found: records/${id}.json`);
|
|
107
115
|
return null;
|
|
@@ -119,8 +127,5 @@ async function countComparisons(GAME_ROOT) {
|
|
|
119
127
|
|
|
120
128
|
// Direct execution guard
|
|
121
129
|
if (process.argv[1] && (process.argv[1].endsWith('compare.js') || process.argv[1].endsWith('compare'))) {
|
|
122
|
-
run().catch(
|
|
123
|
-
console.error(err.message || err);
|
|
124
|
-
process.exit(1);
|
|
125
|
-
});
|
|
130
|
+
run().catch(handleCliError);
|
|
126
131
|
}
|
package/scripts/critique-show.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* sdlab critique show --project star-freight --run run_2026-04-15_001 --md
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { parseArgs } from '../lib/args.js';
|
|
12
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
13
13
|
import { getProjectRoot } from '../lib/paths.js';
|
|
14
14
|
import { loadCritique } from '../lib/critique-engine.js';
|
|
15
15
|
import { renderCritiqueMarkdown, renderCritiqueText } from '../lib/critique-render.js';
|
|
@@ -17,7 +17,7 @@ import { renderCritiqueMarkdown, renderCritiqueText } from '../lib/critique-rend
|
|
|
17
17
|
export async function run(argv = process.argv.slice(2)) {
|
|
18
18
|
const { flags } = parseArgs(argv, {
|
|
19
19
|
flags: {
|
|
20
|
-
project: { type: 'string'
|
|
20
|
+
project: { type: 'string' },
|
|
21
21
|
run: { type: 'string' },
|
|
22
22
|
json: { type: 'boolean' },
|
|
23
23
|
md: { type: 'boolean' },
|
|
@@ -35,7 +35,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const projectName = flags.project;
|
|
38
|
+
const projectName = flags.project || getProjectName(argv);
|
|
39
39
|
const projectRoot = getProjectRoot(projectName);
|
|
40
40
|
|
|
41
41
|
const report = loadCritique(projectRoot, flags.run);
|
package/scripts/critique.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* sdlab critique --project star-freight --run run_2026-04-15_001 --json
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { parseArgs } from '../lib/args.js';
|
|
11
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
12
12
|
import { getProjectRoot } from '../lib/paths.js';
|
|
13
13
|
import { critiqueRun, saveCritique } from '../lib/critique-engine.js';
|
|
14
14
|
import { renderCritiqueMarkdown, renderCritiqueText } from '../lib/critique-render.js';
|
|
@@ -17,7 +17,7 @@ import { info } from '../lib/log.js';
|
|
|
17
17
|
export async function run(argv = process.argv.slice(2)) {
|
|
18
18
|
const { flags } = parseArgs(argv, {
|
|
19
19
|
flags: {
|
|
20
|
-
project: { type: 'string'
|
|
20
|
+
project: { type: 'string' },
|
|
21
21
|
run: { type: 'string' },
|
|
22
22
|
json: { type: 'boolean' },
|
|
23
23
|
},
|
|
@@ -33,7 +33,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const projectName = flags.project;
|
|
36
|
+
const projectName = flags.project || getProjectName(argv);
|
|
37
37
|
const projectRoot = getProjectRoot(projectName);
|
|
38
38
|
|
|
39
39
|
console.log(`\x1b[1mstyle-dataset-lab\x1b[0m critique`);
|