@mcptoolshop/style-dataset-lab 3.0.0 → 3.1.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 +108 -15
- package/CODE_OF_CONDUCT.md +9 -0
- package/CONTRIBUTING.md +71 -0
- package/README.es.md +99 -8
- package/README.fr.md +100 -9
- package/README.hi.md +102 -9
- package/README.it.md +99 -8
- package/README.ja.md +100 -9
- package/README.md +64 -1
- package/README.pt-BR.md +99 -8
- package/README.zh.md +100 -9
- package/SECURITY.md +6 -5
- package/bin/sdlab.js +289 -62
- package/lib/adapters/comfyui-runner.js +11 -15
- 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 +162 -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-output.js +70 -0
- 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-adapters.js +79 -0
- package/lib/training-manifests.js +17 -3
- package/lib/training-packages.js +29 -28
- package/lib/training-profiles.js +10 -0
- package/package.json +27 -3
- package/scripts/batch-generate.js +72 -20
- 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 +96 -29
- 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
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`);
|
package/scripts/curate.js
CHANGED
|
@@ -7,59 +7,73 @@
|
|
|
7
7
|
* node scripts/curate.js <asset_id> approved "Clean silhouette, correct palette" --scores silhouette:0.9,palette:0.8
|
|
8
8
|
* node scripts/curate.js <asset_id> rejected "3D render look, alpha halo" --failures alpha_halo,3d_render_look
|
|
9
9
|
* node scripts/curate.js --list (show uncurated candidates)
|
|
10
|
-
* sdlab curate <asset_id> approved "explanation" --project star-freight
|
|
10
|
+
* sdlab curate <asset_id> approved "explanation" --project star-freight [--dry-run]
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { writeFile, rename, readdir, mkdir, readFile } from "node:fs/promises";
|
|
14
14
|
import { join } from "node:path";
|
|
15
|
-
import { getProjectName } from "../lib/args.js";
|
|
15
|
+
import { parseArgs, getProjectName } from "../lib/args.js";
|
|
16
16
|
import { REPO_ROOT } from "../lib/paths.js";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
for (const a of args) {
|
|
21
|
-
if (a.startsWith(prefix)) return a.slice(prefix.length);
|
|
22
|
-
}
|
|
23
|
-
const idx = args.indexOf(`--${flag}`);
|
|
24
|
-
if (idx !== -1 && idx + 1 < args.length) return args[idx + 1];
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
17
|
+
import { readJsonFile } from "../lib/config.js";
|
|
18
|
+
import { inputError, runtimeError, handleCliError } from "../lib/errors.js";
|
|
19
|
+
import { result } from "../lib/log.js";
|
|
27
20
|
|
|
28
21
|
export async function run(argv = process.argv.slice(2)) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
23
|
+
flags: {
|
|
24
|
+
project: { type: 'string' },
|
|
25
|
+
scores: { type: 'string' },
|
|
26
|
+
failures: { type: 'string' },
|
|
27
|
+
notes: { type: 'string' },
|
|
28
|
+
list: { type: 'boolean', default: false },
|
|
29
|
+
'dry-run': { type: 'boolean', default: false },
|
|
30
|
+
},
|
|
31
|
+
deprecated: { game: 'project' },
|
|
37
32
|
});
|
|
38
33
|
|
|
39
|
-
|
|
34
|
+
const projectName = flags.project || getProjectName(argv);
|
|
35
|
+
const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
|
|
36
|
+
const dryRun = flags['dry-run'] || flags.dryRun;
|
|
37
|
+
|
|
38
|
+
if (flags.list) {
|
|
40
39
|
return listUncurated(GAME_ROOT);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
const [assetId, status, explanation
|
|
42
|
+
const [assetId, status, explanation] = positionals;
|
|
44
43
|
|
|
45
44
|
if (!assetId || !status || !explanation) {
|
|
46
|
-
throw
|
|
45
|
+
throw inputError(
|
|
46
|
+
'INPUT_MISSING_ARGS',
|
|
47
|
+
'Usage: sdlab curate <asset_id> <approved|rejected|borderline> <explanation> [--scores k:v,...] [--failures f1,f2] [--dry-run]'
|
|
48
|
+
);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
if (!["approved", "rejected", "borderline"].includes(status)) {
|
|
50
|
-
throw
|
|
52
|
+
throw inputError(
|
|
53
|
+
'INPUT_BAD_STATUS',
|
|
54
|
+
`Invalid status: ${status}. Must be approved, rejected, or borderline.`
|
|
55
|
+
);
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const notesStr = getFlagValue(rest, "notes");
|
|
58
|
+
const scoresStr = flags.scores;
|
|
59
|
+
const failuresStr = flags.failures;
|
|
60
|
+
const notesStr = flags.notes;
|
|
57
61
|
|
|
58
62
|
const criteria_scores = {};
|
|
59
63
|
if (scoresStr) {
|
|
60
64
|
for (const pair of scoresStr.split(",")) {
|
|
61
65
|
const [k, v] = pair.split(":");
|
|
62
|
-
if (k && v)
|
|
66
|
+
if (k && v) {
|
|
67
|
+
const score = parseFloat(v);
|
|
68
|
+
if (!Number.isFinite(score)) {
|
|
69
|
+
throw inputError(
|
|
70
|
+
'INPUT_BAD_SCORE',
|
|
71
|
+
`Score value for "${k.trim()}" is not a number: "${v}"`,
|
|
72
|
+
'Use the form --scores silhouette:0.9,palette:0.8'
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
criteria_scores[k.trim()] = score;
|
|
76
|
+
}
|
|
63
77
|
}
|
|
64
78
|
}
|
|
65
79
|
|
|
@@ -69,17 +83,44 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
69
83
|
const recordPath = join(GAME_ROOT, `records/${assetId}.json`);
|
|
70
84
|
let record;
|
|
71
85
|
try {
|
|
72
|
-
record =
|
|
73
|
-
} catch {
|
|
74
|
-
|
|
86
|
+
record = await readJsonFile(recordPath);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err && err.code === 'INPUT_FILE_NOT_FOUND') {
|
|
89
|
+
throw inputError('INPUT_UNKNOWN_RECORD', `Record not found: ${recordPath}`);
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
75
92
|
}
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
const oldPath = join(GAME_ROOT,
|
|
94
|
+
const originalAssetPath = record.asset_path;
|
|
95
|
+
const oldPath = join(GAME_ROOT, originalAssetPath);
|
|
79
96
|
const newDir = `outputs/${status}`;
|
|
80
97
|
const newPath = `${newDir}/${assetId}.png`;
|
|
98
|
+
const newFullPath = join(GAME_ROOT, newPath);
|
|
99
|
+
|
|
100
|
+
if (dryRun) {
|
|
101
|
+
console.log(`\x1b[33m(dry-run)\x1b[0m ${assetId}`);
|
|
102
|
+
console.log(` status: ${status}`);
|
|
103
|
+
console.log(` move: ${originalAssetPath} -> ${newPath}`);
|
|
104
|
+
console.log(` note: ${explanation}`);
|
|
105
|
+
if (failure_modes.length) console.log(` failures: ${failure_modes.join(", ")}`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
81
109
|
await mkdir(join(GAME_ROOT, newDir), { recursive: true });
|
|
82
110
|
|
|
111
|
+
// Move image FIRST — if the rename fails we still have a clean record.
|
|
112
|
+
try {
|
|
113
|
+
await rename(oldPath, newFullPath);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
throw runtimeError(
|
|
116
|
+
'RUNTIME_MOVE_FAILED',
|
|
117
|
+
`Could not move ${oldPath} to ${newPath}: ${err.message}`,
|
|
118
|
+
'Check that the source image exists and the destination is writable.',
|
|
119
|
+
err
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Now safely update the record. If writing fails, try to restore the image.
|
|
83
124
|
record.asset_path = newPath;
|
|
84
125
|
record.judgment = {
|
|
85
126
|
status,
|
|
@@ -92,16 +133,23 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
92
133
|
confidence: 0.9,
|
|
93
134
|
};
|
|
94
135
|
|
|
95
|
-
await writeFile(recordPath, JSON.stringify(record, null, 2));
|
|
96
|
-
|
|
97
|
-
// Move image file
|
|
98
136
|
try {
|
|
99
|
-
await
|
|
100
|
-
} catch {
|
|
101
|
-
|
|
137
|
+
await writeFile(recordPath, JSON.stringify(record, null, 2));
|
|
138
|
+
} catch (err) {
|
|
139
|
+
// Attempt rollback: put the image back.
|
|
140
|
+
try { await rename(newFullPath, oldPath); } catch {}
|
|
141
|
+
throw runtimeError(
|
|
142
|
+
'RUNTIME_WRITE_FAILED',
|
|
143
|
+
`Could not write record ${recordPath}: ${err.message}`,
|
|
144
|
+
null,
|
|
145
|
+
err
|
|
146
|
+
);
|
|
102
147
|
}
|
|
103
148
|
|
|
104
|
-
|
|
149
|
+
// Final artifact path — always shown, even under --quiet.
|
|
150
|
+
result('curate', `${assetId} → ${status}`);
|
|
151
|
+
result(` image: ${newPath}`);
|
|
152
|
+
result(` record: records/${assetId}.json`);
|
|
105
153
|
console.log(` ${explanation}`);
|
|
106
154
|
if (failure_modes.length) console.log(` Failures: ${failure_modes.join(", ")}`);
|
|
107
155
|
}
|
|
@@ -135,8 +183,5 @@ async function listUncurated(GAME_ROOT) {
|
|
|
135
183
|
|
|
136
184
|
// Direct execution guard
|
|
137
185
|
if (process.argv[1] && (process.argv[1].endsWith('curate.js') || process.argv[1].endsWith('curate'))) {
|
|
138
|
-
run().catch(
|
|
139
|
-
console.error(err.message || err);
|
|
140
|
-
process.exit(1);
|
|
141
|
-
});
|
|
186
|
+
run().catch(handleCliError);
|
|
142
187
|
}
|
package/scripts/doctor.js
CHANGED
|
@@ -15,6 +15,7 @@ import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { getProjectName } from '../lib/args.js';
|
|
17
17
|
import { REPO_ROOT } from '../lib/paths.js';
|
|
18
|
+
import { runtimeError, handleCliError } from '../lib/errors.js';
|
|
18
19
|
|
|
19
20
|
const REQUIRED_CONFIG_FILES = [
|
|
20
21
|
'project.json',
|
|
@@ -484,14 +485,11 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
484
485
|
console.log(`═══ ${status} — ${passes} passed, ${failures} failed, ${warnings} warnings ═══`);
|
|
485
486
|
|
|
486
487
|
if (failures > 0) {
|
|
487
|
-
throw
|
|
488
|
+
throw runtimeError('PROJECT_UNHEALTHY', `Project "${projectName}" has ${failures} issue(s).`);
|
|
488
489
|
}
|
|
489
490
|
}
|
|
490
491
|
|
|
491
492
|
// Direct execution guard
|
|
492
493
|
if (process.argv[1] && (process.argv[1].endsWith('doctor.js') || process.argv[1].endsWith('doctor'))) {
|
|
493
|
-
run().catch(
|
|
494
|
-
console.error(err.message || err);
|
|
495
|
-
process.exit(1);
|
|
496
|
-
});
|
|
494
|
+
run().catch(handleCliError);
|
|
497
495
|
}
|
package/scripts/eligibility.js
CHANGED
|
@@ -4,36 +4,44 @@
|
|
|
4
4
|
* eligibility.js — Audit training/eval eligibility for all project records.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* sdlab eligibility audit [--project <name>]
|
|
7
|
+
* sdlab eligibility audit [--project <name>] [--profile <id>]
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { readdir, readFile } from 'node:fs/promises';
|
|
11
11
|
import { join } from 'node:path';
|
|
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
14
|
import { loadProjectConfig, loadSelectionProfile } from '../lib/config.js';
|
|
15
|
+
import { handleCliError } from '../lib/errors.js';
|
|
15
16
|
import { evaluateEligibility, categorizeExclusions } from '../lib/eligibility.js';
|
|
16
17
|
|
|
17
18
|
export async function run(argv = process.argv.slice(2)) {
|
|
18
|
-
const
|
|
19
|
+
const { flags } = parseArgs(argv, {
|
|
20
|
+
flags: {
|
|
21
|
+
project: { type: 'string' },
|
|
22
|
+
profile: { type: 'string' },
|
|
23
|
+
},
|
|
24
|
+
deprecated: { game: 'project' },
|
|
25
|
+
allowUnknown: true,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const projectName = flags.project || getProjectName(argv);
|
|
19
29
|
const projectRoot = join(REPO_ROOT, 'projects', projectName);
|
|
20
30
|
|
|
21
|
-
const
|
|
22
|
-
const profileId = profileIdx >= 0 ? argv[profileIdx + 1] : null;
|
|
23
|
-
const profile = loadSelectionProfile(projectRoot, profileId);
|
|
31
|
+
const profile = loadSelectionProfile(projectRoot, flags.profile);
|
|
24
32
|
const config = loadProjectConfig(projectRoot);
|
|
25
33
|
|
|
26
34
|
const recordsDir = join(projectRoot, 'records');
|
|
27
35
|
const files = (await readdir(recordsDir)).filter(f => f.endsWith('.json')).sort();
|
|
28
36
|
|
|
29
37
|
console.log(`\x1b[1msdlab eligibility audit\x1b[0m — ${projectName}`);
|
|
30
|
-
console.log(` Profile: ${
|
|
38
|
+
console.log(` Profile: ${flags.profile || 'training-default (built-in)'}`);
|
|
31
39
|
console.log(` Records: ${files.length}`);
|
|
32
40
|
console.log('');
|
|
33
41
|
|
|
34
42
|
const eligible = [];
|
|
35
43
|
const excluded = [];
|
|
36
|
-
const nearMiss = [];
|
|
44
|
+
const nearMiss = [];
|
|
37
45
|
|
|
38
46
|
for (const file of files) {
|
|
39
47
|
const raw = await readFile(join(recordsDir, file), 'utf-8');
|
|
@@ -55,7 +63,6 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
55
63
|
console.log(` \x1b[31m✗\x1b[0m Excluded: ${excluded.length}`);
|
|
56
64
|
console.log('');
|
|
57
65
|
|
|
58
|
-
// Categorize exclusions
|
|
59
66
|
const categories = categorizeExclusions(excluded);
|
|
60
67
|
console.log(' Exclusion breakdown:');
|
|
61
68
|
for (const [cat, count] of Object.entries(categories)) {
|
|
@@ -65,7 +72,6 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
// Near-miss improvement opportunities
|
|
69
75
|
if (nearMiss.length > 0) {
|
|
70
76
|
console.log(`\n Improvement opportunities (${nearMiss.length} records with 1 failing check):`);
|
|
71
77
|
const byReason = {};
|
|
@@ -84,8 +90,5 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
84
90
|
|
|
85
91
|
// Direct execution guard
|
|
86
92
|
if (process.argv[1] && (process.argv[1].endsWith('eligibility.js') || process.argv[1].endsWith('eligibility'))) {
|
|
87
|
-
run().catch(
|
|
88
|
-
console.error(err.message || err);
|
|
89
|
-
process.exit(1);
|
|
90
|
-
});
|
|
93
|
+
run().catch(handleCliError);
|
|
91
94
|
}
|
package/scripts/eval-pack.js
CHANGED
|
@@ -11,15 +11,20 @@
|
|
|
11
11
|
|
|
12
12
|
import { readFile } from 'node:fs/promises';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
|
-
import { getProjectName } from '../lib/args.js';
|
|
14
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
15
15
|
import { REPO_ROOT } from '../lib/paths.js';
|
|
16
|
+
import { inputError, handleCliError } from '../lib/errors.js';
|
|
16
17
|
import { buildEvalPack, listEvalPacks, loadEvalPack } from '../lib/eval-pack.js';
|
|
17
18
|
|
|
18
19
|
export async function run(argv = process.argv.slice(2)) {
|
|
19
|
-
const
|
|
20
|
+
const { flags, positionals } = parseArgs(argv, {
|
|
21
|
+
flags: { project: { type: 'string' } },
|
|
22
|
+
deprecated: { game: 'project' },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const projectName = flags.project || getProjectName(argv);
|
|
20
26
|
const projectRoot = join(REPO_ROOT, 'projects', projectName);
|
|
21
|
-
const subcommand =
|
|
22
|
-
const args = argv.filter(a => a !== subcommand);
|
|
27
|
+
const subcommand = positionals[0] || 'list';
|
|
23
28
|
|
|
24
29
|
if (subcommand === 'build') {
|
|
25
30
|
console.log(`\x1b[1msdlab eval-pack build\x1b[0m — ${projectName}\n`);
|
|
@@ -48,8 +53,8 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
} else if (subcommand === 'show') {
|
|
51
|
-
const evalId =
|
|
52
|
-
if (!evalId) throw
|
|
56
|
+
const evalId = positionals.find(a => a.startsWith('eval-'));
|
|
57
|
+
if (!evalId) throw inputError('INPUT_MISSING_ARGS', 'Usage: sdlab eval-pack show <eval-id>');
|
|
53
58
|
|
|
54
59
|
const manifest = await loadEvalPack(projectRoot, evalId);
|
|
55
60
|
console.log(`\x1b[1mEval pack\x1b[0m — ${evalId}\n`);
|
|
@@ -80,13 +85,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
} else {
|
|
83
|
-
throw
|
|
88
|
+
throw inputError('INPUT_BAD_SUBCOMMAND', `Unknown subcommand: ${subcommand}. Use: build, list, show`);
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
if (process.argv[1] && (process.argv[1].endsWith('eval-pack.js') || process.argv[1].endsWith('eval-pack'))) {
|
|
88
|
-
run().catch(
|
|
89
|
-
console.error(err.message || err);
|
|
90
|
-
process.exit(1);
|
|
91
|
-
});
|
|
93
|
+
run().catch(handleCliError);
|
|
92
94
|
}
|