@triedotdev/mcp 1.0.36 → 1.0.37

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.
@@ -1074,6 +1074,126 @@ Output STRICT JSON:
1074
1074
  };
1075
1075
 
1076
1076
  // src/agents/design-engineer.ts
1077
+ var DOMAIN_DESIGN_RULES = {
1078
+ fitness: {
1079
+ defaultMode: "dark",
1080
+ accentSuggestions: ["orange", "tomato", "amber"],
1081
+ // energetic - from Radix
1082
+ typographyVibe: "bold-condensed",
1083
+ motionLevel: "high",
1084
+ uiDensity: "spacious",
1085
+ keyPatterns: ["progress-rings", "stat-cards", "workout-timers", "streak-badges"],
1086
+ avoid: ["passive voice", "too much text", "busy layouts"],
1087
+ reference: ["Strava", "Nike Training Club", "Peloton"]
1088
+ },
1089
+ fintech: {
1090
+ defaultMode: "light",
1091
+ accentSuggestions: ["sky", "teal", "grass"],
1092
+ // trust + growth - from Radix
1093
+ typographyVibe: "clean-professional",
1094
+ motionLevel: "subtle",
1095
+ uiDensity: "balanced",
1096
+ keyPatterns: ["account-cards", "transaction-lists", "charts", "security-indicators"],
1097
+ avoid: ["flashy animations", "informal language", "unclear numbers"],
1098
+ reference: ["Stripe Dashboard", "Mercury", "Ramp"]
1099
+ },
1100
+ creativeTools: {
1101
+ defaultMode: "dark",
1102
+ accentSuggestions: ["violet", "pink", "sky"],
1103
+ // creative - from Radix
1104
+ typographyVibe: "minimal-clean",
1105
+ motionLevel: "medium",
1106
+ uiDensity: "minimal-chrome",
1107
+ keyPatterns: ["canvas-first", "contextual-toolbars", "layers-panel"],
1108
+ avoid: ["distracting UI", "modal overload", "complex menus"],
1109
+ reference: ["Figma", "Linear", "Notion"]
1110
+ },
1111
+ ecommerce: {
1112
+ defaultMode: "light",
1113
+ accentSuggestions: ["tomato", "pink", "amber"],
1114
+ // action-oriented - from Radix
1115
+ typographyVibe: "readable-hierarchy",
1116
+ motionLevel: "subtle",
1117
+ uiDensity: "scannable",
1118
+ keyPatterns: ["product-cards", "filters", "cart-drawer", "trust-badges"],
1119
+ avoid: ["slow load", "hidden prices", "complex checkout"],
1120
+ reference: ["Shopify themes", "Apple Store", "Glossier"]
1121
+ },
1122
+ dashboard: {
1123
+ defaultMode: "light",
1124
+ accentSuggestions: ["blue", "indigo", "cyan"],
1125
+ // professional - from Radix
1126
+ typographyVibe: "clean-readable",
1127
+ motionLevel: "subtle",
1128
+ uiDensity: "balanced",
1129
+ keyPatterns: ["data-tables", "charts", "stat-cards", "filters", "navigation"],
1130
+ avoid: ["excessive animations", "low contrast", "information overload"],
1131
+ reference: ["Linear", "Vercel Dashboard", "Stripe Dashboard"]
1132
+ },
1133
+ marketing: {
1134
+ defaultMode: "light",
1135
+ accentSuggestions: ["blue", "violet", "crimson"],
1136
+ // attention-grabbing
1137
+ typographyVibe: "bold-impactful",
1138
+ motionLevel: "high",
1139
+ uiDensity: "spacious",
1140
+ keyPatterns: ["hero-sections", "feature-grids", "testimonials", "CTAs", "pricing-tables"],
1141
+ avoid: ["walls of text", "weak CTAs", "generic stock photos"],
1142
+ reference: ["Linear", "Vercel", "Stripe", "Raycast"]
1143
+ },
1144
+ saas: {
1145
+ defaultMode: "light",
1146
+ accentSuggestions: ["blue", "indigo", "violet"],
1147
+ typographyVibe: "clean-professional",
1148
+ motionLevel: "medium",
1149
+ uiDensity: "balanced",
1150
+ keyPatterns: ["dashboards", "settings", "onboarding", "feature-flags"],
1151
+ avoid: ["complex navigation", "unclear value props"],
1152
+ reference: ["Linear", "Notion", "Slack"]
1153
+ },
1154
+ portfolio: {
1155
+ defaultMode: "dark",
1156
+ accentSuggestions: ["slate", "mauve", "sage"],
1157
+ // premium/minimal
1158
+ typographyVibe: "expressive-editorial",
1159
+ motionLevel: "high",
1160
+ uiDensity: "spacious",
1161
+ keyPatterns: ["case-studies", "image-galleries", "project-grids", "about-sections"],
1162
+ avoid: ["cluttered layouts", "generic templates", "slow loading"],
1163
+ reference: ["Awwwards winners", "Codrops demos"]
1164
+ }
1165
+ };
1166
+ var SPACING_TOKENS = {
1167
+ scale: [0, 1, 2, 4, 6, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96],
1168
+ allowedPx: [0, 1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96, 128]
1169
+ };
1170
+ var AI_SLOP_BLOCKERS = {
1171
+ colors: {
1172
+ rejectPureSaturated: /^#(ff0000|00ff00|0000ff|ffff00|ff00ff|00ffff)$/i,
1173
+ rejectNeonGreen: /^#[0-4][eEfF][0-5][0-4][0-2][0-4]$/,
1174
+ rejectHotMagenta: /^#[fF][0-3][0-5][fF][0-3][fF]$/,
1175
+ maxSaturation: 80,
1176
+ minSurfaceLightnessDelta: 8
1177
+ },
1178
+ typography: {
1179
+ rejectGenericStack: /^(-apple-system|BlinkMacSystemFont|"Segoe UI"|Roboto|sans-serif)$/,
1180
+ minDistinctWeights: 3,
1181
+ allowedSizes: [12, 14, 16, 18, 20, 24, 30, 36, 48, 60, 72]
1182
+ },
1183
+ spacing: {
1184
+ allowedValues: SPACING_TOKENS.allowedPx,
1185
+ rejectMagicNumbers: true
1186
+ },
1187
+ accents: {
1188
+ maxAccentHues: 1,
1189
+ semanticExempt: ["success", "warning", "error", "info"]
1190
+ },
1191
+ contrast: {
1192
+ textOnBackground: 4.5,
1193
+ largeTextOnBackground: 3,
1194
+ uiComponents: 3
1195
+ }
1196
+ };
1077
1197
  var AI_SLOP_COLORS = [
1078
1198
  // Neon/toxic greens (AI loves these for "success")
1079
1199
  { pattern: /#[0-4][eEfF][0-5][0-4][0-2][0-4]/i, name: "toxic neon green", suggestion: "emerald-500 (#10b981) or green-600 (#16a34a)" },
@@ -1183,6 +1303,7 @@ var DesignEngineerAgent = class extends BaseAgent {
1183
1303
  const allContent = await this.getAllContent(files);
1184
1304
  const projectContext = this.detectProjectContext(allContent);
1185
1305
  const hasMotionLibrary = this.detectMotionLibraries(allContent);
1306
+ const designContext = this.detectDesignContext(allContent);
1186
1307
  for (const file of files) {
1187
1308
  try {
1188
1309
  const content = await this.readFile(file);
@@ -1194,11 +1315,30 @@ var DesignEngineerAgent = class extends BaseAgent {
1194
1315
  issues.push(...this.analyzeVisualCraft(content, file));
1195
1316
  issues.push(...this.analyzeModernCSS(content, file));
1196
1317
  issues.push(...this.analyzePerformance(content, file));
1318
+ issues.push(...this.detectAISlopPatterns(content, file));
1319
+ issues.push(...this.analyzeTypographyPatterns(content, file));
1320
+ issues.push(...this.analyzeContrastRatios(content, file));
1321
+ issues.push(...this.analyzeSpacingGrid(content, file));
1197
1322
  }
1198
1323
  } catch (error) {
1199
1324
  console.error(`Design Engineer Agent: Error reading file ${file}:`, error);
1200
1325
  }
1201
1326
  }
1327
+ const healthScore = this.calculateDesignHealthScore(issues);
1328
+ if (healthScore.score < 80 || healthScore.slopScore < 70) {
1329
+ const domainRecommendation = this.getDomainRecommendations(designContext);
1330
+ issues.push(this.createIssue(
1331
+ this.generateIssueId(),
1332
+ healthScore.score < 50 ? "serious" : "moderate",
1333
+ `Design Health Score: ${healthScore.score}/100 | Slop Score: ${healthScore.slopScore}/100`,
1334
+ `Breakdown: Token adoption ${healthScore.breakdown.tokenAdoption}%, Contrast ${healthScore.breakdown.contrastCompliance}%, Spacing ${healthScore.breakdown.spacingConsistency}%, Typography ${healthScore.breakdown.typographySystem}%, Surface hierarchy ${healthScore.breakdown.surfaceHierarchy}%. ${domainRecommendation}`,
1335
+ files[0] || "project",
1336
+ void 0,
1337
+ 0.95,
1338
+ void 0,
1339
+ false
1340
+ ));
1341
+ }
1202
1342
  return issues;
1203
1343
  }
1204
1344
  /**
@@ -1368,9 +1508,9 @@ var DesignEngineerAgent = class extends BaseAgent {
1368
1508
  hexToRgb(hex) {
1369
1509
  const normalized = hex.replace("#", "");
1370
1510
  if (normalized.length === 3) {
1371
- const r = parseInt(normalized[0] + normalized[0], 16);
1372
- const g = parseInt(normalized[1] + normalized[1], 16);
1373
- const b = parseInt(normalized[2] + normalized[2], 16);
1511
+ const r = parseInt(normalized.charAt(0) + normalized.charAt(0), 16);
1512
+ const g = parseInt(normalized.charAt(1) + normalized.charAt(1), 16);
1513
+ const b = parseInt(normalized.charAt(2) + normalized.charAt(2), 16);
1374
1514
  return { r, g, b };
1375
1515
  }
1376
1516
  if (normalized.length === 6) {
@@ -1906,182 +2046,148 @@ var DesignEngineerAgent = class extends BaseAgent {
1906
2046
  }
1907
2047
  return issues;
1908
2048
  }
2049
+ // ============================================================================
2050
+ // ENHANCED AI SLOP DETECTION — Critical for Design Quality
2051
+ // ============================================================================
1909
2052
  /**
1910
- * AI Enhancement for design review
2053
+ * Detect and block AI slop patterns with enhanced rules
2054
+ * Returns issues with severity 'critical' for slop that MUST be fixed
1911
2055
  */
1912
- getAIEnhancementSystemPrompt() {
1913
- return `You are an award-winning design engineer from a top creative agency (Linear, Vercel, Stripe caliber). You review code for Awwwards-level polish and Codrops-worthy effects.
1914
-
1915
- ## Guardrails: Design & Engineering Principles
1916
- - Lead with user-centricity, clarity, and accessibility; animation is optional.
1917
- - KISS and DRY: reduce complexity, prefer design tokens and reusable patterns.
1918
- - Reliability and feedback: motion only when it improves understanding or state change.
1919
- - Efficiency and sustainability: bias to stillness, avoid unnecessary paints/GPU work, honor prefers-reduced-motion.
1920
- - Ethics and integrity: honest communication about capabilities, avoid dark patterns.
1921
-
1922
- ## Color Analysis Priority
1923
- Detect "AI slop" colors \u2014 the garish, oversaturated colors that AI tools generate:
1924
- - Neon greens (#00ff00, #0f0)
1925
- - Electric blues (#0000ff, #00f)
1926
- - Hot magentas (#ff00ff, #f0f)
1927
- - Pure saturated primaries
1928
-
1929
- Apply Josef Albers' "Interaction of Color":
1930
- - Constrain to 1-2 hue families; anchor with neutrals (slate/zinc/stone).
1931
- - Build contrast through value (light/dark) and temperature shifts, not saturation.
1932
- - Test adjacency: colors must work in pairs/triads against neutral backgrounds.
1933
- - Avoid overusing violet/purple gradients (common AI tell); explore original complements/analogous schemes.
1934
-
1935
- Replace with modern SaaS palettes from top sites (saaslandingpage.com, awwwards.com), customized via Albers adjacency tests:
1936
- - **Dark mode**: Vercel (#000, #0070f3), Linear (#000212, #5e6ad2), Stripe (#0a2540, #635bff)
1937
- - **Light mode**: Notion (#fff, #f7f6f3, #2eaadc), Figma (#fff, #f5f5f5, #0d99ff), Framer (#fff, #0055ff)
1938
- - **Neutral grays**: Use slate, zinc, or neutral scales, not flat grays
1939
-
1940
- ## Inspiration Sources (structured lookups)
1941
- - Codrops Creative Hub (https://tympanus.net/codrops/hub/):
1942
- - Tags to scan: scroll, hover, typography, cursor, WebGL, WebGPU, GSAP, Framer Motion, Three.js, grid, parallax, page transition, slider/carousel.
1943
- - Use as inspiration for interaction patterns; keep accessibility, performance, reduced-motion.
1944
- - Awwwards Directory (https://www.awwwards.com/directory/):
1945
- - Benchmarks: Locomotive, Immersive Garden, Active Theory, Hello Monday, Resn, Obys, Build in Amsterdam.
1946
- - Categories to map: animated websites, scrolling, one-page, interaction design, WebGL sites, 3D, GSAP, React, Framer.
1947
- - Extract patterns (structure, choreography, feedback), not pixels; keep clarity, responsiveness, and performance.
1948
-
1949
- ## Motion & Creative Effects
1950
- Recommend motion only when it improves comprehension or feedback; keep durations 150-300ms, ease-out preferred, and provide reduced-motion fallbacks. Prefer micro-interactions over showy effects.
1951
- - Default to static if motion adds no clarity.
1952
- - Ensure @media (prefers-reduced-motion) disables non-essential motion.
1953
- - Prioritize transform/opacity over layout-changing properties.
1954
- When warranted by context, suggest libraries:
1955
- - **React projects**: Framer Motion (gestures, layout), React Spring (physics), Auto-Animate (quick wins)
1956
- - **Complex timelines**: GSAP + ScrollTrigger, SplitText for character animations
1957
- - **3D/WebGL**: React Three Fiber + Drei for hero sections, product showcases
1958
- - **Smooth scroll**: Lenis or Locomotive Scroll for butter-smooth scrolling
1959
- - **Text effects**: Splitting.js for character animations, Typed.js for typewriter
1960
- - **Hover effects**: Atropos for 3D tilt, custom cursor effects
1961
-
1962
- ## Project Context Effects
1963
- - **Marketing/Landing**: Scroll reveals, parallax, text animations, gradient meshes, 3D elements
1964
- - **Dashboard**: Subtle micro-interactions, skeleton loaders, chart transitions
1965
- - **E-commerce**: Image zoom, add-to-cart animations, filter transitions
1966
- - **Portfolio**: Page transitions, cursor effects, image reveals
1967
-
1968
- Analyze detected issues and code for:
1969
- 1. **Color coherence** \u2014 Is this a cohesive palette or random AI-generated colors?
1970
- 2. **Motion library usage** \u2014 Is the project using appropriate animation tools?
1971
- 3. **Design system consistency** \u2014 Tokens, spacing scales, color systems
1972
- 4. **Motion design quality** \u2014 Easing curves, choreography, performance
1973
- 5. **Visual hierarchy** \u2014 Typography systems, contrast, visual flow
1974
- 6. **Creative CSS techniques** \u2014 Gradients, masks, blend modes, clip-paths
1975
- 7. **Modern CSS features** \u2014 Container queries, :has(), subgrid
1976
-
1977
- Output STRICT JSON:
1978
- {
1979
- "validated": [{
1980
- "original_issue": "...",
1981
- "verdict": "TRUE_POSITIVE" | "FALSE_POSITIVE",
1982
- "confidence": 0-100,
1983
- "file": "path",
1984
- "line": 123,
1985
- "severity": "moderate",
1986
- "design_impact": "Why this hurts the user experience",
1987
- "fix": "Creative CSS fix with specific color values from modern palettes"
1988
- }],
1989
- "additional": [{
1990
- "issue": "Design opportunity found",
1991
- "file": "path",
1992
- "line": 123,
1993
- "severity": "low",
1994
- "enhancement": "How to elevate this to Awwwards quality",
1995
- "fix": "Modern CSS/animation code with npm install command if needed"
1996
- }],
1997
- "motion_recommendations": [{
1998
- "library": "framer-motion",
1999
- "install": "npm i framer-motion",
2000
- "use_case": "What specific effect to implement",
2001
- "example_code": "Brief code snippet"
2002
- }],
2003
- "palette_recommendation": {
2004
- "primary": "#hex",
2005
- "secondary": "#hex",
2006
- "background": "#hex",
2007
- "surface": "#hex",
2008
- "text": "#hex",
2009
- "muted": "#hex",
2010
- "accent": "#hex",
2011
- "inspiration": "Which top SaaS this is inspired by"
2012
- },
2013
- "project_context": "marketing | dashboard | ecommerce | portfolio | general",
2014
- "summary": "Overall design craft assessment with specific recommendations"
2015
- }`;
2016
- }
2017
- };
2018
-
2019
- // src/agents/legal.ts
2020
- var LegalAgent = class extends BaseAgent {
2021
- name = "legal";
2022
- description = "Compliance with GDPR, CCPA, data protection laws, and legal requirements";
2023
- version = "1.0.0";
2024
- shouldActivate(context) {
2025
- return context.touchesUserData || context.touchesPayments || context.touchesAuth;
2026
- }
2027
- async analyzeFiles(files, _context) {
2056
+ detectAISlopPatterns(content, file) {
2028
2057
  const issues = [];
2029
- for (const file of files) {
2030
- try {
2031
- const content = await this.readFile(file);
2032
- issues.push(...this.checkGDPRCompliance(content, file));
2033
- issues.push(...this.checkDataRetention(content, file));
2034
- issues.push(...this.checkConsentPatterns(content, file));
2035
- } catch (error) {
2036
- console.error(`Legal Agent: Error reading file ${file}:`, error);
2058
+ const bgColors = this.extractBackgroundColors(content);
2059
+ if (bgColors.length >= 2) {
2060
+ const lightnesses = bgColors.map((c) => this.getLightness(c));
2061
+ const minDelta = this.getMinimumLightnessDelta(lightnesses);
2062
+ if (minDelta < AI_SLOP_BLOCKERS.colors.minSurfaceLightnessDelta) {
2063
+ const suggestedFix = this.suggestLighterSurface(bgColors[0], 10);
2064
+ issues.push(this.createIssue(
2065
+ this.generateIssueId(),
2066
+ "critical",
2067
+ `AI SLOP: Surfaces too similar (${minDelta.toFixed(1)}% lightness delta). Min 8% required.`,
2068
+ `Change surface color to create visible hierarchy. Suggestion: use ${suggestedFix} for elevated surfaces. Reference: zinc scale at tailwindcss.com/docs/customizing-colors`,
2069
+ file,
2070
+ void 0,
2071
+ 0.95,
2072
+ void 0,
2073
+ true
2074
+ ));
2037
2075
  }
2038
2076
  }
2039
- return issues;
2040
- }
2041
- checkGDPRCompliance(content, file) {
2042
- const issues = [];
2043
- const lines = content.split("\n");
2044
- for (let i = 0; i < lines.length; i++) {
2045
- const line = lines[i];
2046
- const lineNumber = i + 1;
2047
- if (/email|phone|address|name|birthdate|ssn/i.test(line) && /save|store|insert|create|post/i.test(line) && !/consent|gdpr|terms/i.test(content)) {
2077
+ const accentColors = this.extractAccentColors(content);
2078
+ const accentHues = this.getUniqueHueFamilies(accentColors);
2079
+ if (accentHues.length > AI_SLOP_BLOCKERS.accents.maxAccentHues + 1) {
2080
+ issues.push(this.createIssue(
2081
+ this.generateIssueId(),
2082
+ "serious",
2083
+ `AI SLOP: ${accentHues.length} accent color families detected. Use ONE primary accent.`,
2084
+ `Standardize on a single accent hue family. Semantic colors (success/warning/error) are exempt. Reference: radix-ui.com/colors for cohesive scales`,
2085
+ file,
2086
+ void 0,
2087
+ 0.9,
2088
+ void 0,
2089
+ true
2090
+ ));
2091
+ }
2092
+ const allColors = this.extractAllColors(content);
2093
+ const purpleCount = allColors.filter((c) => {
2094
+ const hue = this.getHueFromHex(c);
2095
+ return hue >= 250 && hue <= 310;
2096
+ }).length;
2097
+ if (allColors.length > 3 && purpleCount / allColors.length > 0.4) {
2098
+ issues.push(this.createIssue(
2099
+ this.generateIssueId(),
2100
+ "moderate",
2101
+ "AI SLOP: Over-reliance on purple/violet (common AI tell)",
2102
+ "Consider alternatives from radix-ui.com/colors \u2014 try orange, teal, or green scales for differentiation. Apply Josef Albers principle: constrain to 1-2 hue families.",
2103
+ file,
2104
+ void 0,
2105
+ 0.85,
2106
+ void 0,
2107
+ true
2108
+ ));
2109
+ }
2110
+ for (const color of allColors) {
2111
+ const saturation = this.getSaturation(color);
2112
+ const lightness = this.getLightness(color);
2113
+ if (saturation > AI_SLOP_BLOCKERS.colors.maxSaturation && lightness > 40 && lightness < 60) {
2114
+ const desaturated = this.desaturateColor(color, 60);
2048
2115
  issues.push(this.createIssue(
2049
2116
  this.generateIssueId(),
2050
2117
  "serious",
2051
- "Personal data collection without consent verification",
2052
- "Implement consent collection before storing PII (GDPR Article 6)",
2118
+ `AI SLOP: Neon color detected (${color}, ${saturation.toFixed(0)}% saturation)`,
2119
+ `Desaturate to ${desaturated}. Max recommended saturation: 75%. Source colors from radix-ui.com/colors for contrast-safe alternatives.`,
2053
2120
  file,
2054
- lineNumber,
2055
- 0.75,
2056
- "GDPR Article 6 - Lawfulness of processing",
2057
- false
2121
+ void 0,
2122
+ 0.9,
2123
+ void 0,
2124
+ true
2058
2125
  ));
2059
2126
  }
2060
- if (/analytics|gtag|fbq|pixel|tracking/i.test(line) && !/consent|optIn|cookie/i.test(content)) {
2127
+ }
2128
+ return issues;
2129
+ }
2130
+ /**
2131
+ * Analyze typography patterns for AI slop
2132
+ */
2133
+ analyzeTypographyPatterns(content, file) {
2134
+ const issues = [];
2135
+ const weights = this.extractFontWeights(content);
2136
+ const uniqueWeights = new Set(weights);
2137
+ if (uniqueWeights.size === 1 && weights.length > 3) {
2138
+ const singleWeight = [...uniqueWeights][0];
2139
+ issues.push(this.createIssue(
2140
+ this.generateIssueId(),
2141
+ "moderate",
2142
+ `AI SLOP: All text uses font-weight: ${singleWeight}. No visual hierarchy.`,
2143
+ "Use weight hierarchy: 400 for body, 500 for labels/emphasis, 600 for headings, 700 for hero/strong emphasis.",
2144
+ file,
2145
+ void 0,
2146
+ 0.8,
2147
+ void 0,
2148
+ true
2149
+ ));
2150
+ }
2151
+ const fontFamilies = this.extractFontFamilies(content);
2152
+ const hasModernFont = fontFamilies.some(
2153
+ (f) => /inter|geist|graphik|söhne|suisse|plus jakarta|dm sans|manrope/i.test(f)
2154
+ );
2155
+ if (!hasModernFont && fontFamilies.length > 0) {
2156
+ const isGenericOnly = fontFamilies.every(
2157
+ (f) => /^(-apple-system|BlinkMacSystemFont|Segoe UI|Roboto|Helvetica Neue|Arial|sans-serif|system-ui)$/i.test(f.trim())
2158
+ );
2159
+ if (isGenericOnly) {
2061
2160
  issues.push(this.createIssue(
2062
2161
  this.generateIssueId(),
2063
- "serious",
2064
- "Analytics/tracking without consent management",
2065
- "Implement cookie consent banner before loading tracking scripts",
2162
+ "moderate",
2163
+ "AI SLOP: No modern font specified (using only system fonts)",
2164
+ `Add a modern font: font-family: 'Inter', -apple-system, sans-serif. Get Inter from fonts.google.com or Geist from vercel.com/font`,
2066
2165
  file,
2067
- lineNumber,
2068
- 0.8,
2069
- "GDPR Article 7 / ePrivacy Directive",
2070
- false
2166
+ void 0,
2167
+ 0.75,
2168
+ void 0,
2169
+ true
2071
2170
  ));
2072
2171
  }
2073
- if (/userData|userProfile|account/i.test(line) && /get|fetch|query/i.test(line)) {
2074
- const hasExport = /export|download|portability/i.test(content);
2075
- if (!hasExport) {
2172
+ }
2173
+ const fontSizes = content.match(/font-size:\s*([\d.]+)(px|rem)/gi) || [];
2174
+ for (const sizeMatch of fontSizes) {
2175
+ const match = sizeMatch.match(/font-size:\s*([\d.]+)(px|rem)/i);
2176
+ if (match) {
2177
+ const value = parseFloat(match[1]);
2178
+ const unit = match[2];
2179
+ const pxValue = unit === "rem" ? value * 16 : value;
2180
+ const allowedSizes = AI_SLOP_BLOCKERS.typography.allowedSizes;
2181
+ if (!allowedSizes.includes(Math.round(pxValue))) {
2076
2182
  issues.push(this.createIssue(
2077
2183
  this.generateIssueId(),
2078
- "moderate",
2079
- "Consider implementing data portability",
2080
- "Users have right to export their data (GDPR Article 20)",
2184
+ "low",
2185
+ `Font size ${value}${unit} not on type scale`,
2186
+ `Use systematic type scale: ${allowedSizes.join(", ")}px. Or use clamp() for fluid typography.`,
2081
2187
  file,
2082
2188
  void 0,
2083
- 0.6,
2084
- "GDPR Article 20 - Right to data portability",
2189
+ 0.65,
2190
+ void 0,
2085
2191
  false
2086
2192
  ));
2087
2193
  }
@@ -2089,65 +2195,1581 @@ var LegalAgent = class extends BaseAgent {
2089
2195
  }
2090
2196
  return issues;
2091
2197
  }
2092
- checkDataRetention(content, file) {
2198
+ /**
2199
+ * Analyze contrast ratios between colors
2200
+ */
2201
+ analyzeContrastRatios(content, file) {
2093
2202
  const issues = [];
2094
- if (/store|save|persist|database|collection/i.test(content) && !/delete|expire|retention|ttl|expiresAt|deletedAt/i.test(content)) {
2095
- issues.push(this.createIssue(
2096
- this.generateIssueId(),
2097
- "moderate",
2098
- "No data retention/deletion policy detected",
2099
- "Implement data retention limits and deletion procedures",
2100
- file,
2101
- void 0,
2102
- 0.7,
2103
- "GDPR Article 5(1)(e) - Storage limitation",
2104
- false
2105
- ));
2106
- }
2107
- if (/deletedAt|isDeleted|softDelete/i.test(content) && !/hardDelete|permanentDelete|purge/i.test(content)) {
2108
- issues.push(this.createIssue(
2109
- this.generateIssueId(),
2110
- "low",
2111
- "Soft delete without hard delete option",
2112
- "Consider implementing permanent deletion for GDPR right to erasure",
2113
- file,
2114
- void 0,
2115
- 0.65,
2116
- "GDPR Article 17 - Right to erasure",
2117
- false
2118
- ));
2203
+ const lines = content.split("\n");
2204
+ for (let i = 0; i < lines.length; i++) {
2205
+ const line = lines[i];
2206
+ const lineNumber = i + 1;
2207
+ if (/color:\s*#[45678][0-9a-f]{5}/i.test(line)) {
2208
+ const context = lines.slice(Math.max(0, i - 5), i + 5).join("\n");
2209
+ if (/background.*#[012][0-9a-f]{5}|bg-.*-9[05]0/i.test(context)) {
2210
+ issues.push(this.createIssue(
2211
+ this.generateIssueId(),
2212
+ "serious",
2213
+ "Low contrast text detected \u2014 may fail WCAG AA",
2214
+ `Text on dark backgrounds needs 4.5:1 contrast ratio. Use zinc-50 (#fafafa) for primary text, zinc-400 (#a1a1aa) for secondary. Validate at webaim.org/resources/contrastchecker`,
2215
+ file,
2216
+ lineNumber,
2217
+ 0.85,
2218
+ void 0,
2219
+ true
2220
+ ));
2221
+ }
2222
+ }
2223
+ if (/--text-secondary|text-muted|text-tertiary/i.test(line)) {
2224
+ const colorMatch = line.match(/#[0-9a-f]{6}/i);
2225
+ if (colorMatch) {
2226
+ const lightness = this.getLightness(colorMatch[0]);
2227
+ if (lightness < 30) {
2228
+ issues.push(this.createIssue(
2229
+ this.generateIssueId(),
2230
+ "moderate",
2231
+ "Secondary text too dark for readability",
2232
+ "Secondary text should be at least zinc-400 (#a1a1aa, ~40% lightness) on dark backgrounds. Consider zinc-500 (#71717a) minimum.",
2233
+ file,
2234
+ lineNumber,
2235
+ 0.8,
2236
+ void 0,
2237
+ false
2238
+ ));
2239
+ }
2240
+ }
2241
+ }
2119
2242
  }
2120
2243
  return issues;
2121
2244
  }
2122
- checkConsentPatterns(content, file) {
2245
+ /**
2246
+ * Analyze spacing for magic numbers
2247
+ */
2248
+ analyzeSpacingGrid(content, file) {
2123
2249
  const issues = [];
2124
2250
  const lines = content.split("\n");
2125
2251
  for (let i = 0; i < lines.length; i++) {
2126
2252
  const line = lines[i];
2127
2253
  const lineNumber = i + 1;
2128
- if (/checked|defaultChecked|defaultValue.*true/i.test(line) && /consent|newsletter|marketing|terms/i.test(line)) {
2129
- issues.push(this.createIssue(
2130
- this.generateIssueId(),
2131
- "serious",
2132
- "Pre-checked consent box detected",
2133
- "Consent must be freely given - remove default checked state",
2134
- file,
2135
- lineNumber,
2136
- 0.85,
2137
- "GDPR Recital 32 - Consent should not be pre-ticked",
2138
- true
2139
- ));
2254
+ if (/margin|padding|gap|inset/i.test(line)) {
2255
+ const pxMatches = line.matchAll(/:\s*(\d+)px/g);
2256
+ for (const match of pxMatches) {
2257
+ const value = parseInt(match[1]);
2258
+ if (value > 2 && !SPACING_TOKENS.allowedPx.includes(value)) {
2259
+ const closestValue = SPACING_TOKENS.allowedPx.reduce(
2260
+ (prev, curr) => Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev
2261
+ );
2262
+ issues.push(this.createIssue(
2263
+ this.generateIssueId(),
2264
+ "low",
2265
+ `Magic number ${value}px \u2014 not on 4px spacing grid`,
2266
+ `Use ${closestValue}px instead, or design token --space-${closestValue / 4}. Allowed: ${SPACING_TOKENS.allowedPx.slice(0, 10).join(", ")}...`,
2267
+ file,
2268
+ lineNumber,
2269
+ 0.6,
2270
+ void 0,
2271
+ false
2272
+ ));
2273
+ }
2274
+ }
2140
2275
  }
2141
- if (/&&.*&&/i.test(line) && /consent|agree|accept/i.test(line)) {
2142
- issues.push(this.createIssue(
2143
- this.generateIssueId(),
2144
- "moderate",
2145
- "Potentially bundled consent conditions",
2146
- "Separate consents for different purposes (GDPR granular consent)",
2147
- file,
2148
- lineNumber,
2149
- 0.6,
2150
- "GDPR Article 7 - Conditions for consent",
2276
+ }
2277
+ return issues;
2278
+ }
2279
+ // ============================================================================
2280
+ // DESIGN HEALTH SCORING — Self-Critique System
2281
+ // ============================================================================
2282
+ /**
2283
+ * Calculate overall design health score
2284
+ */
2285
+ calculateDesignHealthScore(issues) {
2286
+ const slopIssues = issues.filter((i) => i.isSlop);
2287
+ const criticalIssues = issues.filter((i) => i.severity === "critical");
2288
+ const seriousIssues = issues.filter((i) => i.severity === "serious");
2289
+ const moderateIssues = issues.filter((i) => i.severity === "moderate");
2290
+ const slopScore = Math.max(0, 100 - slopIssues.length * 15);
2291
+ const tokenIssues = issues.filter((i) => /token|hardcoded|magic number/i.test(i.issue));
2292
+ const contrastIssues = issues.filter((i) => /contrast|WCAG|readability/i.test(i.issue));
2293
+ const spacingIssues = issues.filter((i) => /spacing|grid|magic number/i.test(i.issue));
2294
+ const typographyIssues = issues.filter((i) => /font|typography|weight/i.test(i.issue));
2295
+ const surfaceIssues = issues.filter((i) => /surface|hierarchy|dark-on-dark/i.test(i.issue));
2296
+ const tokenAdoption = Math.max(0, 100 - tokenIssues.length * 10);
2297
+ const contrastCompliance = Math.max(0, 100 - contrastIssues.length * 20);
2298
+ const spacingConsistency = Math.max(0, 100 - spacingIssues.length * 8);
2299
+ const typographySystem = Math.max(0, 100 - typographyIssues.length * 12);
2300
+ const surfaceHierarchy = Math.max(0, 100 - surfaceIssues.length * 25);
2301
+ const severityPenalty = criticalIssues.length * 25 + seriousIssues.length * 15 + moderateIssues.length * 5;
2302
+ const score = Math.max(0, 100 - severityPenalty);
2303
+ return {
2304
+ score,
2305
+ slopScore,
2306
+ breakdown: {
2307
+ tokenAdoption,
2308
+ contrastCompliance,
2309
+ spacingConsistency,
2310
+ typographySystem,
2311
+ surfaceHierarchy
2312
+ }
2313
+ };
2314
+ }
2315
+ // ============================================================================
2316
+ // COLOR UTILITY METHODS — Enhanced Analysis
2317
+ // ============================================================================
2318
+ /**
2319
+ * Extract all background colors from content
2320
+ */
2321
+ extractBackgroundColors(content) {
2322
+ const colors = [];
2323
+ const matches = content.matchAll(/background(?:-color)?:\s*(#[0-9a-fA-F]{3,8})/gi);
2324
+ for (const match of matches) {
2325
+ colors.push(match[1].toLowerCase());
2326
+ }
2327
+ const varMatches = content.matchAll(/--(?:bg|background|surface|base)[^:]*:\s*(#[0-9a-fA-F]{3,8})/gi);
2328
+ for (const match of varMatches) {
2329
+ colors.push(match[1].toLowerCase());
2330
+ }
2331
+ return colors;
2332
+ }
2333
+ /**
2334
+ * Extract accent/interactive colors
2335
+ */
2336
+ extractAccentColors(content) {
2337
+ const colors = [];
2338
+ const patterns = [
2339
+ /--(?:accent|primary|interactive|brand|cta)[^:]*:\s*(#[0-9a-fA-F]{3,8})/gi,
2340
+ /(?:button|btn|link)[^{]*{\s*[^}]*(?:background|color):\s*(#[0-9a-fA-F]{3,8})/gi
2341
+ ];
2342
+ for (const pattern of patterns) {
2343
+ const matches = content.matchAll(pattern);
2344
+ for (const match of matches) {
2345
+ colors.push(match[1].toLowerCase());
2346
+ }
2347
+ }
2348
+ return colors;
2349
+ }
2350
+ /**
2351
+ * Extract all hex colors from content
2352
+ */
2353
+ extractAllColors(content) {
2354
+ const colors = [];
2355
+ const matches = content.matchAll(/#[0-9a-fA-F]{3,8}\b/g);
2356
+ for (const match of matches) {
2357
+ const hex = match[0].toLowerCase();
2358
+ if (hex.length === 4) {
2359
+ colors.push(`#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`);
2360
+ } else if (hex.length === 7) {
2361
+ colors.push(hex);
2362
+ }
2363
+ }
2364
+ return [...new Set(colors)];
2365
+ }
2366
+ /**
2367
+ * Extract font weights from content
2368
+ */
2369
+ extractFontWeights(content) {
2370
+ const weights = [];
2371
+ const matches = content.matchAll(/font-weight:\s*(\d+)/g);
2372
+ for (const match of matches) {
2373
+ weights.push(parseInt(match[1]));
2374
+ }
2375
+ return weights;
2376
+ }
2377
+ /**
2378
+ * Extract font families from content
2379
+ */
2380
+ extractFontFamilies(content) {
2381
+ const families = [];
2382
+ const matches = content.matchAll(/font-family:\s*([^;]+)/gi);
2383
+ for (const match of matches) {
2384
+ const fontList = match[1].split(",").map((f) => f.trim().replace(/['"]/g, ""));
2385
+ families.push(...fontList);
2386
+ }
2387
+ return families;
2388
+ }
2389
+ /**
2390
+ * Get lightness value from hex color (0-100)
2391
+ */
2392
+ getLightness(hex) {
2393
+ const rgb = this.hexToRgb(hex);
2394
+ if (!rgb) return 0;
2395
+ const r = rgb.r / 255;
2396
+ const g = rgb.g / 255;
2397
+ const b = rgb.b / 255;
2398
+ const max = Math.max(r, g, b);
2399
+ const min = Math.min(r, g, b);
2400
+ return (max + min) / 2 * 100;
2401
+ }
2402
+ /**
2403
+ * Get saturation value from hex color (0-100)
2404
+ */
2405
+ getSaturation(hex) {
2406
+ const rgb = this.hexToRgb(hex);
2407
+ if (!rgb) return 0;
2408
+ const r = rgb.r / 255;
2409
+ const g = rgb.g / 255;
2410
+ const b = rgb.b / 255;
2411
+ const max = Math.max(r, g, b);
2412
+ const min = Math.min(r, g, b);
2413
+ const l = (max + min) / 2;
2414
+ if (max === min) return 0;
2415
+ const d = max - min;
2416
+ return (l > 0.5 ? d / (2 - max - min) : d / (max + min)) * 100;
2417
+ }
2418
+ /**
2419
+ * Get hue from hex color
2420
+ */
2421
+ getHueFromHex(hex) {
2422
+ const rgb = this.hexToRgb(hex);
2423
+ if (!rgb) return 0;
2424
+ return this.rgbToHue(rgb.r, rgb.g, rgb.b);
2425
+ }
2426
+ /**
2427
+ * Get unique hue families from colors
2428
+ */
2429
+ getUniqueHueFamilies(colors) {
2430
+ const families = /* @__PURE__ */ new Set();
2431
+ for (const color of colors) {
2432
+ const hue = this.getHueFromHex(color);
2433
+ if (hue < 30 || hue >= 330) families.add("red");
2434
+ else if (hue < 60) families.add("orange");
2435
+ else if (hue < 90) families.add("yellow");
2436
+ else if (hue < 150) families.add("green");
2437
+ else if (hue < 210) families.add("cyan");
2438
+ else if (hue < 270) families.add("blue");
2439
+ else if (hue < 330) families.add("purple");
2440
+ }
2441
+ return [...families];
2442
+ }
2443
+ /**
2444
+ * Get minimum lightness delta between colors
2445
+ */
2446
+ getMinimumLightnessDelta(lightnesses) {
2447
+ if (lightnesses.length < 2) return 100;
2448
+ let minDelta = 100;
2449
+ for (let i = 0; i < lightnesses.length; i++) {
2450
+ for (let j = i + 1; j < lightnesses.length; j++) {
2451
+ const delta = Math.abs(lightnesses[i] - lightnesses[j]);
2452
+ if (delta < minDelta) minDelta = delta;
2453
+ }
2454
+ }
2455
+ return minDelta;
2456
+ }
2457
+ /**
2458
+ * Suggest a lighter surface color
2459
+ */
2460
+ suggestLighterSurface(baseColor, deltaPercent) {
2461
+ const rgb = this.hexToRgb(baseColor);
2462
+ if (!rgb) return "#1a1a1a";
2463
+ const increase = deltaPercent / 100 * 255;
2464
+ const newR = Math.min(255, Math.round(rgb.r + increase));
2465
+ const newG = Math.min(255, Math.round(rgb.g + increase));
2466
+ const newB = Math.min(255, Math.round(rgb.b + increase));
2467
+ return `#${newR.toString(16).padStart(2, "0")}${newG.toString(16).padStart(2, "0")}${newB.toString(16).padStart(2, "0")}`;
2468
+ }
2469
+ /**
2470
+ * Desaturate a color
2471
+ */
2472
+ desaturateColor(hex, targetSaturation) {
2473
+ const rgb = this.hexToRgb(hex);
2474
+ if (!rgb) return hex;
2475
+ const gray = (rgb.r + rgb.g + rgb.b) / 3;
2476
+ const factor = targetSaturation / 100;
2477
+ const newR = Math.round(gray + (rgb.r - gray) * factor);
2478
+ const newG = Math.round(gray + (rgb.g - gray) * factor);
2479
+ const newB = Math.round(gray + (rgb.b - gray) * factor);
2480
+ return `#${newR.toString(16).padStart(2, "0")}${newG.toString(16).padStart(2, "0")}${newB.toString(16).padStart(2, "0")}`;
2481
+ }
2482
+ /**
2483
+ * Detect design context from content
2484
+ */
2485
+ detectDesignContext(content) {
2486
+ let productType = "saas";
2487
+ let framework = "unknown";
2488
+ if (/from ['"]react['"]|import React/.test(content)) framework = "react";
2489
+ else if (/from ['"]vue['"]|<template>/.test(content)) framework = "vue";
2490
+ else if (/from ['"]svelte['"]|<script.*lang=["']ts["']/.test(content)) framework = "svelte";
2491
+ if (/dashboard|admin|analytics/i.test(content)) productType = "dashboard";
2492
+ else if (/checkout|cart|product|shop|store/i.test(content)) productType = "ecommerce";
2493
+ else if (/hero|landing|pricing|testimonial|cta/i.test(content)) productType = "marketing";
2494
+ else if (/portfolio|gallery|showcase|case-study/i.test(content)) productType = "portfolio";
2495
+ else if (/fitness|workout|exercise|training/i.test(content)) productType = "fitness";
2496
+ else if (/bank|finance|money|payment|transaction/i.test(content)) productType = "fintech";
2497
+ else if (/canvas|editor|design|creative/i.test(content)) productType = "creativeTools";
2498
+ const hasExistingDesignSystem = /--color-|--spacing-|--radius-|--font-|theme|tokens/i.test(content);
2499
+ return {
2500
+ productType,
2501
+ framework,
2502
+ hasExistingDesignSystem
2503
+ };
2504
+ }
2505
+ /**
2506
+ * Get domain-specific recommendations
2507
+ */
2508
+ getDomainRecommendations(context) {
2509
+ const rules = DOMAIN_DESIGN_RULES[context.productType];
2510
+ if (!rules) return "";
2511
+ return `For ${context.productType}: Use ${rules.defaultMode} mode, accent colors from Radix (${rules.accentSuggestions.join("/")}), ${rules.motionLevel} motion. Reference: ${rules.reference.join(", ")}`;
2512
+ }
2513
+ /**
2514
+ * AI Enhancement for design review
2515
+ */
2516
+ getAIEnhancementSystemPrompt() {
2517
+ return `You are an award-winning design engineer from a top creative agency (Linear, Vercel, Stripe caliber). You review and generate code for Awwwards-level polish and Codrops-worthy effects.
2518
+
2519
+ ## Design Intelligence Stack (5 Layers)
2520
+
2521
+ ### Layer 5: Emotional Design
2522
+ "What feeling should this evoke?"
2523
+ - Calm \u2192 muted colors, generous whitespace, slow transitions
2524
+ - Energetic \u2192 vibrant accents, dynamic motion, bold typography
2525
+ - Trustworthy \u2192 blues, clean lines, consistent patterns
2526
+ - Playful \u2192 rounded corners, illustrations, micro-interactions
2527
+ - Premium \u2192 desaturated, spacious, subtle motion
2528
+
2529
+ ### Layer 4: Domain Awareness
2530
+ "What kind of product is this?"
2531
+ - Fitness app \u2192 dark mode, energetic, progress-focused (Strava, Peloton)
2532
+ - Banking app \u2192 light mode, trustworthy, security-focused (Mercury, Ramp)
2533
+ - Creative tool \u2192 dark mode, minimal chrome, content-first (Figma, Linear)
2534
+ - E-commerce \u2192 light mode, scannable, conversion-optimized (Shopify, Glossier)
2535
+ - Dashboard \u2192 balanced density, clear data hierarchy (Vercel, Stripe)
2536
+
2537
+ ### Layer 3: Brand Intelligence
2538
+ Extract from existing code: logo colors, CSS variables, fonts, tone of voice
2539
+ Extend: generate complementary palette from Radix/Tailwind scales
2540
+ Maintain: track brand consistency across components
2541
+
2542
+ ### Layer 2: Semantic Understanding
2543
+ "What is this component FOR?"
2544
+ - Hero \u2192 command attention, single CTA, emotional hook
2545
+ - Pricing \u2192 scannable, comparison-friendly, reduce anxiety
2546
+ - Testimonials \u2192 build trust, social proof, authentic
2547
+ - Dashboard \u2192 information density, quick actions, status clarity
2548
+
2549
+ ### Layer 1: Token System
2550
+ Foundation layer \u2014 colors, typography, spacing, effects, motion
2551
+ Reference external sources: radix-ui.com/colors, tailwindcss.com/docs
2552
+
2553
+ ## Guardrails: Design & Engineering Principles
2554
+ - Lead with user-centricity, clarity, and accessibility; animation is optional
2555
+ - KISS and DRY: reduce complexity, prefer design tokens and reusable patterns
2556
+ - Reliability and feedback: motion only when it improves understanding or state change
2557
+ - Efficiency and sustainability: bias to stillness, avoid unnecessary paints/GPU work
2558
+ - Ethics and integrity: honest communication about capabilities, avoid dark patterns
2559
+ - Honor prefers-reduced-motion always
2560
+
2561
+ ## CRITICAL: Generation Mode Constraints
2562
+
2563
+ When generating UI, enforce these HARD RULES. Violations are unacceptable:
2564
+
2565
+ ### Color System (DERIVE, don't hardcode)
2566
+ \`\`\`css
2567
+ /* Source: tailwindcss.com/docs/customizing-colors (zinc scale) */
2568
+ /* Or: radix-ui.com/colors (slate scale) */
2569
+
2570
+ /* Dark Mode Foundation */
2571
+ --bg-base: var(--zinc-950); /* True background */
2572
+ --bg-surface: var(--zinc-900); /* Cards \u2014 MUST differ by \u22658% lightness */
2573
+ --bg-elevated: var(--zinc-800); /* Modals, dropdowns */
2574
+ --border-subtle: var(--zinc-800); /* Dividers */
2575
+ --border-default: var(--zinc-700); /* Input borders */
2576
+ --text-primary: var(--zinc-50); /* Main text \u2014 MUST pass WCAG AA (4.5:1) */
2577
+ --text-secondary: var(--zinc-400); /* Muted text */
2578
+ --text-tertiary: var(--zinc-500); /* Disabled/hint */
2579
+
2580
+ /* ACCENT \u2014 Derive from brand or user context */
2581
+ /* Selection process:
2582
+ 1. Extract from logo/brand assets if available
2583
+ 2. Ask: "What feeling? Energetic/Calm/Trustworthy/Bold"
2584
+ 3. Map feeling \u2192 Radix scale (Orange, Teal, Blue, Crimson, etc.)
2585
+ 4. Validate contrast: webaim.org/resources/contrastchecker
2586
+ DO NOT hardcode hex values \u2014 derive from context */
2587
+ --accent-primary: var(--brand-derived);
2588
+
2589
+ /* Semantic colors \u2014 use Radix scales */
2590
+ --success: var(--radix-green-9);
2591
+ --warning: var(--radix-amber-9);
2592
+ --error: var(--radix-red-9);
2593
+ \`\`\`
2594
+
2595
+ ### Surface Hierarchy (CRITICAL \u2014 Squint Test)
2596
+ Background (zinc-950) \u2190 darkest
2597
+ \u2193 +6-10% lightness
2598
+ Surface (zinc-900) \u2190 cards, sidebars
2599
+ \u2193 +6-10% lightness
2600
+ Elevated (zinc-800) \u2190 modals, dropdowns, hover states
2601
+
2602
+ **Test:** Squint at the UI. If cards disappear into background, FAIL.
2603
+
2604
+ ### Typography System
2605
+ \`\`\`css
2606
+ --font-sans: 'Inter', 'Geist Sans', -apple-system, sans-serif;
2607
+ --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
2608
+
2609
+ /* Type Scale */
2610
+ --text-xs: 0.75rem; /* 12px \u2014 captions */
2611
+ --text-sm: 0.875rem; /* 14px \u2014 secondary */
2612
+ --text-base: 1rem; /* 16px \u2014 body */
2613
+ --text-lg: 1.125rem; /* 18px \u2014 emphasized */
2614
+ --text-xl: 1.25rem; /* 20px \u2014 section headers */
2615
+ --text-2xl: 1.5rem; /* 24px \u2014 card titles */
2616
+ --text-3xl: 1.875rem; /* 30px \u2014 page headers */
2617
+ --text-4xl: 2.25rem; /* 36px \u2014 hero */
2618
+
2619
+ /* Weights \u2014 Create hierarchy */
2620
+ --font-normal: 400; /* Body */
2621
+ --font-medium: 500; /* Labels */
2622
+ --font-semibold: 600; /* Headers */
2623
+ --font-bold: 700; /* Hero */
2624
+ \`\`\`
2625
+
2626
+ ### Spacing Scale (4px grid only)
2627
+ Allowed: 0, 2, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96px
2628
+ NO magic numbers (17px, 23px, 37px are REJECTED)
2629
+
2630
+ ### Accent Color Rules
2631
+ 1. ONE primary accent \u2014 for interactive elements, focus states, CTAs
2632
+ 2. Semantic colors are NOT decorative \u2014 green=success, amber=warning, red=error
2633
+ 3. Never 3+ accent colors on same view \u2014 looks like a casino
2634
+
2635
+ ### Contrast Requirements (WCAG AA)
2636
+ | Element | Minimum Ratio |
2637
+ |---------|---------------|
2638
+ | Body text on surface | 4.5:1 |
2639
+ | Large text (18px+) | 3:1 |
2640
+ | UI components | 3:1 |
2641
+
2642
+ ## AI Slop Detection (BLOCK These Patterns)
2643
+
2644
+ ### Colors
2645
+ - \u274C NO pure saturated colors (#ff0000, #00ff00, #0000ff)
2646
+ - \u274C NO neon colors (saturation > 80%)
2647
+ - \u274C NO more than ONE accent color family per view
2648
+ - \u274C NO dark-on-dark where surfaces blend into background
2649
+ - \u274C NO purple/violet overuse (>40% of palette)
2650
+
2651
+ ### Typography
2652
+ - \u274C NO system fonts alone \u2014 specify Inter or Geist
2653
+ - \u274C NO single font-weight for entire UI \u2014 need hierarchy (400, 500, 600)
2654
+
2655
+ ### Spacing
2656
+ - \u274C NO magic numbers \u2014 use 4px grid only
2657
+
2658
+ ### Before Output Checklist
2659
+ 1. [ ] Can I distinguish cards from background? (squint test)
2660
+ 2. [ ] Is there only ONE accent color for interactivity?
2661
+ 3. [ ] Does text have clear size/weight hierarchy?
2662
+ 4. [ ] Are all spacing values on 4px grid?
2663
+ 5. [ ] Is a modern font (Inter/Geist) specified?
2664
+ 6. [ ] Does secondary text pass contrast check on surfaces?
2665
+
2666
+ If ANY check fails \u2192 FIX BEFORE OUTPUTTING
2667
+
2668
+ ## Animation & Visualization Knowledge
2669
+
2670
+ ### GSAP Best Practices
2671
+ - Use gsap.context() for React cleanup
2672
+ - Prefer timelines for sequenced animations
2673
+ - Respect prefers-reduced-motion
2674
+ - Animate transform/opacity only (not width/height/top/left)
2675
+ - Duration guidelines: micro (100-200ms), standard (300-500ms), emphasis (600-1000ms)
2676
+ - Easing: power2.out for entrances, power2.in for exits
2677
+
2678
+ ### Recharts/D3 Best Practices
2679
+ - Always use ResponsiveContainer with explicit height
2680
+ - Use design system colors, not hardcoded hex
2681
+ - Style axes with design tokens
2682
+ - Disable animations for reduced-motion preference
2683
+
2684
+ ### React Three Fiber Best Practices
2685
+ - Performance budget: <100k triangles mobile, <500k desktop
2686
+ - Use Suspense with fallback for loading
2687
+ - Support reduced-motion
2688
+ - Dispose resources on unmount
2689
+ - Cap devicePixelRatio at 2
2690
+
2691
+ ### Motion Tokens (Unified)
2692
+ \`\`\`
2693
+ Duration:
2694
+ - instant: 100ms (micro-interactions)
2695
+ - fast: 200ms (hover, toggle)
2696
+ - normal: 300ms (standard transitions)
2697
+ - slow: 500ms (emphasis)
2698
+ - slower: 800ms (hero, dramatic)
2699
+
2700
+ Easing:
2701
+ - Enter: ease-out / power2.out
2702
+ - Exit: ease-in / power2.in
2703
+ - Move: ease-in-out / power2.inOut
2704
+ \`\`\`
2705
+
2706
+ ## OpenAI Apps SDK UI Guidelines
2707
+ - Max 2 primary actions per card
2708
+ - No nested scrolling \u2014 auto-fit content
2709
+ - 3-8 items per carousel, max 3 metadata lines
2710
+ - Single optional CTA per carousel item
2711
+ - WCAG AA contrast minimum
2712
+ - Support text resizing
2713
+
2714
+ ## Inspiration Sources
2715
+ - Radix Colors: radix-ui.com/colors (contrast-guaranteed scales)
2716
+ - Tailwind CSS: tailwindcss.com/docs (default palettes + spacing)
2717
+ - shadcn/ui: ui.shadcn.com (production-ready themes)
2718
+ - Codrops: tympanus.net/codrops/hub/ (creative patterns)
2719
+ - Awwwards: awwwards.com/directory/ (benchmarks: Linear, Vercel, Stripe)
2720
+
2721
+ Analyze issues and code for:
2722
+ 1. **AI Slop Detection** \u2014 Surface hierarchy, accent count, neon colors, purple overuse
2723
+ 2. **Color coherence** \u2014 Cohesive palette from verified sources
2724
+ 3. **Typography system** \u2014 Modern fonts, weight hierarchy, scale adherence
2725
+ 4. **Contrast compliance** \u2014 WCAG AA validation
2726
+ 5. **Spacing consistency** \u2014 4px grid enforcement
2727
+ 6. **Motion quality** \u2014 Easing, duration, reduced-motion support
2728
+ 7. **Design system** \u2014 Token adoption, component patterns
2729
+
2730
+ Output STRICT JSON:
2731
+ {
2732
+ "validated": [{
2733
+ "original_issue": "...",
2734
+ "verdict": "TRUE_POSITIVE" | "FALSE_POSITIVE",
2735
+ "confidence": 0-100,
2736
+ "file": "path",
2737
+ "line": 123,
2738
+ "severity": "critical" | "serious" | "moderate" | "low",
2739
+ "design_impact": "Why this hurts the user experience",
2740
+ "fix": "Specific fix with exact values from verified sources"
2741
+ }],
2742
+ "additional": [{
2743
+ "issue": "Design opportunity found",
2744
+ "file": "path",
2745
+ "line": 123,
2746
+ "severity": "low",
2747
+ "enhancement": "How to elevate to Awwwards quality",
2748
+ "fix": "Modern CSS/animation code with npm install if needed"
2749
+ }],
2750
+ "slop_indicators": {
2751
+ "surfaceContrast": "pass" | "fail",
2752
+ "accentCount": 1,
2753
+ "neonColors": 0,
2754
+ "purpleOveruse": false,
2755
+ "fontModernity": "modern" | "system-only",
2756
+ "weightVariation": 3,
2757
+ "spacingGrid": "on-grid" | "magic-numbers"
2758
+ },
2759
+ "design_health": {
2760
+ "score": 85,
2761
+ "slopScore": 95,
2762
+ "tokenAdoption": 80,
2763
+ "contrastCompliance": 100,
2764
+ "spacingConsistency": 90,
2765
+ "typographySystem": 85,
2766
+ "surfaceHierarchy": 100
2767
+ },
2768
+ "palette_recommendation": {
2769
+ "primary": "#hex",
2770
+ "secondary": "#hex",
2771
+ "background": "#hex",
2772
+ "surface": "#hex",
2773
+ "text": "#hex",
2774
+ "muted": "#hex",
2775
+ "accent": "#hex",
2776
+ "source": "Radix teal scale / Tailwind zinc",
2777
+ "feeling": "calm | energetic | trustworthy | bold | premium"
2778
+ },
2779
+ "domain_context": {
2780
+ "detected": "saas | ecommerce | dashboard | marketing | portfolio | fitness | fintech | creativeTools",
2781
+ "recommendations": "Domain-specific design guidance"
2782
+ },
2783
+ "motion_recommendations": [{
2784
+ "library": "framer-motion",
2785
+ "install": "npm i framer-motion",
2786
+ "use_case": "What specific effect to implement",
2787
+ "example_code": "Brief code snippet"
2788
+ }],
2789
+ "summary": "Overall design craft assessment with specific recommendations"
2790
+ }`;
2791
+ }
2792
+ };
2793
+
2794
+ // src/agents/legal.ts
2795
+ var LegalAgent = class extends BaseAgent {
2796
+ name = "legal";
2797
+ description = "Comprehensive legal compliance for app development: licensing, ToS, accessibility, IP, data protection, e-commerce, and regulatory requirements";
2798
+ version = "2.0.0";
2799
+ shouldActivate(context) {
2800
+ return context.touchesUserData || context.touchesPayments || context.touchesAuth || context.touchesUI || context.touchesAPI || context.touchesThirdPartyAPI;
2801
+ }
2802
+ async analyzeFiles(files, _context) {
2803
+ const issues = [];
2804
+ for (const file of files) {
2805
+ try {
2806
+ const content = await this.readFile(file);
2807
+ issues.push(...this.checkLicenseCompliance(content, file));
2808
+ issues.push(...this.checkOpenSourceObligations(content, file));
2809
+ issues.push(...this.checkTermsOfService(content, file));
2810
+ issues.push(...this.checkPrivacyPolicyRequirements(content, file));
2811
+ issues.push(...this.checkAPITermsCompliance(content, file));
2812
+ issues.push(...this.checkThirdPartyLicenses(content, file));
2813
+ issues.push(...this.checkCopyrightCompliance(content, file));
2814
+ issues.push(...this.checkTrademarkUsage(content, file));
2815
+ issues.push(...this.checkAccessibilityCompliance(content, file));
2816
+ issues.push(...this.checkDataProtection(content, file));
2817
+ issues.push(...this.checkConsentManagement(content, file));
2818
+ issues.push(...this.checkDataRetention(content, file));
2819
+ issues.push(...this.checkECommerceCompliance(content, file));
2820
+ issues.push(...this.checkPaymentCompliance(content, file));
2821
+ issues.push(...this.checkRefundPolicy(content, file));
2822
+ issues.push(...this.checkMarketingCompliance(content, file));
2823
+ issues.push(...this.checkFTCDisclosure(content, file));
2824
+ issues.push(...this.checkAgeRestrictions(content, file));
2825
+ issues.push(...this.checkExportControls(content, file));
2826
+ issues.push(...this.checkUGCCompliance(content, file));
2827
+ issues.push(...this.checkContentModeration(content, file));
2828
+ issues.push(...this.checkContractPatterns(content, file));
2829
+ issues.push(...this.checkClickwrapCompliance(content, file));
2830
+ issues.push(...this.checkJurisdictionalCompliance(content, file));
2831
+ issues.push(...this.checkSecurityDisclosure(content, file));
2832
+ issues.push(...this.checkLiabilityProtections(content, file));
2833
+ } catch (error) {
2834
+ console.error(`Legal Agent: Error reading file ${file}:`, error);
2835
+ }
2836
+ }
2837
+ return issues;
2838
+ }
2839
+ // ============================================================
2840
+ // LICENSE & OPEN SOURCE COMPLIANCE
2841
+ // ============================================================
2842
+ checkLicenseCompliance(content, file) {
2843
+ const issues = [];
2844
+ const lines = content.split("\n");
2845
+ const gplIndicators = /gpl|gnu\s+general\s+public|copyleft/i;
2846
+ if (gplIndicators.test(content)) {
2847
+ issues.push(this.createIssue(
2848
+ this.generateIssueId(),
2849
+ "serious",
2850
+ "GPL/Copyleft licensed code detected",
2851
+ "GPL code requires your entire project to be GPL-licensed. Verify license compatibility or find alternative libraries.",
2852
+ file,
2853
+ void 0,
2854
+ 0.85,
2855
+ "GPL License Compliance - Copyleft obligations may apply",
2856
+ false
2857
+ ));
2858
+ }
2859
+ if (/agpl|affero/i.test(content)) {
2860
+ issues.push(this.createIssue(
2861
+ this.generateIssueId(),
2862
+ "critical",
2863
+ "AGPL licensed code detected - network use triggers obligations",
2864
+ "AGPL requires source disclosure even for SaaS/network use. This has significant business implications.",
2865
+ file,
2866
+ void 0,
2867
+ 0.95,
2868
+ "AGPL License - Network use copyleft provisions",
2869
+ false
2870
+ ));
2871
+ }
2872
+ const isSourceFile = /\.(ts|js|tsx|jsx|py|java|go|rs|c|cpp|h)$/.test(file);
2873
+ if (isSourceFile && !/(license|copyright|©|\(c\)|spdx)/i.test(content.slice(0, 500))) {
2874
+ issues.push(this.createIssue(
2875
+ this.generateIssueId(),
2876
+ "low",
2877
+ "Missing license header in source file",
2878
+ "Consider adding SPDX license identifier or copyright notice for clarity",
2879
+ file,
2880
+ 1,
2881
+ 0.5,
2882
+ "Best Practice - License clarity in source files",
2883
+ false
2884
+ ));
2885
+ }
2886
+ for (let i = 0; i < lines.length; i++) {
2887
+ const line = lines[i];
2888
+ if (/require\(|import\s+.*from|from\s+['"]/.test(line) && /node_modules|vendor|third.?party/i.test(line)) {
2889
+ if (!this.hasSeenDependencyWarning) {
2890
+ this.hasSeenDependencyWarning = true;
2891
+ issues.push(this.createIssue(
2892
+ this.generateIssueId(),
2893
+ "moderate",
2894
+ "Third-party dependencies detected - license audit recommended",
2895
+ "Run license audit tools (license-checker, pip-licenses) to verify all dependency licenses are compatible",
2896
+ file,
2897
+ i + 1,
2898
+ 0.7,
2899
+ "Dependency License Compliance",
2900
+ false
2901
+ ));
2902
+ }
2903
+ }
2904
+ }
2905
+ return issues;
2906
+ }
2907
+ hasSeenDependencyWarning = false;
2908
+ checkOpenSourceObligations(content, file) {
2909
+ const issues = [];
2910
+ const attributionRequired = /mit|bsd|apache|isc/i.test(content) && /license|copyright/i.test(content);
2911
+ if (attributionRequired && !/notice|attribution|credits/i.test(content)) {
2912
+ issues.push(this.createIssue(
2913
+ this.generateIssueId(),
2914
+ "moderate",
2915
+ "Open source license requires attribution",
2916
+ "MIT, BSD, Apache licenses require copyright notice preservation. Ensure NOTICE/CREDITS file exists.",
2917
+ file,
2918
+ void 0,
2919
+ 0.75,
2920
+ "Open Source Attribution Requirements",
2921
+ false
2922
+ ));
2923
+ }
2924
+ if (/apache.*2\.0|apache-2\.0/i.test(content)) {
2925
+ issues.push(this.createIssue(
2926
+ this.generateIssueId(),
2927
+ "low",
2928
+ "Apache 2.0 includes patent grant provisions",
2929
+ "Apache 2.0 includes express patent license from contributors. Understand implications for your project.",
2930
+ file,
2931
+ void 0,
2932
+ 0.55,
2933
+ "Apache 2.0 Patent Grant",
2934
+ false
2935
+ ));
2936
+ }
2937
+ return issues;
2938
+ }
2939
+ // ============================================================
2940
+ // TERMS OF SERVICE & PRIVACY POLICY
2941
+ // ============================================================
2942
+ checkTermsOfService(content, file) {
2943
+ const issues = [];
2944
+ if (/signup|register|createAccount|onboard/i.test(content) && !/terms|tos|termsOfService|agreement|policy/i.test(content)) {
2945
+ issues.push(this.createIssue(
2946
+ this.generateIssueId(),
2947
+ "serious",
2948
+ "User registration without Terms of Service reference",
2949
+ "Link to Terms of Service during registration. Users must agree before account creation.",
2950
+ file,
2951
+ void 0,
2952
+ 0.85,
2953
+ "Contract Law - Terms of Service acceptance",
2954
+ false
2955
+ ));
2956
+ }
2957
+ if (/terms.*accept|agree.*terms|accept.*policy/i.test(content)) {
2958
+ if (/defaultChecked|checked.*true|value.*true/i.test(content)) {
2959
+ issues.push(this.createIssue(
2960
+ this.generateIssueId(),
2961
+ "serious",
2962
+ "Pre-checked Terms of Service acceptance",
2963
+ "ToS acceptance should require affirmative action. Do not pre-check agreement boxes.",
2964
+ file,
2965
+ void 0,
2966
+ 0.9,
2967
+ "Contract Law - Affirmative consent required",
2968
+ true
2969
+ ));
2970
+ }
2971
+ }
2972
+ return issues;
2973
+ }
2974
+ checkPrivacyPolicyRequirements(content, file) {
2975
+ const issues = [];
2976
+ const collectsData = /email|phone|address|location|deviceId|fingerprint|analytics/i.test(content);
2977
+ const hasPrivacyRef = /privacy|privacyPolicy|dataPolicy/i.test(content);
2978
+ if (collectsData && !hasPrivacyRef) {
2979
+ issues.push(this.createIssue(
2980
+ this.generateIssueId(),
2981
+ "serious",
2982
+ "Data collection without privacy policy reference",
2983
+ "Any data collection requires accessible privacy policy disclosure. Link to privacy policy.",
2984
+ file,
2985
+ void 0,
2986
+ 0.85,
2987
+ "Privacy Law - Disclosure requirements (GDPR, CCPA, CalOPPA)",
2988
+ false
2989
+ ));
2990
+ }
2991
+ if (/california|\.ca\.|\bca\b/i.test(content) && collectsData) {
2992
+ issues.push(this.createIssue(
2993
+ this.generateIssueId(),
2994
+ "moderate",
2995
+ "California users detected - CalOPPA compliance required",
2996
+ "California Online Privacy Protection Act requires conspicuous privacy policy link",
2997
+ file,
2998
+ void 0,
2999
+ 0.7,
3000
+ "CalOPPA Compliance",
3001
+ false
3002
+ ));
3003
+ }
3004
+ return issues;
3005
+ }
3006
+ // ============================================================
3007
+ // THIRD-PARTY & API COMPLIANCE
3008
+ // ============================================================
3009
+ checkAPITermsCompliance(content, file) {
3010
+ const issues = [];
3011
+ const lines = content.split("\n");
3012
+ const apiPatterns = [
3013
+ { pattern: /api\.openai\.com|openai/i, name: "OpenAI", concern: "Usage policies prohibit certain content generation" },
3014
+ { pattern: /api\.stripe\.com|stripe/i, name: "Stripe", concern: "PCI compliance and prohibited business types" },
3015
+ { pattern: /graph\.facebook\.com|facebook/i, name: "Meta/Facebook", concern: "Platform policies and data usage restrictions" },
3016
+ { pattern: /api\.twitter\.com|twitter|x\.com/i, name: "Twitter/X", concern: "API rate limits and content policies" },
3017
+ { pattern: /maps\.google|googleapis\.com\/maps/i, name: "Google Maps", concern: "Display requirements and usage restrictions" },
3018
+ { pattern: /api\.twilio\.com|twilio/i, name: "Twilio", concern: "A2P messaging compliance and opt-out requirements" },
3019
+ { pattern: /api\.sendgrid\.com|sendgrid/i, name: "SendGrid", concern: "Anti-spam compliance required" },
3020
+ { pattern: /api\.aws\.amazon|amazonaws\.com/i, name: "AWS", concern: "Acceptable Use Policy and export controls" },
3021
+ { pattern: /youtube|youtu\.be/i, name: "YouTube", concern: "API Services Terms of Service, embedding restrictions" }
3022
+ ];
3023
+ for (let i = 0; i < lines.length; i++) {
3024
+ const line = lines[i];
3025
+ for (const api of apiPatterns) {
3026
+ if (api.pattern.test(line)) {
3027
+ issues.push(this.createIssue(
3028
+ this.generateIssueId(),
3029
+ "moderate",
3030
+ `${api.name} API usage detected - review terms`,
3031
+ `${api.concern}. Verify compliance with ${api.name} Terms of Service.`,
3032
+ file,
3033
+ i + 1,
3034
+ 0.7,
3035
+ `${api.name} API Terms of Service`,
3036
+ false
3037
+ ));
3038
+ break;
3039
+ }
3040
+ }
3041
+ }
3042
+ return issues;
3043
+ }
3044
+ checkThirdPartyLicenses(content, file) {
3045
+ const issues = [];
3046
+ if (/\.(ttf|otf|woff|woff2)/i.test(content)) {
3047
+ issues.push(this.createIssue(
3048
+ this.generateIssueId(),
3049
+ "moderate",
3050
+ "Font files detected - verify font licensing",
3051
+ "Many fonts have commercial licensing requirements. Verify you have proper license for web/app use.",
3052
+ file,
3053
+ void 0,
3054
+ 0.75,
3055
+ "Font Licensing - Commercial use restrictions",
3056
+ false
3057
+ ));
3058
+ }
3059
+ if (/unsplash|pexels|shutterstock|gettyimages|istock/i.test(content)) {
3060
+ issues.push(this.createIssue(
3061
+ this.generateIssueId(),
3062
+ "low",
3063
+ "Stock asset service detected - verify attribution requirements",
3064
+ "Check license terms for attribution requirements and usage restrictions",
3065
+ file,
3066
+ void 0,
3067
+ 0.6,
3068
+ "Stock Asset Licensing",
3069
+ false
3070
+ ));
3071
+ }
3072
+ return issues;
3073
+ }
3074
+ // ============================================================
3075
+ // INTELLECTUAL PROPERTY
3076
+ // ============================================================
3077
+ checkCopyrightCompliance(content, file) {
3078
+ const issues = [];
3079
+ const lines = content.split("\n");
3080
+ for (let i = 0; i < lines.length; i++) {
3081
+ const line = lines[i];
3082
+ if (/stackoverflow|copied from|adapted from|based on|from:?\s*http/i.test(line)) {
3083
+ issues.push(this.createIssue(
3084
+ this.generateIssueId(),
3085
+ "low",
3086
+ "Code attribution comment detected - verify license",
3087
+ "Ensure copied code is properly licensed for your use. Stack Overflow code is CC BY-SA licensed.",
3088
+ file,
3089
+ i + 1,
3090
+ 0.6,
3091
+ "Copyright - Code attribution and licensing",
3092
+ false
3093
+ ));
3094
+ }
3095
+ if (/logo|brand|trademark|®|™/i.test(line)) {
3096
+ issues.push(this.createIssue(
3097
+ this.generateIssueId(),
3098
+ "low",
3099
+ "Trademark/branding reference detected",
3100
+ "Verify trademark usage complies with brand guidelines and trademark law",
3101
+ file,
3102
+ i + 1,
3103
+ 0.5,
3104
+ "Trademark Law Compliance",
3105
+ false
3106
+ ));
3107
+ }
3108
+ }
3109
+ return issues;
3110
+ }
3111
+ checkTrademarkUsage(content, file) {
3112
+ const issues = [];
3113
+ const trademarks = [
3114
+ { pattern: /\bapple\b|iphone|ipad|ios|app\s*store/i, name: "Apple", guide: "Apple Trademark Guidelines" },
3115
+ { pattern: /\bgoogle\b|android|play\s*store|chrome/i, name: "Google", guide: "Google Brand Guidelines" },
3116
+ { pattern: /\bmicrosoft\b|windows|azure/i, name: "Microsoft", guide: "Microsoft Trademark Guidelines" },
3117
+ { pattern: /\bamazon\b|aws|alexa/i, name: "Amazon", guide: "Amazon Trademark Guidelines" }
3118
+ ];
3119
+ for (const tm of trademarks) {
3120
+ if (tm.pattern.test(content)) {
3121
+ issues.push(this.createIssue(
3122
+ this.generateIssueId(),
3123
+ "low",
3124
+ `${tm.name} trademark reference detected`,
3125
+ `If using ${tm.name} branding, review ${tm.guide} for proper usage`,
3126
+ file,
3127
+ void 0,
3128
+ 0.5,
3129
+ `${tm.name} Trademark Guidelines`,
3130
+ false
3131
+ ));
3132
+ break;
3133
+ }
3134
+ }
3135
+ return issues;
3136
+ }
3137
+ // ============================================================
3138
+ // ACCESSIBILITY (ADA/SECTION 508)
3139
+ // ============================================================
3140
+ checkAccessibilityCompliance(content, file) {
3141
+ const issues = [];
3142
+ const lines = content.split("\n");
3143
+ for (let i = 0; i < lines.length; i++) {
3144
+ const line = lines[i];
3145
+ if (/<img/i.test(line) && !/alt\s*=/i.test(line)) {
3146
+ issues.push(this.createIssue(
3147
+ this.generateIssueId(),
3148
+ "moderate",
3149
+ "Image without alt text - accessibility violation",
3150
+ "Add alt attribute to images for screen reader accessibility (ADA/WCAG 2.1)",
3151
+ file,
3152
+ i + 1,
3153
+ 0.8,
3154
+ "ADA/WCAG 2.1 - Images require alternative text",
3155
+ true
3156
+ ));
3157
+ }
3158
+ if (/onClick|@click/i.test(line) && /<(div|span|p)\s/i.test(line) && !/role=|tabIndex|tabindex/i.test(line)) {
3159
+ issues.push(this.createIssue(
3160
+ this.generateIssueId(),
3161
+ "moderate",
3162
+ "Click handler on non-interactive element without keyboard support",
3163
+ 'Add role="button" and tabIndex for keyboard accessibility',
3164
+ file,
3165
+ i + 1,
3166
+ 0.75,
3167
+ "WCAG 2.1.1 - Keyboard accessible",
3168
+ true
3169
+ ));
3170
+ }
3171
+ if (/color.*:.*red|color.*:.*green/i.test(line) && /error|success|valid|invalid/i.test(line)) {
3172
+ issues.push(this.createIssue(
3173
+ this.generateIssueId(),
3174
+ "moderate",
3175
+ "Color may be sole indicator of status",
3176
+ "Do not use color alone to convey information. Add icons or text for colorblind users.",
3177
+ file,
3178
+ i + 1,
3179
+ 0.7,
3180
+ "WCAG 1.4.1 - Use of Color",
3181
+ false
3182
+ ));
3183
+ }
3184
+ }
3185
+ if (/<video|video\s*:/i.test(content) && !/caption|subtitle|track\s+kind/i.test(content)) {
3186
+ issues.push(this.createIssue(
3187
+ this.generateIssueId(),
3188
+ "moderate",
3189
+ "Video content without captions/subtitles",
3190
+ "Provide captions for video content (ADA/WCAG 1.2.2)",
3191
+ file,
3192
+ void 0,
3193
+ 0.75,
3194
+ "WCAG 1.2.2 - Captions (Prerecorded)",
3195
+ false
3196
+ ));
3197
+ }
3198
+ return issues;
3199
+ }
3200
+ // ============================================================
3201
+ // DATA PROTECTION (GDPR/CCPA - GENERAL)
3202
+ // ============================================================
3203
+ checkDataProtection(content, file) {
3204
+ const issues = [];
3205
+ const lines = content.split("\n");
3206
+ for (let i = 0; i < lines.length; i++) {
3207
+ const line = lines[i];
3208
+ const lineNumber = i + 1;
3209
+ if (/email|phone|location|deviceId/i.test(line) && /save|store|insert|create|post/i.test(line) && !/consent|optIn|agreement/i.test(content)) {
3210
+ issues.push(this.createIssue(
3211
+ this.generateIssueId(),
3212
+ "serious",
3213
+ "Personal data collection without consent verification",
3214
+ "Implement consent collection before storing personal data (GDPR Article 6, CCPA)",
3215
+ file,
3216
+ lineNumber,
3217
+ 0.8,
3218
+ "GDPR Article 6 / CCPA - Lawful basis for processing",
3219
+ false
3220
+ ));
3221
+ }
3222
+ if (/analytics|gtag|fbq|pixel|tracking|mixpanel|amplitude|segment/i.test(line) && !/consent|optIn|cookie/i.test(content)) {
3223
+ issues.push(this.createIssue(
3224
+ this.generateIssueId(),
3225
+ "serious",
3226
+ "Analytics/tracking without consent management",
3227
+ "Implement cookie consent before loading tracking scripts (ePrivacy/GDPR)",
3228
+ file,
3229
+ lineNumber,
3230
+ 0.85,
3231
+ "GDPR Article 7 / ePrivacy Directive",
3232
+ false
3233
+ ));
3234
+ }
3235
+ }
3236
+ if (/userData|userProfile|account/i.test(content)) {
3237
+ if (!/export|download|portability/i.test(content)) {
3238
+ issues.push(this.createIssue(
3239
+ this.generateIssueId(),
3240
+ "moderate",
3241
+ "Consider implementing data portability",
3242
+ "Users have right to export their data in machine-readable format (GDPR Article 20)",
3243
+ file,
3244
+ void 0,
3245
+ 0.65,
3246
+ "GDPR Article 20 - Right to data portability",
3247
+ false
3248
+ ));
3249
+ }
3250
+ }
3251
+ if (/deleteAccount|removeAccount|closeAccount/i.test(content)) {
3252
+ } else if (/account|user.*profile/i.test(content)) {
3253
+ issues.push(this.createIssue(
3254
+ this.generateIssueId(),
3255
+ "moderate",
3256
+ "Account deletion functionality may be missing",
3257
+ "Users have right to delete their accounts and data (GDPR Article 17)",
3258
+ file,
3259
+ void 0,
3260
+ 0.7,
3261
+ "GDPR Article 17 - Right to erasure",
3262
+ false
3263
+ ));
3264
+ }
3265
+ return issues;
3266
+ }
3267
+ checkConsentManagement(content, file) {
3268
+ const issues = [];
3269
+ const lines = content.split("\n");
3270
+ for (let i = 0; i < lines.length; i++) {
3271
+ const line = lines[i];
3272
+ const lineNumber = i + 1;
3273
+ if (/checked|defaultChecked|defaultValue.*true/i.test(line) && /consent|newsletter|marketing/i.test(line)) {
3274
+ issues.push(this.createIssue(
3275
+ this.generateIssueId(),
3276
+ "serious",
3277
+ "Pre-checked consent detected",
3278
+ "Consent must be freely given via affirmative action. Remove default checked state.",
3279
+ file,
3280
+ lineNumber,
3281
+ 0.9,
3282
+ "GDPR Recital 32 - No pre-ticked consent boxes",
3283
+ true
3284
+ ));
3285
+ }
3286
+ if (/&&.*consent.*&&|all.*agree|single.*checkbox/i.test(line)) {
3287
+ issues.push(this.createIssue(
3288
+ this.generateIssueId(),
3289
+ "moderate",
3290
+ "Potentially bundled consent",
3291
+ "Separate consents for different purposes (marketing vs. essential)",
3292
+ file,
3293
+ lineNumber,
3294
+ 0.7,
3295
+ "GDPR - Granular consent requirements",
3296
+ false
3297
+ ));
3298
+ }
3299
+ }
3300
+ return issues;
3301
+ }
3302
+ checkDataRetention(content, file) {
3303
+ const issues = [];
3304
+ if (/store|save|persist|database/i.test(content) && !/delete|expire|retention|ttl|expiresAt|purge/i.test(content)) {
3305
+ issues.push(this.createIssue(
3306
+ this.generateIssueId(),
3307
+ "moderate",
3308
+ "No data retention/deletion policy detected",
3309
+ "Implement data retention limits and automatic deletion procedures",
3310
+ file,
3311
+ void 0,
3312
+ 0.7,
3313
+ "GDPR Article 5(1)(e) - Storage limitation principle",
3314
+ false
3315
+ ));
3316
+ }
3317
+ return issues;
3318
+ }
3319
+ // ============================================================
3320
+ // E-COMMERCE & CONSUMER PROTECTION
3321
+ // ============================================================
3322
+ checkECommerceCompliance(content, file) {
3323
+ const issues = [];
3324
+ if (/price|cost|\$|€|£/i.test(content) && /product|item|cart|checkout/i.test(content)) {
3325
+ if (!/tax|vat|total|shipping/i.test(content)) {
3326
+ issues.push(this.createIssue(
3327
+ this.generateIssueId(),
3328
+ "moderate",
3329
+ "Price display may not include all costs",
3330
+ "Display total price including taxes and fees before purchase (Consumer protection laws)",
3331
+ file,
3332
+ void 0,
3333
+ 0.75,
3334
+ "FTC / EU Consumer Rights Directive - Price transparency",
3335
+ false
3336
+ ));
3337
+ }
3338
+ }
3339
+ if (/checkout|purchase|buy|order/i.test(content)) {
3340
+ if (!/confirm|review|final/i.test(content)) {
3341
+ issues.push(this.createIssue(
3342
+ this.generateIssueId(),
3343
+ "low",
3344
+ "Consider clear purchase confirmation step",
3345
+ "Provide order review before final purchase to prevent accidental orders",
3346
+ file,
3347
+ void 0,
3348
+ 0.6,
3349
+ "Consumer Protection - Clear purchase flow",
3350
+ false
3351
+ ));
3352
+ }
3353
+ }
3354
+ return issues;
3355
+ }
3356
+ checkPaymentCompliance(content, file) {
3357
+ const issues = [];
3358
+ const lines = content.split("\n");
3359
+ for (let i = 0; i < lines.length; i++) {
3360
+ const line = lines[i];
3361
+ if (/cardNumber|cvv|cvc|credit.*card|card.*number/i.test(line) && !/stripe|braintree|square|paypal|token/i.test(content)) {
3362
+ issues.push(this.createIssue(
3363
+ this.generateIssueId(),
3364
+ "critical",
3365
+ "Direct payment card handling detected",
3366
+ "Use PCI-compliant payment processor (Stripe, Braintree, etc.) for card handling. Never store raw card data.",
3367
+ file,
3368
+ i + 1,
3369
+ 0.95,
3370
+ "PCI DSS Compliance - Use tokenization",
3371
+ true
3372
+ ));
3373
+ }
3374
+ if (/subscription|recurring|autoRenew/i.test(line)) {
3375
+ if (!/cancel|unsubscribe|manage/i.test(content)) {
3376
+ issues.push(this.createIssue(
3377
+ this.generateIssueId(),
3378
+ "serious",
3379
+ "Subscription without clear cancellation method",
3380
+ "FTC requires clear and simple cancellation process for subscriptions",
3381
+ file,
3382
+ i + 1,
3383
+ 0.8,
3384
+ "FTC - Click-to-Cancel Rule / Restore Online Shoppers Confidence Act",
3385
+ false
3386
+ ));
3387
+ }
3388
+ }
3389
+ }
3390
+ return issues;
3391
+ }
3392
+ checkRefundPolicy(content, file) {
3393
+ const issues = [];
3394
+ if (/purchase|payment|checkout|order/i.test(content)) {
3395
+ if (!/refund|return|cancellation|moneyBack/i.test(content)) {
3396
+ issues.push(this.createIssue(
3397
+ this.generateIssueId(),
3398
+ "moderate",
3399
+ "No refund/return policy reference found",
3400
+ "Link to refund policy during checkout (required in EU, recommended globally)",
3401
+ file,
3402
+ void 0,
3403
+ 0.7,
3404
+ "EU Consumer Rights Directive - Return policy disclosure",
3405
+ false
3406
+ ));
3407
+ }
3408
+ }
3409
+ return issues;
3410
+ }
3411
+ // ============================================================
3412
+ // MARKETING & ADVERTISING COMPLIANCE
3413
+ // ============================================================
3414
+ checkMarketingCompliance(content, file) {
3415
+ const issues = [];
3416
+ const lines = content.split("\n");
3417
+ for (let i = 0; i < lines.length; i++) {
3418
+ const line = lines[i];
3419
+ if (/sendEmail|mailchimp|sendgrid|newsletter|marketing.*email/i.test(line)) {
3420
+ if (!/unsubscribe|optOut|preferences/i.test(content)) {
3421
+ issues.push(this.createIssue(
3422
+ this.generateIssueId(),
3423
+ "serious",
3424
+ "Marketing email without unsubscribe mechanism",
3425
+ "CAN-SPAM requires clear opt-out in every marketing email",
3426
+ file,
3427
+ i + 1,
3428
+ 0.85,
3429
+ "CAN-SPAM Act - Unsubscribe requirements",
3430
+ false
3431
+ ));
3432
+ }
3433
+ }
3434
+ if (/sms|textMessage|twilio.*sms/i.test(line) && /marketing|promo|offer/i.test(content)) {
3435
+ if (!/consent|optIn|tcpa/i.test(content)) {
3436
+ issues.push(this.createIssue(
3437
+ this.generateIssueId(),
3438
+ "critical",
3439
+ "SMS marketing requires express written consent",
3440
+ "TCPA requires prior express written consent for marketing SMS. Violations up to $1,500 per text.",
3441
+ file,
3442
+ i + 1,
3443
+ 0.95,
3444
+ "TCPA - Telephone Consumer Protection Act",
3445
+ false
3446
+ ));
3447
+ }
3448
+ }
3449
+ }
3450
+ return issues;
3451
+ }
3452
+ checkFTCDisclosure(content, file) {
3453
+ const issues = [];
3454
+ if (/affiliate|referral|sponsored|partner.*link|commission/i.test(content)) {
3455
+ if (!/disclosure|ad|sponsored|affiliate.*disclosure/i.test(content)) {
3456
+ issues.push(this.createIssue(
3457
+ this.generateIssueId(),
3458
+ "serious",
3459
+ "Affiliate/sponsored content without disclosure",
3460
+ "FTC requires clear disclosure of material connections (affiliate links, sponsorships)",
3461
+ file,
3462
+ void 0,
3463
+ 0.85,
3464
+ "FTC Endorsement Guidelines - Material connection disclosure",
3465
+ false
3466
+ ));
3467
+ }
3468
+ }
3469
+ if (/testimonial|review|rating|feedback/i.test(content)) {
3470
+ if (/fake|generated|synthetic|ai.*review/i.test(content)) {
3471
+ issues.push(this.createIssue(
3472
+ this.generateIssueId(),
3473
+ "critical",
3474
+ "Fake reviews/testimonials detected",
3475
+ "FTC prohibits fake reviews. Ensure all testimonials are genuine and typical.",
3476
+ file,
3477
+ void 0,
3478
+ 0.95,
3479
+ "FTC Act - Deceptive advertising prohibition",
3480
+ true
3481
+ ));
3482
+ }
3483
+ }
3484
+ return issues;
3485
+ }
3486
+ // ============================================================
3487
+ // AGE RESTRICTIONS & CHILD SAFETY
3488
+ // ============================================================
3489
+ checkAgeRestrictions(content, file) {
3490
+ const issues = [];
3491
+ const lines = content.split("\n");
3492
+ for (let i = 0; i < lines.length; i++) {
3493
+ const line = lines[i];
3494
+ if (/kids|children|child|minor|under.*13|age.*13/i.test(line)) {
3495
+ if (!/coppa|parentalConsent|verifiableConsent/i.test(content)) {
3496
+ issues.push(this.createIssue(
3497
+ this.generateIssueId(),
3498
+ "critical",
3499
+ "Child-directed content without COPPA compliance",
3500
+ "COPPA requires verifiable parental consent before collecting data from children under 13",
3501
+ file,
3502
+ i + 1,
3503
+ 0.95,
3504
+ "COPPA - Children's Online Privacy Protection Act",
3505
+ false
3506
+ ));
3507
+ }
3508
+ }
3509
+ if (/alcohol|gambling|cannabis|adult|18\+|21\+/i.test(line)) {
3510
+ if (!/ageVerification|verifyAge|dateOfBirth|ageGate/i.test(content)) {
3511
+ issues.push(this.createIssue(
3512
+ this.generateIssueId(),
3513
+ "serious",
3514
+ "Age-restricted content without age verification",
3515
+ "Implement age verification for alcohol, gambling, or adult content",
3516
+ file,
3517
+ i + 1,
3518
+ 0.85,
3519
+ "Age-restricted content regulations",
3520
+ false
3521
+ ));
3522
+ }
3523
+ }
3524
+ }
3525
+ return issues;
3526
+ }
3527
+ // ============================================================
3528
+ // EXPORT CONTROLS & ENCRYPTION
3529
+ // ============================================================
3530
+ checkExportControls(content, file) {
3531
+ const issues = [];
3532
+ if (/aes-256|rsa|elliptic.*curve|ed25519|crypto\.subtle/i.test(content)) {
3533
+ issues.push(this.createIssue(
3534
+ this.generateIssueId(),
3535
+ "low",
3536
+ "Strong encryption detected - be aware of export controls",
3537
+ "Encryption software may have export control requirements (EAR/ITAR). Review if shipping internationally.",
3538
+ file,
3539
+ void 0,
3540
+ 0.5,
3541
+ "Export Administration Regulations (EAR) - Encryption controls",
3542
+ false
3543
+ ));
3544
+ }
3545
+ if (/country|region|locale/i.test(content) && /block|restrict|prohibit/i.test(content)) {
3546
+ if (!/ofac|sanction|embargo/i.test(content)) {
3547
+ issues.push(this.createIssue(
3548
+ this.generateIssueId(),
3549
+ "moderate",
3550
+ "Consider OFAC sanctions compliance",
3551
+ "If operating internationally, implement OFAC sanctions screening",
3552
+ file,
3553
+ void 0,
3554
+ 0.65,
3555
+ "OFAC - Sanctions compliance",
3556
+ false
3557
+ ));
3558
+ }
3559
+ }
3560
+ return issues;
3561
+ }
3562
+ // ============================================================
3563
+ // USER-GENERATED CONTENT & MODERATION
3564
+ // ============================================================
3565
+ checkUGCCompliance(content, file) {
3566
+ const issues = [];
3567
+ if (/userContent|userPost|comment|review|upload/i.test(content)) {
3568
+ if (!/moderate|report|flag|review.*queue/i.test(content)) {
3569
+ issues.push(this.createIssue(
3570
+ this.generateIssueId(),
3571
+ "moderate",
3572
+ "User-generated content without moderation system",
3573
+ "Implement content moderation to prevent liability for user content",
3574
+ file,
3575
+ void 0,
3576
+ 0.7,
3577
+ "Section 230 / Platform liability",
3578
+ false
3579
+ ));
3580
+ }
3581
+ }
3582
+ if (/upload|file.*share|media.*upload/i.test(content)) {
3583
+ if (!/dmca|takedown|copyright.*notice/i.test(content)) {
3584
+ issues.push(this.createIssue(
3585
+ this.generateIssueId(),
3586
+ "moderate",
3587
+ "File uploads without DMCA takedown process",
3588
+ "Implement DMCA takedown procedures to maintain safe harbor protection",
3589
+ file,
3590
+ void 0,
3591
+ 0.75,
3592
+ "DMCA Safe Harbor Requirements",
3593
+ false
3594
+ ));
3595
+ }
3596
+ }
3597
+ return issues;
3598
+ }
3599
+ checkContentModeration(content, file) {
3600
+ const issues = [];
3601
+ if (/chat|message|post|comment/i.test(content) && /public|share|publish/i.test(content)) {
3602
+ if (!/filter|moderate|block|detect/i.test(content)) {
3603
+ issues.push(this.createIssue(
3604
+ this.generateIssueId(),
3605
+ "moderate",
3606
+ "Public content without content filtering",
3607
+ "Consider content filtering for CSAM, hate speech, and illegal content",
3608
+ file,
3609
+ void 0,
3610
+ 0.7,
3611
+ "Platform Safety - Content moderation best practices",
3612
+ false
3613
+ ));
3614
+ }
3615
+ }
3616
+ return issues;
3617
+ }
3618
+ // ============================================================
3619
+ // CONTRACT & AGREEMENT PATTERNS
3620
+ // ============================================================
3621
+ checkContractPatterns(content, file) {
3622
+ const issues = [];
3623
+ if (/terms|eula|license.*agreement/i.test(content) && /accept|agree/i.test(content)) {
3624
+ if (!/scroll|read|acknowledge/i.test(content)) {
3625
+ issues.push(this.createIssue(
3626
+ this.generateIssueId(),
3627
+ "low",
3628
+ "Agreement acceptance without scroll/read verification",
3629
+ "Consider requiring users scroll through terms for stronger enforceability",
3630
+ file,
3631
+ void 0,
3632
+ 0.55,
3633
+ "Contract Law - Browse-wrap vs click-wrap enforceability",
3634
+ false
3635
+ ));
3636
+ }
3637
+ }
3638
+ if (/arbitration|dispute.*resolution/i.test(content)) {
3639
+ if (!/opt.*out|waiver|class.*action/i.test(content)) {
3640
+ issues.push(this.createIssue(
3641
+ this.generateIssueId(),
3642
+ "low",
3643
+ "Arbitration clause without opt-out provision",
3644
+ "Consider arbitration opt-out period for consumer contracts",
3645
+ file,
3646
+ void 0,
3647
+ 0.5,
3648
+ "Arbitration - Consumer protection considerations",
3649
+ false
3650
+ ));
3651
+ }
3652
+ }
3653
+ return issues;
3654
+ }
3655
+ checkClickwrapCompliance(content, file) {
3656
+ const issues = [];
3657
+ if (/accept.*terms|agree.*terms/i.test(content)) {
3658
+ if (!/timestamp|recordConsent|auditLog|agreement.*record/i.test(content)) {
3659
+ issues.push(this.createIssue(
3660
+ this.generateIssueId(),
3661
+ "moderate",
3662
+ "Terms acceptance without consent recording",
3663
+ "Record timestamp and version of terms accepted for legal enforceability",
3664
+ file,
3665
+ void 0,
3666
+ 0.7,
3667
+ "Contract Law - Evidence of acceptance",
3668
+ false
3669
+ ));
3670
+ }
3671
+ }
3672
+ return issues;
3673
+ }
3674
+ // ============================================================
3675
+ // JURISDICTIONAL & INTERNATIONAL COMPLIANCE
3676
+ // ============================================================
3677
+ checkJurisdictionalCompliance(content, file) {
3678
+ const issues = [];
3679
+ if (/europe|eu|gdpr|\.eu\b|germany|france|spain/i.test(content)) {
3680
+ issues.push(this.createIssue(
3681
+ this.generateIssueId(),
3682
+ "moderate",
3683
+ "EU market detected - ensure GDPR compliance",
3684
+ "EU users require GDPR-compliant data handling, cookie consent, and DPO if processing at scale",
3685
+ file,
3686
+ void 0,
3687
+ 0.75,
3688
+ "GDPR - EU data protection requirements",
3689
+ false
3690
+ ));
3691
+ }
3692
+ if (/transfer|export|crossBorder/i.test(content) && /data|user|personal/i.test(content)) {
3693
+ if (!/sccs|adequacy|binding.*corporate/i.test(content)) {
3694
+ issues.push(this.createIssue(
3695
+ this.generateIssueId(),
3696
+ "moderate",
3697
+ "Cross-border data transfer without legal basis",
3698
+ "International data transfers require SCCs, adequacy decisions, or other legal mechanisms",
3699
+ file,
3700
+ void 0,
3701
+ 0.7,
3702
+ "GDPR Chapter V - International data transfers",
3703
+ false
3704
+ ));
3705
+ }
3706
+ }
3707
+ if (/brazil|brasil|\.br\b|lgpd/i.test(content)) {
3708
+ issues.push(this.createIssue(
3709
+ this.generateIssueId(),
3710
+ "moderate",
3711
+ "Brazil market detected - ensure LGPD compliance",
3712
+ "LGPD (Lei Geral de Prote\xE7\xE3o de Dados) applies to Brazilian user data",
3713
+ file,
3714
+ void 0,
3715
+ 0.7,
3716
+ "LGPD - Brazil data protection law",
3717
+ false
3718
+ ));
3719
+ }
3720
+ return issues;
3721
+ }
3722
+ // ============================================================
3723
+ // SECURITY DISCLOSURE & LIABILITY
3724
+ // ============================================================
3725
+ checkSecurityDisclosure(content, file) {
3726
+ const issues = [];
3727
+ if (/security|vulnerability|disclosure/i.test(file)) {
3728
+ } else if (/contact|report|bug/i.test(content)) {
3729
+ if (!/security|vulnerability/i.test(content)) {
3730
+ issues.push(this.createIssue(
3731
+ this.generateIssueId(),
3732
+ "low",
3733
+ "Consider adding security vulnerability disclosure process",
3734
+ "Add security.txt or responsible disclosure policy for security researchers",
3735
+ file,
3736
+ void 0,
3737
+ 0.5,
3738
+ "Security Best Practice - Vulnerability disclosure",
3739
+ false
3740
+ ));
3741
+ }
3742
+ }
3743
+ return issues;
3744
+ }
3745
+ checkLiabilityProtections(content, file) {
3746
+ const issues = [];
3747
+ if (/license|readme|terms/i.test(file.toLowerCase())) {
3748
+ if (!/warranty|as.?is|without.*guarantee|liability/i.test(content)) {
3749
+ issues.push(this.createIssue(
3750
+ this.generateIssueId(),
3751
+ "low",
3752
+ "Consider adding warranty disclaimer",
3753
+ "Add appropriate warranty disclaimers and limitation of liability clauses",
3754
+ file,
3755
+ void 0,
3756
+ 0.5,
3757
+ "Warranty - Software liability protection",
3758
+ false
3759
+ ));
3760
+ }
3761
+ }
3762
+ if (/userContent|thirdParty.*content/i.test(content)) {
3763
+ if (!/indemnify|hold.*harmless/i.test(content)) {
3764
+ issues.push(this.createIssue(
3765
+ this.generateIssueId(),
3766
+ "low",
3767
+ "Consider indemnification clause for user content",
3768
+ "Add indemnification provisions for user-generated content in Terms of Service",
3769
+ file,
3770
+ void 0,
3771
+ 0.5,
3772
+ "Indemnification - User content liability",
2151
3773
  false
2152
3774
  ));
2153
3775
  }
@@ -5392,4 +7014,4 @@ export {
5392
7014
  CustomAgent,
5393
7015
  getAgentRegistry
5394
7016
  };
5395
- //# sourceMappingURL=chunk-ZTQ2QWUQ.js.map
7017
+ //# sourceMappingURL=chunk-QFTSX2BX.js.map