← back to Designerwallcoverings
scripts/bannedword-body-leak/bannedword-body-leak.mjs
86 lines
// bannedword-body-leak — pull the live body_html for the AI-preamble-leaking
// products (from the 2026-06-15 banned-word audit), classify the leak, and PREVIEW
// the strip transform. READ-ONLY (GraphQL GET of descriptionHtml). No writes. $0.
//
// Leak classes:
// md_fence — markdown code fences wrapping the body (```html ... ```)
// ai_preamble — leading "Sure, here is..." / "Certainly!" / "As an AI..." etc.
// ai_trailing — trailing "Let me know if...", "I hope this helps"
// Strip transform (previewed, not applied): drop fences + leading/trailing AI lines.
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
const TOKEN = (fs.readFileSync(`${process.env.HOME}/Projects/secrets-manager/.env`, 'utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1]?.trim();
const HOST = 'designer-laboratory-sandbox.myshopify.com', API = '2024-10';
const AUDIT = `${process.env.HOME}/.claude/yolo-queue/banned-word-live-audit-2026-06-15.json`;
const OUT = `${process.env.HOME}/.claude/yolo-queue/bannedword-body-leak-2026-06-16.json`;
const MD = `${process.env.HOME}/.claude/yolo-queue/bannedword-body-leak-2026-06-16.md`;
const ids = JSON.parse(fs.readFileSync(AUDIT, 'utf8')).samples.preamble.map(p => `gid://shopify/Product/${p.id}`);
async function gql(query) {
const res = await fetch(`https://${HOST}/admin/api/${API}/graphql.json`, {
method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
});
return (await res.json()).data;
}
// strip transform
const FENCE = /^\s*```(?:html|json|markdown)?\s*$|```/gim;
const PREAMBLE = /^\s*(?:sure|certainly|absolutely|of course|here(?:'s| is)(?: the| a)?|below is|i'd be happy to|i would be happy to|as an ai|as a language model|i cannot|i can't|i apologize|okay|ok)\b[^.\n]*[.:!]?\s*/i;
const TRAILING = /\s*(?:let me know if[^.\n]*\.?|i hope this helps[^.\n]*\.?|feel free to[^.\n]*\.?|hope (?:this|that) helps[^.\n]*\.?)\s*$/i;
function classify(body) {
const cls = [];
if (/```/.test(body)) cls.push('md_fence');
if (PREAMBLE.test(body.replace(/<[^>]+>/g, '').trimStart())) cls.push('ai_preamble');
if (TRAILING.test(body.replace(/<[^>]+>/g, '').trimEnd())) cls.push('ai_trailing');
return cls;
}
function strip(body) {
let s = body.replace(FENCE, '');
// strip leading preamble from the visible text (after any opening tag)
s = s.replace(/^(\s*(?:<p>|<div>|<span>)?\s*)/i, (m) => m).replace(PREAMBLE, '');
s = s.replace(TRAILING, '');
return s.trim();
}
(async () => {
const out = [];
for (let i = 0; i < ids.length; i += 40) {
const chunk = ids.slice(i, i + 40);
const d = await gql(`{ nodes(ids: [${chunk.map(x => `"${x}"`).join(',')}]) { ... on Product { id title vendor descriptionHtml } } }`);
for (const n of (d?.nodes || [])) {
if (!n) continue;
const body = n.descriptionHtml || '';
const cls = classify(body);
const after = strip(body);
out.push({ id: n.id.split('/').pop(), vendor: n.vendor, title: n.title, classes: cls,
before_len: body.length, after_len: after.length, removed_chars: body.length - after.length,
before_head: body.slice(0, 160), after_head: after.slice(0, 160) });
}
}
const byClass = out.reduce((m, r) => { for (const c of (r.classes.length ? r.classes : ['none'])) m[c] = (m[c]||0)+1; return m; }, {});
const byVendor = out.reduce((m, r) => (m[r.vendor] = (m[r.vendor]||0)+1, m), {});
const report = { generated_at: new Date().toISOString(), source: 'live Shopify descriptionHtml (READ-ONLY)', total: out.length, by_class: byClass, by_vendor: byVendor, products: out };
fs.writeFileSync(OUT, JSON.stringify(report, null, 2));
let md = `# Banned-word body-leak (AI preamble) — live cleanup list + strip preview\n\n`;
md += `**$0, READ-ONLY** (pulled live descriptionHtml for the ${out.length} flagged products). No writes.\n\n`;
md += `## Leak classes\n${Object.entries(byClass).map(([k,v])=>`- **${k}**: ${v}`).join('\n')}\n\n`;
md += `## By vendor\n${Object.entries(byVendor).sort((a,b)=>b[1]-a[1]).map(([k,v])=>`- ${k}: ${v}`).join('\n')}\n\n`;
md += `## Strip transform (previewed, GATED to apply)\n`;
md += `1. remove markdown code fences (\\\`\\\`\\\`html … \\\`\\\`\\\`)\n2. strip leading AI preamble ("Sure, here is…", "Certainly!", "As an AI…")\n3. strip trailing AI notes ("Let me know if…", "I hope this helps")\n\n`;
md += `## Before → after (sample)\n`;
for (const r of out.filter(r => r.classes.length).slice(0, 15)) {
md += `\n**${r.vendor} · ${r.title}** (id ${r.id}, classes: ${r.classes.join(',')}, −${r.removed_chars} chars)\n`;
md += `- before: \`${r.before_head.replace(/\n/g,' ')}\`\n`;
md += `- after: \`${r.after_head.replace(/\n/g,' ')}\`\n`;
}
md += `\n_Full list + per-product before/after in the JSON. Apply via shopify_api_queue {id, body_html:<stripped>} — GATED, Steve approves._\n`;
fs.writeFileSync(MD, md);
console.log(`[body-leak] ${out.length} products | classes: ${JSON.stringify(byClass)}`);
console.log(`Report: ${MD}`);
})().catch(e => { console.error('FATAL', e.message); process.exit(1); });