@mcptoolshop/style-dataset-lab 2.2.1 → 3.0.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.es.md +76 -144
  3. package/README.fr.md +75 -143
  4. package/README.hi.md +75 -143
  5. package/README.it.md +76 -144
  6. package/README.ja.md +75 -143
  7. package/README.md +36 -8
  8. package/README.pt-BR.md +75 -143
  9. package/README.zh.md +76 -144
  10. package/SECURITY.md +3 -1
  11. package/bin/sdlab.js +146 -17
  12. package/lib/adapters/comfyui-runner.js +170 -0
  13. package/lib/adapters/comfyui-workflows.js +163 -0
  14. package/lib/batch-compiler.js +118 -0
  15. package/lib/batch-modes.js +111 -0
  16. package/lib/batch-runs.js +168 -0
  17. package/lib/batch-sheet-render.js +206 -0
  18. package/lib/brief-compiler.js +598 -0
  19. package/lib/brief-render.js +205 -0
  20. package/lib/critique-engine.js +413 -0
  21. package/lib/critique-render.js +171 -0
  22. package/lib/errors.js +82 -0
  23. package/lib/generated-provenance.js +87 -0
  24. package/lib/log.js +36 -0
  25. package/lib/paths.js +31 -0
  26. package/lib/refine-briefs.js +329 -0
  27. package/lib/reingest-selected.js +173 -0
  28. package/lib/run-summary.js +131 -0
  29. package/lib/runtime-runs.js +168 -0
  30. package/lib/selections.js +323 -0
  31. package/lib/snapshot.js +5 -2
  32. package/lib/split.js +5 -2
  33. package/lib/workflow-profiles.js +161 -0
  34. package/package.json +8 -6
  35. package/scripts/batch-generate.js +167 -0
  36. package/scripts/batch-sheet.js +46 -0
  37. package/scripts/batch-show.js +86 -0
  38. package/scripts/brief-compile.js +134 -0
  39. package/scripts/brief-show.js +52 -0
  40. package/scripts/critique-show.js +50 -0
  41. package/scripts/critique.js +70 -0
  42. package/scripts/doctor.js +169 -1
  43. package/scripts/init.js +22 -0
  44. package/scripts/refine.js +119 -0
  45. package/scripts/reingest-selected.js +78 -0
  46. package/scripts/reingest.js +7 -1
  47. package/scripts/run-generate.js +76 -0
  48. package/scripts/run-list.js +58 -0
  49. package/scripts/run-show.js +63 -0
  50. package/scripts/select.js +118 -0
  51. package/scripts/selection-show.js +88 -0
  52. package/scripts/snapshot.js +27 -23
  53. package/scripts/split.js +3 -1
  54. package/scripts/workflow-list.js +61 -0
  55. package/scripts/workflow-show.js +96 -0
  56. package/templates/domains/architecture/workflows/profiles/environment-moodboard.json +62 -0
  57. package/templates/domains/architecture/workflows/profiles/interior-variant-pack.json +60 -0
  58. package/templates/domains/character-design/workflows/profiles/character-portrait-set.json +61 -0
  59. package/templates/domains/character-design/workflows/profiles/expression-sheet.json +59 -0
  60. package/templates/domains/creature-design/workflows/profiles/creature-portrait-set.json +61 -0
  61. package/templates/domains/creature-design/workflows/profiles/silhouette-exploration.json +58 -0
  62. package/templates/domains/game-art/workflows/profiles/character-portrait-set.json +62 -0
  63. package/templates/domains/game-art/workflows/profiles/environment-moodboard.json +61 -0
  64. package/templates/domains/vehicle-mech/workflows/profiles/variant-pack.json +60 -0
  65. package/templates/domains/vehicle-mech/workflows/profiles/vehicle-silhouette-pack.json +59 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Critique renderer — converts critique report to human-readable formats.
3
+ *
4
+ * Reads like a working note, not a report card.
5
+ */
6
+
7
+ // ─── Fit badge ───────────────────────────────────────────────────
8
+
9
+ const FIT_BADGES = {
10
+ strong: '✓ strong',
11
+ usable: '○ usable',
12
+ 'off-model': '✗ off-model',
13
+ };
14
+
15
+ const FIT_EMOJI = {
16
+ strong: '🟢',
17
+ usable: '🟡',
18
+ 'off-model': '🔴',
19
+ };
20
+
21
+ // ─── Markdown renderer ──────────────────────────────────────────
22
+
23
+ /**
24
+ * Render a critique report as Markdown.
25
+ */
26
+ export function renderCritiqueMarkdown(report) {
27
+ const lines = [];
28
+
29
+ lines.push(`# Critique: ${report.run_id}`);
30
+ lines.push('');
31
+ lines.push(`**Brief:** ${report.brief_id}`);
32
+ lines.push(`**Workflow:** ${report.workflow_id}`);
33
+ lines.push(`**Project:** ${report.project_id}`);
34
+ if (report.subject_id) lines.push(`**Subject:** ${report.subject_id}`);
35
+ lines.push(`**Created:** ${report.created_at}`);
36
+ lines.push('');
37
+
38
+ // Recommended action
39
+ const action = report.recommended_action;
40
+ const modeLabel = {
41
+ accept_one: '✓ Accept one candidate',
42
+ refine_from_one: '→ Refine from one candidate',
43
+ rerun_broader: '↻ Rerun with broader parameters',
44
+ discard_run: '✗ Discard this run',
45
+ };
46
+ lines.push('## Recommended Action');
47
+ lines.push('');
48
+ lines.push(`**${modeLabel[action.mode] || action.mode}**`);
49
+ if (action.preferred_candidate) {
50
+ lines.push(`Preferred: **${action.preferred_candidate}**`);
51
+ }
52
+ if (action.reason) lines.push(`Reason: ${action.reason}`);
53
+ lines.push('');
54
+
55
+ // Active dimensions
56
+ lines.push('## Active Dimensions');
57
+ lines.push('');
58
+ for (const dim of report.active_dimensions) {
59
+ lines.push(`- ${dim.replace(/_/g, ' ')}`);
60
+ }
61
+ lines.push('');
62
+
63
+ // Candidates
64
+ lines.push('## Candidates');
65
+ lines.push('');
66
+
67
+ for (const c of report.candidates) {
68
+ const badge = FIT_EMOJI[c.overall_fit] || '?';
69
+ lines.push(`### ${badge} ${c.filename} (seed: ${c.seed ?? '?'})`);
70
+ lines.push('');
71
+ lines.push(`**Fit:** ${c.overall_fit}`);
72
+ lines.push('');
73
+
74
+ if (c.strengths.length > 0) {
75
+ lines.push('**Strengths:**');
76
+ for (const s of c.strengths) lines.push(`- ${s}`);
77
+ lines.push('');
78
+ }
79
+
80
+ if (c.drift_issues.length > 0) {
81
+ lines.push('**Drift Issues:**');
82
+ for (const d of c.drift_issues) lines.push(`- ⚠ ${d}`);
83
+ lines.push('');
84
+ }
85
+
86
+ if (c.preserve_next_pass.length > 0) {
87
+ lines.push('**Preserve Next Pass:**');
88
+ for (const p of c.preserve_next_pass) lines.push(`- ✓ ${p}`);
89
+ lines.push('');
90
+ }
91
+
92
+ if (c.correct_next_pass.length > 0) {
93
+ lines.push('**Correct Next Pass:**');
94
+ for (const x of c.correct_next_pass) lines.push(`- → ${x}`);
95
+ lines.push('');
96
+ }
97
+ }
98
+
99
+ // Next-pass direction summary
100
+ lines.push('## Next-Pass Direction');
101
+ lines.push('');
102
+
103
+ // Aggregate preserve/correct across all candidates
104
+ const allPreserve = new Set();
105
+ const allCorrect = new Set();
106
+ for (const c of report.candidates) {
107
+ for (const p of c.preserve_next_pass) allPreserve.add(p);
108
+ for (const x of c.correct_next_pass) allCorrect.add(x);
109
+ }
110
+
111
+ if (allPreserve.size > 0) {
112
+ lines.push('**Preserve:**');
113
+ for (const p of allPreserve) lines.push(`- ${p}`);
114
+ lines.push('');
115
+ }
116
+
117
+ if (allCorrect.size > 0) {
118
+ lines.push('**Correct:**');
119
+ for (const x of allCorrect) lines.push(`- ${x}`);
120
+ lines.push('');
121
+ }
122
+
123
+ return lines.join('\n');
124
+ }
125
+
126
+ // ─── Terminal text renderer ──────────────────────────────────────
127
+
128
+ /**
129
+ * Render a critique report as plain text for terminal display.
130
+ */
131
+ export function renderCritiqueText(report) {
132
+ const lines = [];
133
+
134
+ lines.push(`Critique: ${report.run_id}`);
135
+ lines.push(`Brief: ${report.brief_id} Workflow: ${report.workflow_id}`);
136
+ if (report.subject_id) lines.push(`Subject: ${report.subject_id}`);
137
+ lines.push('');
138
+
139
+ // Action
140
+ const action = report.recommended_action;
141
+ const actionIcon = {
142
+ accept_one: '✓',
143
+ refine_from_one: '→',
144
+ rerun_broader: '↻',
145
+ discard_run: '✗',
146
+ };
147
+ lines.push(`${actionIcon[action.mode] || '?'} ${action.mode.replace(/_/g, ' ')}`);
148
+ if (action.preferred_candidate) lines.push(` Preferred: ${action.preferred_candidate}`);
149
+ if (action.reason) lines.push(` ${action.reason}`);
150
+ lines.push('');
151
+
152
+ // Dimensions
153
+ lines.push(`Dimensions: ${report.active_dimensions.map(d => d.replace(/_/g, ' ')).join(', ')}`);
154
+ lines.push('');
155
+
156
+ // Candidates
157
+ for (const c of report.candidates) {
158
+ const badge = FIT_BADGES[c.overall_fit] || '? unknown';
159
+ lines.push(` ${badge} ${c.filename} (seed: ${c.seed ?? '?'})`);
160
+
161
+ if (c.strengths.length > 0) {
162
+ lines.push(` strengths: ${c.strengths.slice(0, 3).join('; ')}`);
163
+ }
164
+ if (c.drift_issues.length > 0) {
165
+ lines.push(` drift: ${c.drift_issues.slice(0, 3).join('; ')}`);
166
+ }
167
+ }
168
+ lines.push('');
169
+
170
+ return lines.join('\n');
171
+ }
package/lib/errors.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Structured error shape for Style Dataset Lab.
3
+ *
4
+ * Every user-facing error has: code, message, hint.
5
+ * Exit codes: 0 ok, 1 user error, 2 runtime error.
6
+ */
7
+
8
+ export class SdlabError extends Error {
9
+ /**
10
+ * @param {string} code — error code (e.g. INPUT_MISSING_FLAG)
11
+ * @param {string} message — human-readable description
12
+ * @param {Object} options
13
+ * @param {string} [options.hint] — actionable fix suggestion
14
+ * @param {Error} [options.cause] — original error
15
+ * @param {number} [options.exitCode] — 1 (user) or 2 (runtime)
16
+ */
17
+ constructor(code, message, { hint, cause, exitCode } = {}) {
18
+ super(message);
19
+ this.name = 'SdlabError';
20
+ this.code = code;
21
+ this.hint = hint || null;
22
+ this.cause = cause || null;
23
+ this.exitCode = exitCode || 1;
24
+ }
25
+
26
+ toJSON() {
27
+ return {
28
+ code: this.code,
29
+ message: this.message,
30
+ hint: this.hint,
31
+ };
32
+ }
33
+ }
34
+
35
+ // ─── Convenience constructors ──────────────────────────────────────
36
+
37
+ export function inputError(code, message, hint) {
38
+ return new SdlabError(code, message, { hint, exitCode: 1 });
39
+ }
40
+
41
+ export function runtimeError(code, message, hint, cause) {
42
+ return new SdlabError(code, message, { hint, cause, exitCode: 2 });
43
+ }
44
+
45
+ // ─── CLI error handler ─────────────────────────────────────────────
46
+
47
+ let debugMode = false;
48
+
49
+ export function enableDebug() {
50
+ debugMode = true;
51
+ }
52
+
53
+ export function isDebug() {
54
+ return debugMode;
55
+ }
56
+
57
+ /**
58
+ * Handle an error at the CLI boundary.
59
+ * Shows structured output by default, stack trace with --debug.
60
+ */
61
+ export function handleCliError(err) {
62
+ if (err instanceof SdlabError) {
63
+ console.error(`\x1b[31mError [${err.code}]:\x1b[0m ${err.message}`);
64
+ if (err.hint) {
65
+ console.error(`\x1b[33mHint:\x1b[0m ${err.hint}`);
66
+ }
67
+ if (debugMode && err.stack) {
68
+ console.error('\n' + err.stack);
69
+ }
70
+ if (debugMode && err.cause) {
71
+ console.error('\nCaused by:', err.cause);
72
+ }
73
+ process.exit(err.exitCode);
74
+ } else {
75
+ // Unstructured error — runtime failure
76
+ console.error(`\x1b[31mError:\x1b[0m ${err.message || err}`);
77
+ if (debugMode && err.stack) {
78
+ console.error('\n' + err.stack);
79
+ }
80
+ process.exit(2);
81
+ }
82
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Generated provenance builder — constructs the provenance block
3
+ * attached to records created from selected run/batch outputs.
4
+ *
5
+ * Normalizes source context (run vs batch) into a single shape
6
+ * that traces back through brief → workflow → seed → config.
7
+ */
8
+
9
+ import { readFileSync, existsSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { createHash } from 'node:crypto';
12
+
13
+ /**
14
+ * Build a generation provenance payload for a selected output.
15
+ *
16
+ * @param {Object} opts
17
+ * @param {'run'|'batch'} opts.sourceType
18
+ * @param {string} opts.sourceId — run_id or batch_id
19
+ * @param {Object} opts.runManifest — the run manifest containing this output
20
+ * @param {Object} [opts.batchManifest] — batch manifest if source is batch
21
+ * @param {Object} opts.item — the selection item (slot_or_output, filename, seed)
22
+ * @param {string} opts.projectRoot
23
+ * @returns {Object} generation_provenance block
24
+ */
25
+ export function buildGeneratedProvenance({
26
+ sourceType,
27
+ sourceId,
28
+ runManifest,
29
+ batchManifest,
30
+ item,
31
+ projectRoot,
32
+ }) {
33
+ const prov = {
34
+ source_type: sourceType,
35
+ source_id: sourceId,
36
+ run_id: runManifest.run_id,
37
+ brief_id: runManifest.brief_id,
38
+ workflow_id: runManifest.workflow_template_id,
39
+ selected_filename: item.filename,
40
+ generated_at: runManifest.created_at,
41
+ };
42
+
43
+ if (sourceType === 'batch' && batchManifest) {
44
+ prov.batch_id = batchManifest.batch_id;
45
+ }
46
+
47
+ // Seed from the item or from the run manifest outputs
48
+ if (item.seed !== undefined) {
49
+ prov.seed = item.seed;
50
+ } else if (runManifest.outputs) {
51
+ const match = runManifest.outputs.find(o => o.filename === item.filename);
52
+ if (match?.seed !== undefined) prov.seed = match.seed;
53
+ }
54
+
55
+ // Adapter target from run manifest
56
+ if (runManifest.adapter_target) {
57
+ prov.adapter_target = runManifest.adapter_target;
58
+ }
59
+
60
+ // Subject from run brief or batch manifest
61
+ if (batchManifest?.subject_id) {
62
+ prov.subject_id = batchManifest.subject_id;
63
+ } else {
64
+ // Try to read subject from brief
65
+ const briefPath = join(projectRoot, 'briefs', `${runManifest.brief_id}.json`);
66
+ if (existsSync(briefPath)) {
67
+ try {
68
+ const brief = JSON.parse(readFileSync(briefPath, 'utf-8'));
69
+ if (brief.subject_id) prov.subject_id = brief.subject_id;
70
+ if (brief.parent_brief_id) prov.parent_brief_id = brief.parent_brief_id;
71
+ } catch { /* skip */ }
72
+ }
73
+ }
74
+
75
+ // Config fingerprint — hash the run manifest for reproducibility tracing
76
+ prov.config_fingerprint = createHash('sha256')
77
+ .update(JSON.stringify({
78
+ brief_id: runManifest.brief_id,
79
+ workflow_template_id: runManifest.workflow_template_id,
80
+ adapter_target: runManifest.adapter_target,
81
+ seed_plan: runManifest.seed_plan,
82
+ }))
83
+ .digest('hex')
84
+ .slice(0, 16);
85
+
86
+ return prov;
87
+ }
package/lib/log.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Logging levels for CLI output.
3
+ *
4
+ * Levels: quiet < normal < verbose
5
+ * Set via --quiet or --verbose flags (parsed by caller).
6
+ */
7
+
8
+ let level = 'normal'; // quiet | normal | verbose
9
+
10
+ export function setLogLevel(l) {
11
+ level = l;
12
+ }
13
+
14
+ export function getLogLevel() {
15
+ return level;
16
+ }
17
+
18
+ /** Always shown (errors, final results). */
19
+ export function log(...args) {
20
+ console.log(...args);
21
+ }
22
+
23
+ /** Shown at normal and verbose levels (progress, summaries). */
24
+ export function info(...args) {
25
+ if (level !== 'quiet') console.log(...args);
26
+ }
27
+
28
+ /** Shown only at verbose level (per-record details, debug context). */
29
+ export function verbose(...args) {
30
+ if (level === 'verbose') console.log(...args);
31
+ }
32
+
33
+ /** Always shown on stderr. */
34
+ export function warn(...args) {
35
+ console.error('\x1b[33m⚠\x1b[0m', ...args);
36
+ }
package/lib/paths.js CHANGED
@@ -36,3 +36,34 @@ export function getProjectRoot(name) {
36
36
  export function resolveProjectPath(name, ...segments) {
37
37
  return join(getProjectRoot(name), ...segments);
38
38
  }
39
+
40
+ /**
41
+ * Return the workflow profiles directory for a project root.
42
+ * @param {string} projectRoot — absolute path to project
43
+ */
44
+ export function getWorkflowProfilesDir(projectRoot) {
45
+ return join(projectRoot, 'workflows', 'profiles');
46
+ }
47
+
48
+ /**
49
+ * Return the briefs directory for a project root.
50
+ * @param {string} projectRoot — absolute path to project
51
+ */
52
+ export function getBriefsDir(projectRoot) {
53
+ return join(projectRoot, 'briefs');
54
+ }
55
+
56
+ /**
57
+ * Return the runs directory for a project root.
58
+ * @param {string} projectRoot — absolute path to project
59
+ */
60
+ export function getRunsDir(projectRoot) {
61
+ return join(projectRoot, 'runs');
62
+ }
63
+
64
+ /**
65
+ * Return the runtime templates directory (repo-level).
66
+ */
67
+ export function getRuntimeDir() {
68
+ return join(REPO_ROOT, 'runtime');
69
+ }