diff --git a/frontend/index.html b/frontend/index.html index fa307b2d..f5bae965 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1141,6 +1141,31 @@ font-family: var(--font-mono); } + .complexity-chip { + padding: 2px 10px; + border-radius: 99px; + font-size: 0.68rem; + font-weight: 700; + letter-spacing: 0.02em; + cursor: help; + white-space: nowrap; + } + + .complexity-chip.complexity-easy { + background: rgba(34, 197, 94, 0.15); + color: #4ade80; + } + + .complexity-chip.complexity-medium { + background: rgba(245, 200, 66, 0.15); + color: var(--yellow); + } + + .complexity-chip.complexity-hard { + background: rgba(242, 87, 87, 0.15); + color: var(--red); + } + .issue-type { font-size: 0.88rem; font-weight: 600 @@ -4066,6 +4091,51 @@

Project Health Score

`; } + // ── Issue Complexity Badge ── + // Pure frontend heuristic: scores an issue using fields already present on the + // issue object (severity, description length, suggestion presence, keywords) + // and maps it to Easy / Medium / Hard. No backend involvement required. + const COMPLEXITY_KEYWORDS = { + hard: ['race condition', 'deadlock', 'memory leak', 'security', 'vulnerability', 'concurrency', 'thread', 'async', 'architecture', 'refactor', 'sql injection', 'recursion', 'algorithm'], + medium: ['null', 'undefined', 'exception', 'error handling', 'type', 'performance', 'validation', 'edge case', 'logic'], + }; + + function scoreIssueComplexity(issue) { + let score = 0; + const desc = (issue.description || '').toLowerCase(); + const type = (issue.type || '').toLowerCase(); + const text = `${desc} ${type}`; + + if (issue.severity === 'error') score += 3; + else if (issue.severity === 'warning') score += 2; + else if (issue.severity === 'info') score += 1; + + const len = desc.length; + if (len > 160) score += 3; + else if (len > 80) score += 2; + else if (len > 0) score += 1; + + if (COMPLEXITY_KEYWORDS.hard.some(k => text.includes(k))) score += 3; + else if (COMPLEXITY_KEYWORDS.medium.some(k => text.includes(k))) score += 1; + + if (!issue.suggestion) score += 1; + + return score; + } + + function getIssueComplexity(issue) { + const score = scoreIssueComplexity(issue); + if (score >= 7) return { level: 'hard', label: 'Hard', emoji: '🔴', score }; + if (score >= 4) return { level: 'medium', label: 'Medium', emoji: '🟡', score }; + return { level: 'easy', label: 'Easy', emoji: '🟢', score }; + } + + function renderComplexityBadge(issue) { + const c = getIssueComplexity(issue); + const tooltip = `Estimated complexity: ${c.label} (score ${c.score}/11) — based on severity, description length, keywords, and whether a fix suggestion is provided.`; + return `${c.emoji} ${c.label}`; + } + function renderDebug(dbg) { document.getElementById('emptyDebug').style.display = 'none'; const el = document.getElementById('debugResult'); @@ -4106,6 +4176,7 @@

Project Health Score

${issue.type} + ${renderComplexityBadge(issue)} ${issue.line ? `Line ${issue.line}` : ''} @@ -5876,4 +5947,4 @@

Live Collaboration

- \ No newline at end of file + diff --git a/frontend/script.js b/frontend/script.js index be23ee3e..7c06caf9 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -426,6 +426,55 @@ async function runAnalysis() { } } +// ── Issue Complexity Badge ── +// Pure frontend heuristic: scores an issue using fields already present on the +// issue object (severity, description length, suggestion presence, keywords) +// and maps it to Easy / Medium / Hard. No backend involvement required. +const COMPLEXITY_KEYWORDS = { + hard: ['race condition', 'deadlock', 'memory leak', 'security', 'vulnerability', 'concurrency', 'thread', 'async', 'architecture', 'refactor', 'sql injection', 'recursion', 'algorithm'], + medium: ['null', 'undefined', 'exception', 'error handling', 'type', 'performance', 'validation', 'edge case', 'logic'], +}; + +function scoreIssueComplexity(issue) { + let score = 0; + const desc = (issue.description || '').toLowerCase(); + const type = (issue.type || '').toLowerCase(); + const text = `${desc} ${type}`; + + // Severity contributes the most signal when available. + if (issue.severity === 'error') score += 3; + else if (issue.severity === 'warning') score += 2; + else if (issue.severity === 'info') score += 1; + + // Longer descriptions tend to indicate more nuanced/complex issues. + const len = desc.length; + if (len > 160) score += 3; + else if (len > 80) score += 2; + else if (len > 0) score += 1; + + // Keyword heuristics. + if (COMPLEXITY_KEYWORDS.hard.some(k => text.includes(k))) score += 3; + else if (COMPLEXITY_KEYWORDS.medium.some(k => text.includes(k))) score += 1; + + // A missing fix suggestion usually means the issue needs more judgment to resolve. + if (!issue.suggestion) score += 1; + + return score; +} + +function getIssueComplexity(issue) { + const score = scoreIssueComplexity(issue); + if (score >= 7) return { level: 'hard', label: 'Hard', emoji: '🔴', score }; + if (score >= 4) return { level: 'medium', label: 'Medium', emoji: '🟡', score }; + return { level: 'easy', label: 'Easy', emoji: '🟢', score }; +} + +function renderComplexityBadge(issue) { + const c = getIssueComplexity(issue); + const tooltip = `Estimated complexity: ${c.label} (score ${c.score}/11) — based on severity, description length, keywords, and whether a fix suggestion is provided.`; + return `${c.emoji} ${c.label}`; +} + // ── Render Output ── function renderResult(data, mode) { let html = ''; @@ -457,6 +506,7 @@ function renderResult(data, mode) { ? '✓ No issues found' : issues.map(i => `
${escHtml(i.type || 'Issue')} + ${renderComplexityBadge(i)}

${escHtml(i.description || '')}

${i.suggestion ? `

Fix: ${escHtml(i.suggestion)}

` : ''}
`).join('')} @@ -503,6 +553,7 @@ function renderResult(data, mode) { : issues.map(i => `
${escHtml(i.type || 'Issue')} ${i.line ? `Line ${i.line}` : ''} + ${renderComplexityBadge(i)}

${escHtml(i.description || '')}

${i.suggestion ? `

→ ${escHtml(i.suggestion)}

` : ''}
`).join('')} @@ -636,4 +687,5 @@ if (digestForm) { // ── Init ── renderHistory(); -renderFavorites(); \ No newline at end of file +renderFavorites(); + diff --git a/frontend/style.css b/frontend/style.css index b05b82a7..6bfe3c5c 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -527,6 +527,15 @@ body { .tag-ok { background: rgba(34,197,94,0.12); color: #86efac; border: 1px solid rgba(34,197,94,0.2); } .tag-info { background: rgba(59,130,246,0.12); color: #93c5fd; border: 1px solid rgba(59,130,246,0.2); } +/* ── Issue Complexity Badge ── */ +.complexity-badge { + font-weight: 600; + cursor: help; +} +.complexity-easy { background: rgba(34,197,94,0.12); color: #86efac; border: 1px solid rgba(34,197,94,0.25); } +.complexity-medium { background: rgba(234,179,8,0.14); color: #fde047; border: 1px solid rgba(234,179,8,0.3); } +.complexity-hard { background: rgba(239,68,68,0.14); color: #f87171; border: 1px solid rgba(239,68,68,0.3); } + /* ── History ── */ .history-section, .fav-section { padding: 4rem 4rem; } .history-container, .fav-container { @@ -611,3 +620,4 @@ body { text-align: center; } } +