@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.
- package/README.md +187 -8
- package/dist/{chunk-ZTQ2QWUQ.js → chunk-QFTSX2BX.js} +1831 -209
- package/dist/chunk-QFTSX2BX.js.map +1 -0
- package/dist/{chunk-OKCVAJDR.js → chunk-VSCPOIWS.js} +2 -2
- package/dist/cli/yolo-daemon.js +2 -2
- package/dist/index.js +2 -2
- package/dist/workers/agent-worker.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZTQ2QWUQ.js.map +0 -1
- /package/dist/{chunk-OKCVAJDR.js.map → chunk-VSCPOIWS.js.map} +0 -0
|
@@ -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
|
|
1372
|
-
const g = parseInt(normalized
|
|
1373
|
-
const b = parseInt(normalized
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
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
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
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
|
-
|
|
2052
|
-
|
|
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
|
-
|
|
2055
|
-
0.
|
|
2056
|
-
|
|
2057
|
-
|
|
2121
|
+
void 0,
|
|
2122
|
+
0.9,
|
|
2123
|
+
void 0,
|
|
2124
|
+
true
|
|
2058
2125
|
));
|
|
2059
2126
|
}
|
|
2060
|
-
|
|
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
|
-
"
|
|
2064
|
-
"
|
|
2065
|
-
|
|
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
|
-
|
|
2068
|
-
0.
|
|
2069
|
-
|
|
2070
|
-
|
|
2166
|
+
void 0,
|
|
2167
|
+
0.75,
|
|
2168
|
+
void 0,
|
|
2169
|
+
true
|
|
2071
2170
|
));
|
|
2072
2171
|
}
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
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
|
-
"
|
|
2079
|
-
|
|
2080
|
-
|
|
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.
|
|
2084
|
-
|
|
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
|
-
|
|
2198
|
+
/**
|
|
2199
|
+
* Analyze contrast ratios between colors
|
|
2200
|
+
*/
|
|
2201
|
+
analyzeContrastRatios(content, file) {
|
|
2093
2202
|
const issues = [];
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
0
|
|
2116
|
-
|
|
2117
|
-
|
|
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
|
-
|
|
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 (/
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
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
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
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-
|
|
7017
|
+
//# sourceMappingURL=chunk-QFTSX2BX.js.map
|