diff --git a/.claude/skills/critique/scripts/detect-antipatterns-browser.js b/.claude/skills/critique/scripts/detect-antipatterns-browser.js index c7e9b3f98..3e4343f46 100644 --- a/.claude/skills/critique/scripts/detect-antipatterns-browser.js +++ b/.claude/skills/critique/scripts/detect-antipatterns-browser.js @@ -341,26 +341,27 @@ function checkColors(opts) { // Tailwind class checks if (classList) { - if (/\bbg-black\b/.test(classList)) { + const classStr = typeof classList === 'string' ? classList : Array.from(classList).join(' '); + if (/\bbg-black\b/.test(classStr)) { findings.push({ id: 'pure-black-white', snippet: 'bg-black' }); } - const grayMatch = classList.match(/\btext-(?:gray|slate|zinc|neutral|stone)-\d+\b/); - const colorBgMatch = classList.match(/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/); + const grayMatch = classStr.match(/\btext-(?:gray|slate|zinc|neutral|stone)-\d+\b/); + const colorBgMatch = classStr.match(/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/); if (grayMatch && colorBgMatch) { findings.push({ id: 'gray-on-color', snippet: `${grayMatch[0]} on ${colorBgMatch[0]}` }); } - if (/\bbg-clip-text\b/.test(classList) && /\bbg-gradient-to-/.test(classList)) { + if (/\bbg-clip-text\b/.test(classStr) && /\bbg-gradient-to-/.test(classStr)) { findings.push({ id: 'gradient-text', snippet: 'bg-clip-text + bg-gradient (Tailwind)' }); } - const purpleText = classList.match(/\btext-(?:purple|violet|indigo)-\d+\b/); - if (purpleText && (['h1', 'h2', 'h3'].includes(tag) || /\btext-(?:[2-9]xl)\b/.test(classList))) { + const purpleText = classStr.match(/\btext-(?:purple|violet|indigo)-\d+\b/); + if (purpleText && (['h1', 'h2', 'h3'].includes(tag) || /\btext-(?:[2-9]xl)\b/.test(classStr))) { findings.push({ id: 'ai-color-palette', snippet: `${purpleText[0]} on heading` }); } - if (/\bfrom-(?:purple|violet|indigo)-\d+\b/.test(classList) && /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(classList)) { + if (/\bfrom-(?:purple|violet|indigo)-\d+\b/.test(classStr) && /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(classStr)) { findings.push({ id: 'ai-color-palette', snippet: 'Purple/violet gradient (Tailwind)' }); } } @@ -1395,9 +1396,11 @@ if (IS_BROWSER) { } } + const pageLevelFindings = []; + const typoFindings = checkTypography(); if (typoFindings.length > 0) { - showPageBanner(typoFindings); + pageLevelFindings.push(...typoFindings); allFindings.push({ el: document.body, findings: typoFindings }); } @@ -1412,7 +1415,7 @@ if (IS_BROWSER) { // Page-level quality checks (headings, etc.) const qualityFindings = checkPageQualityDOM(); if (qualityFindings.length > 0) { - showPageBanner(qualityFindings); + pageLevelFindings.push(...qualityFindings); allFindings.push({ el: document.body, findings: qualityFindings }); } @@ -1420,10 +1423,14 @@ if (IS_BROWSER) { const htmlPatternFindings = checkHtmlPatterns(document.documentElement.outerHTML); if (htmlPatternFindings.length > 0) { const mapped = htmlPatternFindings.map(f => ({ type: f.id, detail: f.snippet })); - showPageBanner(mapped); + pageLevelFindings.push(...mapped); allFindings.push({ el: document.body, findings: mapped }); } + if (pageLevelFindings.length > 0) { + showPageBanner(pageLevelFindings); + } + printSummary(allFindings); return allFindings; }; diff --git a/README.md b/README.md index db6d411b7..6989234ce 100644 --- a/README.md +++ b/README.md @@ -150,10 +150,10 @@ Most commands accept an optional argument to focus on a specific area: Impeccable includes a standalone CLI for detecting anti-patterns without an AI harness: ```bash -npx impeccable detect src/ # scan a directory -npx impeccable detect index.html # scan an HTML file -npx impeccable detect https://example.com # scan a URL (Puppeteer) -npx impeccable detect --fast --json . # regex-only, JSON output +bun bin/impeccable.mjs detect src/ # scan a directory +bun bin/impeccable.mjs detect index.html # scan an HTML file +bun bin/impeccable.mjs detect https://example.com # scan a URL (Puppeteer) +bun bin/impeccable.mjs detect --fast --json . # regex-only, JSON output ``` The detector catches 25 issues across AI slop (side-tab borders, purple gradients, bounce easing, dark glows) and general design quality (line length, cramped padding, small touch targets, skipped headings, and more). diff --git a/source/skills/critique/scripts/detect-antipatterns.mjs b/source/skills/critique/scripts/detect-antipatterns.mjs index 44659a9cd..1675c9cf7 100644 --- a/source/skills/critique/scripts/detect-antipatterns.mjs +++ b/source/skills/critique/scripts/detect-antipatterns.mjs @@ -339,26 +339,27 @@ function checkColors(opts) { // Tailwind class checks if (classList) { - if (/\bbg-black\b/.test(classList)) { + const classStr = typeof classList === 'string' ? classList : Array.from(classList).join(' '); + if (/\bbg-black\b/.test(classStr)) { findings.push({ id: 'pure-black-white', snippet: 'bg-black' }); } - const grayMatch = classList.match(/\btext-(?:gray|slate|zinc|neutral|stone)-\d+\b/); - const colorBgMatch = classList.match(/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/); + const grayMatch = classStr.match(/\btext-(?:gray|slate|zinc|neutral|stone)-\d+\b/); + const colorBgMatch = classStr.match(/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/); if (grayMatch && colorBgMatch) { findings.push({ id: 'gray-on-color', snippet: `${grayMatch[0]} on ${colorBgMatch[0]}` }); } - if (/\bbg-clip-text\b/.test(classList) && /\bbg-gradient-to-/.test(classList)) { + if (/\bbg-clip-text\b/.test(classStr) && /\bbg-gradient-to-/.test(classStr)) { findings.push({ id: 'gradient-text', snippet: 'bg-clip-text + bg-gradient (Tailwind)' }); } - const purpleText = classList.match(/\btext-(?:purple|violet|indigo)-\d+\b/); - if (purpleText && (['h1', 'h2', 'h3'].includes(tag) || /\btext-(?:[2-9]xl)\b/.test(classList))) { + const purpleText = classStr.match(/\btext-(?:purple|violet|indigo)-\d+\b/); + if (purpleText && (['h1', 'h2', 'h3'].includes(tag) || /\btext-(?:[2-9]xl)\b/.test(classStr))) { findings.push({ id: 'ai-color-palette', snippet: `${purpleText[0]} on heading` }); } - if (/\bfrom-(?:purple|violet|indigo)-\d+\b/.test(classList) && /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(classList)) { + if (/\bfrom-(?:purple|violet|indigo)-\d+\b/.test(classStr) && /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(classStr)) { findings.push({ id: 'ai-color-palette', snippet: 'Purple/violet gradient (Tailwind)' }); } } @@ -1393,9 +1394,11 @@ if (IS_BROWSER) { } } + const pageLevelFindings = []; + const typoFindings = checkTypography(); if (typoFindings.length > 0) { - showPageBanner(typoFindings); + pageLevelFindings.push(...typoFindings); allFindings.push({ el: document.body, findings: typoFindings }); } @@ -1410,7 +1413,7 @@ if (IS_BROWSER) { // Page-level quality checks (headings, etc.) const qualityFindings = checkPageQualityDOM(); if (qualityFindings.length > 0) { - showPageBanner(qualityFindings); + pageLevelFindings.push(...qualityFindings); allFindings.push({ el: document.body, findings: qualityFindings }); } @@ -1418,10 +1421,14 @@ if (IS_BROWSER) { const htmlPatternFindings = checkHtmlPatterns(document.documentElement.outerHTML); if (htmlPatternFindings.length > 0) { const mapped = htmlPatternFindings.map(f => ({ type: f.id, detail: f.snippet })); - showPageBanner(mapped); + pageLevelFindings.push(...mapped); allFindings.push({ el: document.body, findings: mapped }); } + if (pageLevelFindings.length > 0) { + showPageBanner(pageLevelFindings); + } + printSummary(allFindings); return allFindings; };