@mcptoolshop/style-dataset-lab 3.3.0 → 3.5.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 +60 -0
- package/README.es.md +117 -55
- package/README.fr.md +125 -65
- package/README.hi.md +117 -68
- package/README.it.md +119 -58
- package/README.ja.md +124 -74
- package/README.md +73 -7
- package/README.pt-BR.md +128 -63
- package/README.zh.md +116 -65
- package/bin/sdlab.js +100 -4
- package/lib/adapters/ai-toolkit.js +224 -35
- package/lib/adapters/comfyui-runner.js +79 -26
- package/lib/adapters/comfyui-workflows.js +94 -11
- 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/asset-ingest.js +575 -0
- package/lib/asset-masks.js +323 -0
- package/lib/asset-source.js +1176 -0
- 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-author.js +350 -0
- package/lib/canon-build/build.js +146 -93
- package/lib/canon-review-gate.js +205 -0
- package/lib/captions.js +57 -0
- package/lib/card.js +74 -2
- package/lib/colab-launcher-guards.js +285 -0
- 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 +16 -3
- package/lib/freeze-stamp.js +28 -3
- package/lib/generated-provenance.js +194 -6
- package/lib/implementation-packs.js +95 -17
- package/lib/ingest.js +396 -0
- package/lib/judgment-provenance.js +220 -0
- package/lib/lane1-render.js +668 -0
- package/lib/measure.js +422 -0
- package/lib/memory-store.js +663 -0
- package/lib/model-licences.js +305 -0
- package/lib/npy-meta.js +130 -0
- package/lib/paths.js +196 -7
- package/lib/png-meta.js +512 -0
- package/lib/records.js +54 -8
- package/lib/reingest-selected.js +113 -17
- package/lib/reingest.js +27 -3
- package/lib/run-manifest.js +159 -25
- 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/train-runs.js +214 -0
- package/lib/training-manifests.js +114 -21
- package/lib/training-packages.js +147 -15
- package/lib/verifier.js +3 -0
- package/lib/verify-chain.js +367 -0
- package/lib/workflow-profiles.js +24 -3
- package/package.json +4 -3
- 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/asset-ingest.js +160 -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-draft.js +82 -0
- package/scripts/canon-drift.js +16 -7
- package/scripts/canon-freeze-status.js +14 -6
- package/scripts/canon-freeze.js +31 -11
- package/scripts/canon-unfreeze.js +17 -9
- package/scripts/card.js +2 -2
- package/scripts/colab/REAL_SERVICE_ASSUMPTIONS.md +14 -0
- package/scripts/colab/ai-toolkit-override-py315.txt +45 -0
- package/scripts/colab/colab_window.sh +587 -0
- package/scripts/colab/lane1_render.py +970 -0
- package/scripts/colab/launch_guards.py +564 -0
- package/scripts/colab/launch_lane1_stub.sh +309 -0
- package/scripts/colab/launch_lane3_stub.sh +366 -0
- package/scripts/colab/remote_runner.py +59 -0
- package/scripts/compare.js +2 -2
- package/scripts/critique.js +16 -1
- package/scripts/curate.js +2 -2
- package/scripts/doctor.js +182 -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 +86 -17
- 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/verify.js +67 -0
- package/scripts/workflow-list.js +2 -1
- package/scripts/workflow-show.js +2 -1
package/lib/snapshot.js
CHANGED
|
@@ -41,6 +41,96 @@ export function checkManifestVersion(manifest, kind) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* SDL-M10: atomically claim a fresh, unused SUBDIRECTORY of `parentDir`.
|
|
46
|
+
*
|
|
47
|
+
* The pattern this replaces — `existsSync(dir)` then throw-if-true,
|
|
48
|
+
* followed later by `mkdir(dir, {recursive:true})` — is check-then-act: a
|
|
49
|
+
* second concurrent process can create the same directory in the window
|
|
50
|
+
* between the check and the mkdir, and recursive mkdir never throws on an
|
|
51
|
+
* already-existing directory, so the race is silently invisible (two
|
|
52
|
+
* builders interleave writes into one snapshot/split/export/etc. dir —
|
|
53
|
+
* Law 1 exposure: "snapshots frozen").
|
|
54
|
+
*
|
|
55
|
+
* Mirrors lib/selections.js's claimSelectionId: a non-recursive `mkdir`
|
|
56
|
+
* is atomic at the filesystem level (POSIX and Windows both guarantee
|
|
57
|
+
* this) — it either creates the directory and returns, or fails with
|
|
58
|
+
* EEXIST if something else claimed that exact name first. On EEXIST we
|
|
59
|
+
* regenerate a fresh id and retry; for the timestamp+random ids used
|
|
60
|
+
* throughout this module, two consecutive real collisions is already
|
|
61
|
+
* vanishingly unlikely.
|
|
62
|
+
*
|
|
63
|
+
* Creating `parentDir` itself (e.g. "snapshots/") is NOT part of the race
|
|
64
|
+
* being closed here — two processes racing to create the same shared
|
|
65
|
+
* parent with {recursive:true} is harmless (both succeed; no caller's
|
|
66
|
+
* data lives directly in the parent, only under their own distinctly
|
|
67
|
+
* named leaf directory).
|
|
68
|
+
*
|
|
69
|
+
* @param {string} parentDir — parent directory, created recursively if missing
|
|
70
|
+
* @param {() => string} generateId — produces a candidate leaf id; called
|
|
71
|
+
* again on every retry so concurrent claimants converge on different ids
|
|
72
|
+
* @param {{code: string, message: string, hint: string, maxAttempts?: number}} onExhausted
|
|
73
|
+
* — error identity thrown if every attempt collides (kept caller-specific
|
|
74
|
+
* so existing codes like SNAPSHOT_ID_COLLISION stay externally stable)
|
|
75
|
+
* @returns {Promise<{id: string, dir: string}>}
|
|
76
|
+
*/
|
|
77
|
+
export async function claimIdDir(parentDir, generateId, onExhausted) {
|
|
78
|
+
const maxAttempts = onExhausted.maxAttempts ?? 25;
|
|
79
|
+
if (!existsSync(parentDir)) {
|
|
80
|
+
await mkdir(parentDir, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
83
|
+
const id = generateId();
|
|
84
|
+
const dir = join(parentDir, id);
|
|
85
|
+
try {
|
|
86
|
+
await mkdir(dir); // non-recursive — throws EEXIST if already claimed
|
|
87
|
+
return { id, dir };
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (err.code === 'EEXIST') continue;
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw inputError(onExhausted.code, onExhausted.message, onExhausted.hint);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* SDL-M10: atomically claim a fresh, unused FILE `<parentDir>/<id>.json`.
|
|
98
|
+
*
|
|
99
|
+
* Same TOCTOU problem as claimIdDir, but for the one artifact in this
|
|
100
|
+
* domain stored as a flat file rather than a directory (training
|
|
101
|
+
* manifests: `training/manifests/<id>.json`). The atomic primitive here
|
|
102
|
+
* is `writeFile(..., {flag: 'wx'})` — exclusive create, which fails with
|
|
103
|
+
* EEXIST rather than silently overwriting — so claim-and-write happen in
|
|
104
|
+
* one step instead of racing a separate existsSync check against it.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} parentDir — parent directory, created recursively if missing
|
|
107
|
+
* @param {() => string} generateId — produces a candidate id; called again
|
|
108
|
+
* on every retry
|
|
109
|
+
* @param {(id: string) => string} buildContent — renders the file content
|
|
110
|
+
* for a given candidate id (called fresh per attempt since the content
|
|
111
|
+
* embeds the id)
|
|
112
|
+
* @param {{code: string, message: string, hint: string, maxAttempts?: number}} onExhausted
|
|
113
|
+
* @returns {Promise<{id: string, filePath: string}>}
|
|
114
|
+
*/
|
|
115
|
+
export async function claimIdFile(parentDir, generateId, buildContent, onExhausted) {
|
|
116
|
+
const maxAttempts = onExhausted.maxAttempts ?? 25;
|
|
117
|
+
if (!existsSync(parentDir)) {
|
|
118
|
+
await mkdir(parentDir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
121
|
+
const id = generateId();
|
|
122
|
+
const filePath = join(parentDir, `${id}.json`);
|
|
123
|
+
try {
|
|
124
|
+
await writeFile(filePath, buildContent(id), { flag: 'wx' }); // exclusive create — atomic
|
|
125
|
+
return { id, filePath };
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (err.code === 'EEXIST') continue;
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
throw inputError(onExhausted.code, onExhausted.message, onExhausted.hint);
|
|
132
|
+
}
|
|
133
|
+
|
|
44
134
|
/**
|
|
45
135
|
* Derive which rule names were evaluated against a record for a given profile.
|
|
46
136
|
* Mirrors the order of checks in evaluateEligibility() so the trace is stable.
|
|
@@ -101,18 +191,6 @@ export function computeConfigFingerprint(projectRoot) {
|
|
|
101
191
|
export async function createSnapshot(projectRoot, profile, options = {}) {
|
|
102
192
|
const config = loadProjectConfig(projectRoot);
|
|
103
193
|
const recordsDir = join(projectRoot, 'records');
|
|
104
|
-
const snapshotId = generateSnapshotId();
|
|
105
|
-
const snapshotDir = join(projectRoot, 'snapshots', snapshotId);
|
|
106
|
-
|
|
107
|
-
// D-005: refuse to proceed if the generated ID has collided with an existing
|
|
108
|
-
// snapshot dir — silent overwrite would break Law 1 (snapshots are frozen).
|
|
109
|
-
if (!options.dryRun && existsSync(snapshotDir)) {
|
|
110
|
-
throw inputError(
|
|
111
|
-
'SNAPSHOT_ID_COLLISION',
|
|
112
|
-
`Snapshot directory already exists: ${snapshotDir}`,
|
|
113
|
-
'Retry to generate a fresh snapshot ID.',
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
194
|
|
|
117
195
|
const configFingerprint = computeConfigFingerprint(projectRoot);
|
|
118
196
|
const profileId = profile.profile_id || profile.id || 'unknown';
|
|
@@ -147,7 +225,7 @@ export async function createSnapshot(projectRoot, profile, options = {}) {
|
|
|
147
225
|
continue;
|
|
148
226
|
}
|
|
149
227
|
|
|
150
|
-
const { eligible, reasons } = evaluateEligibility(
|
|
228
|
+
const { eligible, reasons, reason_codes: reasonCodes } = evaluateEligibility(
|
|
151
229
|
record, profile, config.lanes, config.terminology
|
|
152
230
|
);
|
|
153
231
|
|
|
@@ -181,7 +259,13 @@ export async function createSnapshot(projectRoot, profile, options = {}) {
|
|
|
181
259
|
}
|
|
182
260
|
}
|
|
183
261
|
} else {
|
|
184
|
-
|
|
262
|
+
// F5: forward the stable reason_codes alongside the human-readable
|
|
263
|
+
// reasons — purely additive on excluded.jsonl's on-disk shape (one
|
|
264
|
+
// new key), so old readers/old files that only expect
|
|
265
|
+
// {record_id, reasons} are unaffected. Lets categorizeExclusions
|
|
266
|
+
// (lib/eligibility.js) categorize this snapshot's exclusions by
|
|
267
|
+
// code instead of prose-matching `reasons`.
|
|
268
|
+
excluded.push({ record_id: record.id, reasons, reason_codes: reasonCodes });
|
|
185
269
|
}
|
|
186
270
|
}
|
|
187
271
|
|
|
@@ -197,9 +281,24 @@ export async function createSnapshot(projectRoot, profile, options = {}) {
|
|
|
197
281
|
|
|
198
282
|
// Write snapshot files (skip if dry run)
|
|
199
283
|
if (options.dryRun) {
|
|
284
|
+
// Dry run never touches disk, so there is nothing to atomically claim —
|
|
285
|
+
// a fresh preview id is enough to characterize the shape of the result.
|
|
286
|
+
const snapshotId = generateSnapshotId();
|
|
200
287
|
return { snapshotId, included: included.length, excluded: excluded.length, unreadable: unreadable.length };
|
|
201
288
|
}
|
|
202
|
-
|
|
289
|
+
|
|
290
|
+
// SDL-M10: atomically claim the snapshot id/dir — closes the
|
|
291
|
+
// check-then-act race the old existsSync+recursive-mkdir pattern had
|
|
292
|
+
// (D-005's intent, now actually race-proof; see claimIdDir above).
|
|
293
|
+
const { id: snapshotId, dir: snapshotDir } = await claimIdDir(
|
|
294
|
+
join(projectRoot, 'snapshots'),
|
|
295
|
+
generateSnapshotId,
|
|
296
|
+
{
|
|
297
|
+
code: 'SNAPSHOT_ID_COLLISION',
|
|
298
|
+
message: 'Could not claim a unique snapshot ID — every candidate collided with an existing snapshot directory.',
|
|
299
|
+
hint: 'Retry; if this persists, another sdlab process may be creating snapshots in a tight loop.',
|
|
300
|
+
},
|
|
301
|
+
);
|
|
203
302
|
|
|
204
303
|
const manifest = {
|
|
205
304
|
// DB-001 / DB-006: stamp manifest schema version (reader checks on load)
|