← back to Wallco Ai
bad_examples registry: dedicated anti-pattern DB ('keep all bad/deleted so we never repeat the error') — backfilled 33,881 bad/deleted designs w/ full prompt+seed+generator provenance; reusable recordBad() helper; logged 11 agent-confirmed repeat-breaks (color-deltaE scan's blind spot)
d042eec882b4cb7167b47621293ac378d552371e · 2026-05-27 09:11:00 -0700 · Steve Abrams
Files touched
A scripts/bad-examples-schema.sqlA scripts/record-bad-example.js
Diff
commit d042eec882b4cb7167b47621293ac378d552371e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 27 09:11:00 2026 -0700
bad_examples registry: dedicated anti-pattern DB ('keep all bad/deleted so we never repeat the error') — backfilled 33,881 bad/deleted designs w/ full prompt+seed+generator provenance; reusable recordBad() helper; logged 11 agent-confirmed repeat-breaks (color-deltaE scan's blind spot)
---
scripts/bad-examples-schema.sql | 62 +++++++++++++++++++++++++++++++++++
scripts/record-bad-example.js | 71 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 133 insertions(+)
diff --git a/scripts/bad-examples-schema.sql b/scripts/bad-examples-schema.sql
new file mode 100644
index 0000000..e367160
--- /dev/null
+++ b/scripts/bad-examples-schema.sql
@@ -0,0 +1,62 @@
+-- bad_examples — permanent, isolated anti-pattern registry for wallco.ai.
+-- "Keep all bad/deleted items in its own db so we never create the same error."
+-- The negative mirror of the `elements` (good-reference) library.
+--
+-- Design rules:
+-- * ONE row per design_id. defect_tags accumulates every defect signal.
+-- * Stores the PROVENANCE that produced the error (prompt / negative_prompt /
+-- seed / generator / category / dims) so generation can learn the anti-pattern.
+-- * local_path is preserved — the bad FILE is never deleted; this row points to it.
+-- * Lives in dw_unified but is NEVER written to data/designs.json, so catalog
+-- rebuilds / publishes cannot clobber it. Append-and-merge only.
+
+CREATE TABLE IF NOT EXISTS bad_examples (
+ design_id BIGINT PRIMARY KEY,
+ defect_tags TEXT[] NOT NULL DEFAULT '{}', -- {edges_fail, user_removed, ghost, bleed, heal_derivative, repeat_break_lr, repeat_break_tb, ...}
+ severity INT, -- worst known severity 0-3
+ category TEXT,
+ prompt TEXT, -- the prompt that produced the bad design
+ negative_prompt TEXT,
+ seed BIGINT,
+ generator TEXT, -- replicate / comfy / pil-mid-heal / gemini-...
+ width_in NUMERIC,
+ height_in NUMERIC,
+ dominant_hex TEXT,
+ parent_design_id BIGINT,
+ local_path TEXT, -- preserved bad file (NEVER deleted)
+ scores JSONB, -- lens / structural scores when available
+ notes TEXT, -- original defect detail
+ source TEXT, -- backfill | repeat-break-scan | agent-audit | bulk-reject | edges-agent
+ design_created_at TIMESTAMPTZ,
+ recorded_at TIMESTAMPTZ DEFAULT now()
+);
+CREATE INDEX IF NOT EXISTS bad_examples_defect_idx ON bad_examples USING GIN(defect_tags);
+CREATE INDEX IF NOT EXISTS bad_examples_seed_idx ON bad_examples(seed);
+CREATE INDEX IF NOT EXISTS bad_examples_cat_idx ON bad_examples(category);
+CREATE INDEX IF NOT EXISTS bad_examples_gen_idx ON bad_examples(generator);
+
+-- Backfill from every currently-bad-or-deleted design, merging defect tags.
+INSERT INTO bad_examples
+ (design_id, defect_tags, category, prompt, negative_prompt, seed, generator,
+ width_in, height_in, dominant_hex, parent_design_id, local_path, notes, source, design_created_at)
+SELECT id,
+ ( ARRAY[]::text[]
+ || CASE WHEN user_removed THEN ARRAY['user_removed'] ELSE '{}'::text[] END
+ || CASE WHEN notes ILIKE '%EDGES_FAIL%' THEN ARRAY['edges_fail'] ELSE '{}'::text[] END
+ || CASE WHEN notes ILIKE '%EDGES_SCAN_ERROR%' THEN ARRAY['edges_scan_error'] ELSE '{}'::text[] END
+ || CASE WHEN notes ILIKE '%ghost%' THEN ARRAY['ghost'] ELSE '{}'::text[] END
+ || CASE WHEN notes ILIKE '%bleed%' THEN ARRAY['bleed'] ELSE '{}'::text[] END
+ || CASE WHEN notes ILIKE '%mid_heal%' OR notes ILIKE '%MID_HEALED%' OR generator ILIKE '%heal%'
+ THEN ARRAY['heal_derivative'] ELSE '{}'::text[] END
+ ),
+ category, prompt, negative_prompt, seed, generator,
+ width_in, height_in, dominant_hex, parent_design_id, local_path, notes, 'backfill', created_at
+FROM spoon_all_designs
+WHERE user_removed = TRUE
+ OR notes ILIKE '%EDGES_FAIL%' OR notes ILIKE '%EDGES_SCAN_ERROR%'
+ OR notes ILIKE '%ghost%' OR notes ILIKE '%bleed%'
+ OR notes ILIKE '%mid_heal%' OR notes ILIKE '%MID_HEALED%' OR generator ILIKE '%heal%'
+ON CONFLICT (design_id) DO UPDATE
+ SET defect_tags = (SELECT array_agg(DISTINCT t)
+ FROM unnest(bad_examples.defect_tags || EXCLUDED.defect_tags) AS t),
+ recorded_at = now();
diff --git a/scripts/record-bad-example.js b/scripts/record-bad-example.js
new file mode 100644
index 0000000..45e3189
--- /dev/null
+++ b/scripts/record-bad-example.js
@@ -0,0 +1,71 @@
+#!/usr/bin/env node
+/**
+ * record-bad-example — append/merge a design into the bad_examples anti-pattern
+ * registry, pulling its full provenance (prompt/seed/category/generator/dims)
+ * from spoon_all_designs. NEVER deletes the file; just records the row.
+ *
+ * "Keep all bad/deleted items in its own db so we never create the same error."
+ *
+ * Pipelines (edges-agent, bulk-reject, repeat-break-scan, ghost/bleed guards)
+ * should call recordBad() whenever they flag or delete a design.
+ *
+ * CLI: node scripts/record-bad-example.js --id 18924 --tags repeat_break_lr --sev 2 \
+ * --source agent-audit --note "cacti sliced at vertical center" [--scores '{...}']
+ * Lib: const { recordBad } = require('./record-bad-example');
+ * recordBad({ designId, tags:['repeat_break_lr'], severity:2, source, note, scores });
+ */
+'use strict';
+const { execFileSync } = require('child_process');
+
+function psql(sql) {
+ return execFileSync('psql', ['-d', 'dw_unified', '-tAc', sql], { encoding: 'utf8' }).trim();
+}
+function lit(v) {
+ if (v === null || v === undefined) return 'NULL';
+ return "'" + String(v).replace(/'/g, "''") + "'";
+}
+function pgArray(arr) {
+ return "ARRAY[" + arr.map(t => lit(t)).join(',') + "]::text[]";
+}
+
+function recordBad({ designId, tags, severity = null, source = 'manual', note = null, scores = null }) {
+ const id = parseInt(designId, 10);
+ if (!Number.isFinite(id)) throw new Error('designId required');
+ if (!Array.isArray(tags) || !tags.length) throw new Error('tags[] required');
+ const scoresLit = scores ? `${lit(JSON.stringify(scores))}::jsonb` : 'NULL';
+ const sevLit = severity == null ? 'NULL' : parseInt(severity, 10);
+ // Pull provenance from spoon_all_designs; merge defect tags on conflict; keep
+ // the worst severity; append the note.
+ const sql = `
+INSERT INTO bad_examples
+ (design_id, defect_tags, severity, category, prompt, negative_prompt, seed, generator,
+ width_in, height_in, dominant_hex, parent_design_id, local_path, scores, notes, source, design_created_at)
+SELECT s.id, ${pgArray(tags)}, ${sevLit}, s.category, s.prompt, s.negative_prompt, s.seed, s.generator,
+ s.width_in, s.height_in, s.dominant_hex, s.parent_design_id, s.local_path, ${scoresLit},
+ ${note ? lit(note) : 's.notes'}, ${lit(source)}, s.created_at
+FROM spoon_all_designs s WHERE s.id=${id}
+ON CONFLICT (design_id) DO UPDATE SET
+ defect_tags = (SELECT array_agg(DISTINCT t) FROM unnest(bad_examples.defect_tags || EXCLUDED.defect_tags) t),
+ severity = GREATEST(COALESCE(bad_examples.severity,0), COALESCE(EXCLUDED.severity,0)),
+ scores = COALESCE(EXCLUDED.scores, bad_examples.scores),
+ notes = COALESCE(EXCLUDED.notes, bad_examples.notes),
+ source = EXCLUDED.source,
+ recorded_at = now()
+RETURNING design_id;`;
+ const out = psql(sql);
+ return parseInt(out, 10) || id;
+}
+
+module.exports = { recordBad };
+
+if (require.main === module) {
+ const a = process.argv.slice(2);
+ const get = (k) => { const i = a.indexOf('--' + k); return i >= 0 ? a[i + 1] : null; };
+ const tags = (get('tags') || '').split(',').map(s => s.trim()).filter(Boolean);
+ let scores = null; try { if (get('scores')) scores = JSON.parse(get('scores')); } catch {}
+ const id = recordBad({
+ designId: get('id'), tags, severity: get('sev'),
+ source: get('source') || 'manual', note: get('note'), scores,
+ });
+ console.log('recorded bad_example design_id=' + id + ' tags=' + tags.join(','));
+}
← d8d8b1b repeat-break audit: agent vision pass over 36 published desi
·
back to Wallco Ai
·
edges-review: sort by status (removed/active first) + dynami 017a711 →