#!/usr/bin/env node /** * Dark Patterns Detector - Enforces inst_079 * Scans UI code for manipulative patterns */ const fs = require('fs'); function checkFile(filePath) { const content = fs.readFileSync(filePath, 'utf8'); const violations = []; const lines = content.split('\n'); const darkPatterns = [ { pattern: /confirm\s*\?\s*:\s*cancel/i, msg: 'Confirm shaming (makes cancel feel negative)' }, { pattern: /hidden|display:\s*none.*subscribe|newsletter/i, msg: 'Hidden subscription checkbox' }, { pattern: /setTimeout.*modal|popup/i, msg: 'Timed popup (interrupts user)' }, { pattern: /onbeforeunload.*subscribe|buy/i, msg: 'Exit popup manipulation' }, { pattern: /disabled.*unsubscribe|cancel/i, msg: 'Disabled unsubscribe/cancel button' } ]; lines.forEach((line, idx) => { darkPatterns.forEach(({ pattern, msg }) => { if (pattern.test(line)) { violations.push({ file: filePath, line: idx + 1, text: line.trim(), message: msg }); } }); }); return violations; } function main() { console.log('\nšŸŽØ Dark Patterns Detection (inst_079)\n'); const files = process.argv.slice(2); if (files.length === 0) { console.log('āœ… No files to scan\n'); process.exit(0); } const allViolations = []; files.forEach(file => { if (!fs.existsSync(file)) return; if (!file.match(/\.(html|js|ts)$/)) return; try { const violations = checkFile(file); allViolations.push(...violations); } catch (err) {} }); if (allViolations.length === 0) { console.log('āœ… No dark patterns detected\n'); process.exit(0); } console.log(`āŒ Found ${allViolations.length} dark pattern(s):\n`); allViolations.forEach(v => { console.log(`šŸ”“ ${v.file}:${v.line}`); console.log(` ${v.message}`); console.log(` ${v.text.substring(0, 60)}\n`); }); process.exit(1); } main();