From 1b8f29e417c50c7b4347e9caa8c70e9123dbbeae Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Thu, 23 Oct 2025 17:55:01 +0530 Subject: [PATCH 01/11] Initial commit --- .gitignore | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++++++++ 2 files changed, 160 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..9a5acedff --- /dev/null +++ b/.gitignore @@ -0,0 +1,139 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.* +!.env.example + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Sveltekit cache directory +.svelte-kit/ + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Firebase cache directory +.firebase/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v3 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Vite logs files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..548454e94 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Sudheer Yadav + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From af58386dc66a7ed5a5e69f269cd9c62f141465db Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Thu, 23 Oct 2025 18:16:22 +0530 Subject: [PATCH 02/11] Add full Dev Detective v3 project files --- .gitignore | 10 + .idea/.gitignore | 8 + .idea/18 - Dev Detective Github.iml | 14 ++ .idea/jsLibraryMappings.xml | 6 + .idea/material_theme_project_new.xml | 12 + .idea/modules.xml | 8 + Images/android-chrome-192x192.png | Bin 0 -> 38611 bytes Images/android-chrome-512x512.png | Bin 0 -> 186661 bytes Images/apple-touch-icon.png | Bin 0 -> 34632 bytes Images/company-icon.svg | 1 + Images/favicon-16x16.png | Bin 0 -> 868 bytes Images/favicon-32x32.png | Bin 0 -> 2405 bytes Images/favicon.ico | Bin 0 -> 15406 bytes Images/location-icon.svg | 5 + Images/logo.png | Bin 0 -> 75722 bytes Images/moon-icon.svg | 5 + Images/search-icon.svg | 5 + Images/site.webmanifest | 1 + Images/sun-icon.svg | 6 + Images/twitter-icon.svg | 5 + Images/website-icon.svg | 8 + README.md | 1 + index.html | 151 ++++++++++++ manifest.json | 12 + script.js | 336 +++++++++++++++++++++++++++ style.css | 105 +++++++++ sw.js | 12 + 27 files changed, 711 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/18 - Dev Detective Github.iml create mode 100644 .idea/jsLibraryMappings.xml create mode 100644 .idea/material_theme_project_new.xml create mode 100644 .idea/modules.xml create mode 100644 Images/android-chrome-192x192.png create mode 100644 Images/android-chrome-512x512.png create mode 100644 Images/apple-touch-icon.png create mode 100644 Images/company-icon.svg create mode 100644 Images/favicon-16x16.png create mode 100644 Images/favicon-32x32.png create mode 100644 Images/favicon.ico create mode 100644 Images/location-icon.svg create mode 100644 Images/logo.png create mode 100644 Images/moon-icon.svg create mode 100644 Images/search-icon.svg create mode 100644 Images/site.webmanifest create mode 100644 Images/sun-icon.svg create mode 100644 Images/twitter-icon.svg create mode 100644 Images/website-icon.svg create mode 100644 README.md create mode 100644 index.html create mode 100644 manifest.json create mode 100644 script.js create mode 100644 style.css create mode 100644 sw.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..6743dc624 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# system +.DS_Store +Thumbs.db + +# logs +*.log +*.log + +# node (if ever used) +node_modules/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..13566b81b --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/18 - Dev Detective Github.iml b/.idea/18 - Dev Detective Github.iml new file mode 100644 index 000000000..d0f1ed12b --- /dev/null +++ b/.idea/18 - Dev Detective Github.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 000000000..de4660ea6 --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 000000000..7d3b86a2d --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..c4870ec60 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Images/android-chrome-192x192.png b/Images/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..1b29de4ba5fedbd77b2c49992da8609a96c3fd05 GIT binary patch literal 38611 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

+f9TY%pmLn*~H5FzrvpaWzbR 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֭tq8>CW9>Uj*}7 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 literal 0 HcmV?d00001 diff --git a/Images/apple-touch-icon.png b/Images/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c850eacad44ee6c25294aa5fabf104626f09693c GIT binary patch literal 34632 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±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 eM#^0D78DKV^*R`O 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 U;) \ No newline at end of file diff --git a/Images/favicon-16x16.png b/Images/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..f82b01da41ce6f18262a188813444fc72134d388 GIT binary patch literal 868 zcmV-q1DpJbP)0V^TLU4Ja>@=r+pO zg_mi2+0!I;d4Ro)Nt2+WXe&|~Fv7hTC{0pwA#F(FIF4g`$7t)?3SsO2aE{OS)A#@T zI}}ztZEbC}oYiugrkU-sEMJGA%e>9L*)WXpr@8#TNF)+lDxp>awsm&?>i7G0>6(To znKZPDhL`HT zPUF#Rw0CT5?96ik9UUFRVlX)9bPAB90+NXYG~&eL^}^|Nkv$2qc9c@8a8Ok%?83qVve_&V zR6tWiPSeDI@vIHg32)=!sq?_)a+>ZQM`N?cO#2~0aCG{;f!#q*RZpn`Mk>nWKpDCL zSQ#BQ8w`la^upgzWClN< zK8N1Dy-=pk5)u99EAK?!Tm~=~#|FA*nl^4)y`^<63=%d&euP!klZ(NtNLj?Dl8|KM zgO>d_%ohH4(ACv-AUPL1`qL4MSl<|=XwHFBp@hP`gqolml%bOT6PTRHpWHlfdyrTs zBmNg4iee}EUB9JLscqN3ZX8;(wt5Hc5HK>H!-HrM9jyWyygKG)bJ9$@`0~y})2U@q zCPCV$s%i__{QlAx%^N&5^kuJ)6F56h8eyQ7ipZoCB(wUvufKQe+ZWzO^W*4O;cyja u4BM>KR+1;aRK^`yp-#MUF!JNd+xrg&{8jhGH#1BC0000jgpDzze)=SysDY zOMT1t-0AOYG`P<*P|-G@p`>(f#+eE26DL^bX|vS+mJ*NvaCRqL|B%MbUJOP zGurmf&dwKS+J8m>_4V~j{eIu+l3>sy%d+DzpVyJiYGhg1i19P>0PRwyU85~Y%j!M!0`4O14EHv>^BU< z`E1f>28Ns?Umy@h{{B=-YnuXK(R+bR=j!Te-zp0DOKqF84AU&+b={)y8SzA79W4-< z^e7SpLUh9G;@N4ZxJwaW;bzX6LqBj)?J({M{?F; z6R}RNva<52MT-{hRuzS3h;x)XoyemR!5Gt7(P5#cK0rnws1!RPXK8RUz*0fZofDIh z|)>;PrOtcR5F=V!3fH}V<3qeiD-26Z+(4zzor9!$abk9 zTR_N`l$C`yhUSD?h;R#NBaWSdRlavSB30efQ1U&^UFCJcYvz#4 zll%yfX^>+U1$xrr=wz-O6gQDY;7Bbl$Px#S!olZpp;$GMk0+oKe~p4Uy63Io2Xh97 z$w!-u0`Lpb+yIvn?zM>&9r2v!Ots6@*yJrmH z=lS4$X6MfLD}u?o%1{EFq)<^IQqc(=Ax;UFrTzTKax|2@1Fvd>cVs$AkDQjL0!BqC zn?T2jAtWaXU3=f|U-^$d=(OZ|UzoNRG&MndddC}QE6gLcixyIKlf4)k1^)DIFHGXX z#tjivlqH@|; zbZUpKQ`fWrc5Q(ax&PI^a-nLrtg`S*ZaRu825#==bo-B37)~h`Pu!*c0#J?9gJYn|}Z?qXCE8IK1)l3kdjR z-22csuo+=Q_tTPa)22<^ zsMzf7K6e%`JtAUlODQBTIhQ=wOEBwr|EIf5XX_>JH2e3-^_j(Ns)h7eoblgZ(!kIlEW^WfKCM2@ImA2}!WP6|%_0H5y2D zP!P7P-roJY*#O|?&6{_D=C~S~<6_jg>gl2WBR{Hb4yvx>Wu+qL3sT#l2oxx%2ahN4 z+JONKM$=G9nyXioVEb3gA@daeY#y<39m8?%{J^nupL*c8gzP5QCsIHI$6@Mp>nQ(Q zY5kkmo;mvB@_lpsc(^1eK=n~iXJZ-ldJqBgCUrlT3C_sCr14A7i^$$D>m=+K^s$0ykD6?yHI0Ut+WI60MQFrjwqkd7uk)~54W(~(>} z!`Ug#dUEZK^FO_n^P8$*x`&SKuJPG|^olCuD_)s&O;e;e)qQBrpt%pwbPnm9oy_ap zx7Iw}_4-WCZwtV+&5rDjG`mFmVUfpb@?S+cl*xb6Fu4ncVgE%3_sTsxx}vjj&er@J XfN*t6P_LW*00000NkvXXu0mjfMG2f- literal 0 HcmV?d00001 diff --git a/Images/favicon.ico b/Images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6d652abe6a61ea5189f482755a54ea695766e9cf GIT binary patch literal 15406 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 F{{apg!hHY$ literal 0 HcmV?d00001 diff --git a/Images/location-icon.svg b/Images/location-icon.svg new file mode 100644 index 000000000..533476f00 --- /dev/null +++ b/Images/location-icon.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/Images/logo.png b/Images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..69f428c9473e142808a09f02a4e1e1d21582d53a GIT binary patch literal 75722 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 #~W3MsT|;bmxs7x3C7brCoa&o45mHLN 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 literal 0 HcmV?d00001 diff --git a/Images/moon-icon.svg b/Images/moon-icon.svg new file mode 100644 index 000000000..998ffd3c7 --- /dev/null +++ b/Images/moon-icon.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/Images/search-icon.svg b/Images/search-icon.svg new file mode 100644 index 000000000..9cd81ca31 --- /dev/null +++ b/Images/search-icon.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/Images/site.webmanifest b/Images/site.webmanifest new file mode 100644 index 000000000..45dc8a206 --- /dev/null +++ b/Images/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/Images/sun-icon.svg b/Images/sun-icon.svg new file mode 100644 index 000000000..623f24109 --- /dev/null +++ b/Images/sun-icon.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Images/twitter-icon.svg b/Images/twitter-icon.svg new file mode 100644 index 000000000..f16f42dd3 --- /dev/null +++ b/Images/twitter-icon.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/Images/website-icon.svg b/Images/website-icon.svg new file mode 100644 index 000000000..92ddd9722 --- /dev/null +++ b/Images/website-icon.svg @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..1e70aafbc --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +🕵️‍♂️ Dev Detective — A modern GitHub Profile Analyzer built using HTML, CSS & JavaScript. diff --git a/index.html b/index.html new file mode 100644 index 000000000..b5ece8f9c --- /dev/null +++ b/index.html @@ -0,0 +1,151 @@ + + + + + + DevDetective + + + + + + + + + + + + +

+ +
+
+
+

Dev Detective

+

GitHub profile inspector • mini-portfolio • analytics

+
+
+ + + + +
+
+ +
+ + + + +
+
+
+
+
Profile Insights
+
No profile selected
+
+
+
+ +
+
Avg Stars
+
Most Starred Repo
+
Dev Score
+
Activity (14d)
+
+ +
+
+
+
Location
+
+
+
+ +
+
+ + + + +
+
+
+ +
+
Activity Timeline
+
+
+ +
+
Developer Leaderboard (local)
+
+
+
+
+ + + +
+ + + + + + diff --git a/manifest.json b/manifest.json new file mode 100644 index 000000000..970e3f773 --- /dev/null +++ b/manifest.json @@ -0,0 +1,12 @@ +{ + "name": "DevDetective", + "short_name": "DevDetective", + "start_url": "./index.html", + "display": "standalone", + "background_color": "#f6f8ff", + "theme_color": "#0079ff", + "icons": [ + { "src": "images/favicon-16x16.png", "sizes": "192x192", "type": "image/png" }, + { "src": "images/favicon-32x32.png", "sizes": "512x512", "type": "image/png" } + ] +} diff --git a/script.js b/script.js new file mode 100644 index 000000000..d4a70cc65 --- /dev/null +++ b/script.js @@ -0,0 +1,336 @@ +/* DevDetective v3 - script.js + Put in same folder as index.html & style.css +*/ +const API_ROOT = 'https://api.github.com/users/'; +const el = id => document.getElementById(id); + +// elements +const input = el('usernameInput'); +const searchBtn = el('searchBtn'); +const voiceBtn = el('voiceBtn'); +const modal = el('modal'); +const modalPanel = el('modalPanel'); + +let lastProfile = null; +let pinned = JSON.parse(localStorage.getItem('devdetective_pinned') || '[]'); +let history = JSON.parse(localStorage.getItem('devdetective_history') || '[]'); +let leaderboard = JSON.parse(localStorage.getItem('devdetective_lb') || '[]'); + +let langChart = null; +let map = null; + +// init +renderHistory(); +renderLeaderboard(); + +// events +searchBtn.addEventListener('click', () => startSearch(input.value.trim())); +input.addEventListener('keydown', (e) => { if (e.key === 'Enter') startSearch(input.value.trim()); }); + +// voice search +voiceBtn.addEventListener('click', () => { + if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) { alert('Voice not supported'); return; } + const SR = window.SpeechRecognition || window.webkitSpeechRecognition; + const r = new SR(); r.lang = 'en-US'; r.interimResults = false; r.start(); + r.onresult = (ev) => { const txt = ev.results[0][0].transcript.trim(); input.value = txt.replace(/\s+/g, ''); startSearch(input.value); } +}); + +// keyboard shortcut +document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); input.focus(); } }); + +// modal close +modal.addEventListener('click', (e) => { if (e.target === modal) modal.classList.remove('show'); }); + +/* MAIN SEARCH FLOW */ +async function startSearch(username) { + if (!username) return; + showTyping(true); + try { + const profile = await fetch(API_ROOT + username).then(r => r.json()); + if (profile.message === 'Not Found') { alert('User not found'); showTyping(false); return; } + lastProfile = profile; + saveHistory(username); + await enrichAndRender(profile); + showTyping(false); + } catch (err) { console.error(err); showTyping(false); alert('Error fetching user'); } +} + +function showTyping(on) { + document.querySelectorAll('.dot').forEach(d => d.style.display = on ? 'inline-block' : 'none'); +} + +/* ENRICH WITH REPOS & LANGS */ +async function enrichAndRender(profile) { + const repos = await fetch(profile.repos_url + '?per_page=100').then(r => r.json()); + profile._repos = Array.isArray(repos) ? repos : []; + // languages + const langCount = {}; + profile._repos.forEach(r => { if (r.language) langCount[r.language] = (langCount[r.language] || 0) + 1; }); + profile._langCount = langCount; + renderProfile(profile); + renderRepos(profile._repos); + renderAnalytics(profile); + renderMap(profile); + renderTimeline(profile); + generateLeaderboardEntry(profile); +} + +/* RENDER PROFILE */ +function renderProfile(p) { + el('avatarImg').src = p.avatar_url; + el('fullName').innerText = p.name || p.login; + el('userLink').innerText = '@' + p.login; el('userLink').href = p.html_url; + el('joined').innerText = p.created_at ? 'Joined ' + new Date(p.created_at).toDateString() : 'Joined —'; + el('bio').innerText = p.bio || 'This profile has no bio.'; + el('statRepos').innerText = p.public_repos; + el('statFollowers').innerText = p.followers; + el('statFollowing').innerText = p.following; + el('lastSeen').innerText = new Date(p.updated_at || p.created_at).toLocaleString(); + el('mainBadge').innerText = computeMainBadge(p); + + // languages pills + const langWrap = el('languages'); langWrap.innerHTML = ''; + const langs = Object.entries(p._langCount).sort((a, b) => b[1] - a[1]); + langs.slice(0, 6).forEach(([lang, c]) => { const s = document.createElement('div'); s.className = 'pill'; s.innerText = `${lang} (${c})`; langWrap.appendChild(s); }); +} + +/* compute main badge */ +function computeMainBadge(p) { + if (p.followers > 500) return '🌟 Popular Dev'; + if (p.public_repos > 50) return '🧠 Open Source Wizard'; + if (p.public_repos < 5) return '🆕 Newbie'; + return '💻 Dev'; +} + +/* RENDER REPOS */ +function renderRepos(repos) { + const list = el('reposList'); list.innerHTML = ''; + repos.slice(0, 20).forEach(r => { + const div = document.createElement('div'); div.className = 'repo'; + div.innerHTML = `
${r.name}
${r.description || ''}
+
⭐ ${r.stargazers_count}
Open
`; + list.appendChild(div); + }); + // openRepos button + el('openReposBtn').onclick = () => { + if (!lastProfile) return; + const html = `

Top repos for ${lastProfile.login}

${lastProfile._repos.slice(0, 20).map(r => + `
${r.name} — ⭐ ${r.stargazers_count}
${r.description||''}
` + ).join('')}`; + openModal(html); + }; +} + +/* HISTORY & LEADERBOARD */ +function saveHistory(username) { + history = [username].concat(history.filter(u => u !== username)).slice(0, 8); + localStorage.setItem('devdetective_history', JSON.stringify(history)); + renderHistory(); +} +function renderHistory() { + const h = el('history'); h.innerHTML = ''; + history.forEach(u => { const b = document.createElement('div'); b.className = 'pill'; b.innerText = u; b.onclick = () => startSearch(u); h.appendChild(b); }); +} + +function generateLeaderboardEntry(p) { + leaderboard = leaderboard.filter(x => x.login !== p.login); + leaderboard.unshift({ login: p.login, followers: p.followers, repos: p.public_repos, avatar: p.avatar_url }); + leaderboard = leaderboard.slice(0, 10); + localStorage.setItem('devdetective_lb', JSON.stringify(leaderboard)); + renderLeaderboard(); +} +function renderLeaderboard() { + const box = el('leaderboard'); box.innerHTML = ''; + leaderboard.forEach((u, idx) => { + const d = document.createElement('div'); d.className = 'row'; + d.style.alignItems = 'center'; d.style.justifyContent = 'space-between'; + d.style.padding = '6px 0'; + d.innerHTML = `
+
${u.login}
${u.followers} followers
#${idx+1}
`; + box.appendChild(d); + }); +} + +/* ANALYTICS & CHART */ +function renderAnalytics(p) { + const langs = p._langCount; + const labels = Object.keys(langs); + const data = Object.values(langs); + if (!langChart) { + const ctx = document.getElementById('langChart').getContext('2d'); + langChart = new Chart(ctx, { type: 'doughnut', data: { labels, datasets: [{ data }] }, options: { plugins: { legend: { position: 'bottom' } } } }); + } else { + langChart.data.labels = labels; langChart.data.datasets[0].data = data; langChart.update(); + } + const avg = p._repos.length ? Math.round(p._repos.reduce((s, r) => s + r.stargazers_count, 0) / p._repos.length) : 0; + el('avgStars').innerText = avg; + const top = p._repos.slice().sort((a, b) => b.stargazers_count - a.stargazers_count)[0]; + el('topRepo').innerText = top ? top.name : '—'; + const score = Math.round((p.followers * 0.3 + p.public_repos * 2 + (avg) * 1.5)); + el('devScore').innerText = score; + const now = Date.now(); const recent = p._repos.filter(r => (now - new Date(r.updated_at)) / 86400000 < 14).length; + el('streak').innerText = recent + ' updates (14d)'; + el('insightSummary').innerText = generateSummary(p, avg); +} + +/* simple rule-based AI summary */ +async function generateSummary(p, avgStars) { + const prompt = `Summarize this GitHub profile: ${p.name || p.login}, main language ${Object.keys(p._langCount)[0]}. + ${p.followers} followers, ${p.public_repos} repos, average stars ${avgStars}. Write in 1 sentence.`; + try { + const r = await fetch("https://api.openai.com/v1/chat/completions", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer YOUR_OPENAI_API_KEY" + }, + body: JSON.stringify({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: prompt }], + max_tokens: 40 + }) + }).then(r => r.json()); + return r.choices?.[0]?.message?.content || "AI summary unavailable."; + } catch (e) { console.warn(e); return "AI summary unavailable."; } +} + +/* MAP (Nominatim geocoding + Leaflet) */ +function renderMap(p) { + try { + if (!map) { map = L.map('map', { attributionControl: false }).setView([20, 0], 2); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); } + const loc = p.location; if (!loc) { map.setView([20, 0], 2); return; } + fetch('https://nominatim.openstreetmap.org/search?format=json&q=' + encodeURIComponent(loc)).then(r => r.json()).then(data => { + if (data && data[0]) { + const lat = data[0].lat, lon = data[0].lon; + map.setView([lat, lon], 5); + L.marker([lat, lon]).addTo(map).bindPopup(p.login + ' — ' + (p.location || '')).openPopup(); + } + }).catch(e => console.warn(e)); + } catch (e) { console.warn(e); } +} + +/* TIMELINE (recent repo updates) */ +function renderTimeline(p) { + const t = el('timeline'); t.innerHTML = ''; + const items = p._repos.slice(0, 8).map(r => ({ title: r.name, when: new Date(r.updated_at).toLocaleString(), desc: r.description || '' })); + items.forEach(it => { + const d = document.createElement('div'); d.className = 'small muted'; d.style.padding = '8px 0'; + d.innerHTML = `${it.title}${it.when}
${it.desc}
`; + t.appendChild(d); + }); +} + +/* MODAL helpers */ +function openModal(html) { modal.classList.add('show'); modalPanel.innerHTML = html; } +function closeModal() { modal.classList.remove('show'); } + +/* PINS */ +el('pinBtn').addEventListener('click', () => { + if (!lastProfile) return; + if (!pinned.includes(lastProfile.login)) pinned.push(lastProfile.login); + localStorage.setItem('devdetective_pinned', JSON.stringify(pinned)); + alert('Pinned ' + lastProfile.login); +}); + +/* DOWNLOAD JSON */ +el('downloadJsonBtn').addEventListener('click', () => { + if (!lastProfile) return; + const data = JSON.stringify(lastProfile, null, 2); const url = URL.createObjectURL(new Blob([data], { type: 'application/json' })); + const a = document.createElement('a'); a.href = url; a.download = lastProfile.login + '.json'; a.click(); URL.revokeObjectURL(url); +}); + +/* EXPORT DEV CARD (canvas to PNG) */ +el('exportCardBtn').addEventListener('click', () => { + if (!lastProfile) return; + const c = document.createElement('canvas'); c.width = 1000; c.height = 520; const ctx = c.getContext('2d'); + ctx.fillStyle = '#0f1724'; ctx.fillRect(0, 0, c.width, c.height); + ctx.fillStyle = '#fff'; ctx.font = 'bold 36px monospace'; ctx.fillText(lastProfile.name || lastProfile.login, 160, 80); + const img = new Image(); img.crossOrigin = 'anonymous'; + img.onload = function () { + ctx.drawImage(img, 40, 40, 100, 100); + ctx.font = '16px monospace'; ctx.fillText('@' + lastProfile.login, 160, 120); ctx.font = '14px monospace'; + wrapText(ctx, lastProfile.bio || '', 160, 160, 760, 20); + const a = document.createElement('a'); a.href = c.toDataURL('image/png'); a.download = lastProfile.login + '_card.png'; a.click(); + }; + img.src = lastProfile.avatar_url; +}); + +/* small text wrap helper */ +function wrapText(ctx, text, x, y, maxWidth, lineHeight) { + const words = text.split(' '); + let line = ''; + for (let n = 0; n < words.length; n++) { + const testLine = line + words[n] + ' '; + const metrics = ctx.measureText(testLine); + const testWidth = metrics.width; + if (testWidth > maxWidth && n > 0) { ctx.fillText(line, x, y); line = words[n] + ' '; y += lineHeight; } else { line = testLine; } + } + ctx.fillText(line, x, y); +} + +/* RESUME (open printable HTML) */ +el('resumeBtn').addEventListener('click', () => { + if (!lastProfile) return; + const win = window.open('', '_blank'); + const html = `Resume - ${lastProfile.name || lastProfile.login} + +

${lastProfile.name || lastProfile.login}

${lastProfile.bio || ''}

Followers: ${lastProfile.followers} | Repos: ${lastProfile.public_repos}

+

Top repos

    ${lastProfile._repos.slice(0, 6).map(r => '
  • ' + r.name + ' — ⭐' + r.stargazers_count + '
  • ').join('')}
`; + win.document.write(html); win.document.close(); win.print(); +}); + +/* BADGES */ +el('badgeBtn').addEventListener('click', () => { + if (!lastProfile) return; + const badges = computeBadges(lastProfile); alert('Badges: ' + (badges.length ? badges.join(', ') : 'None')); +}); +function computeBadges(p) { + const out = []; + if (p.public_repos > 30) out.push('Open Source Wizard'); + if (p.followers > 200) out.push('Popular'); + if (p.followers > 50 && p.public_repos > 10) out.push('Influencer'); + if ((p._repos || []).some(r => r.stargazers_count > 50)) out.push('Starred Repo'); + if (Object.keys(p._langCount).length > 3) out.push('Polyglot'); + return out; +} + +/* COMMENTS (localStorage) */ +let comments = JSON.parse(localStorage.getItem('devdetective_comments') || '{}'); +el('commentToggle').addEventListener('click', () => { + if (!lastProfile) return; const k = lastProfile.login; const c = comments[k] || []; + openModal(`

Comments for ${k}

${c.map(x => `
${x}
`).join('')}
+
`); + document.getElementById('addCmt').onclick = () => { const t = document.getElementById('cmtText').value.trim(); if (!t) return; comments[k] = comments[k] || []; comments[k].unshift(t); localStorage.setItem('devdetective_comments', JSON.stringify(comments)); modal.classList.remove('show'); alert('Comment added'); }; + document.getElementById('closeC').onclick = () => modal.classList.remove('show'); +}); + +/* COMPARE */ +el('compareOpenBtn').addEventListener('click', () => { + openModal(`

Compare Developers

+
`); + document.getElementById('doCompare').onclick = async () => { + const u1 = document.getElementById('c1').value.trim(), u2 = document.getElementById('c2').value.trim(); + if (!u1 || !u2) return; + const a = await fetch(API_ROOT + u1).then(r => r.json()); const b = await fetch(API_ROOT + u2).then(r => r.json()); + const html = `

${a.login}

Followers: ${a.followers}
Repos: ${a.public_repos}
+

${b.login}

Followers: ${b.followers}
Repos: ${b.public_repos}
+
Winner: ${ (a.followers + a.public_repos * 2) > (b.followers + b.public_repos * 2) ? a.login : b.login }
`; + document.getElementById('cmpRes').innerHTML = html; + }; +}); + +/* THEME APPLY (mood) */ +el('themeApplyBtn').addEventListener('click', () => { if (!lastProfile) return; const mainLang = Object.keys(lastProfile._langCount)[0] || 'Unknown'; applyMoodTheme(mainLang); }); +function applyMoodTheme(lang) { + if (['JavaScript', 'TypeScript'].includes(lang)) { document.documentElement.style.setProperty('--accent', '#f7df1e'); document.body.style.background = 'linear-gradient(180deg,#fff7d9,#fefae0)'; } + else if (['Python'].includes(lang)) { document.documentElement.style.setProperty('--accent', '#306998'); document.body.style.background = 'linear-gradient(180deg,#eaf6ff,#e9f2ff)'; } + else { document.documentElement.style.setProperty('--accent', '#0079ff'); document.body.style.background = 'linear-gradient(180deg,#eaf0ff,#f6f8ff)'; } +} +function openModal(html) { modal.classList.add('show'); modalPanel.innerHTML = html; } +startSearch('sudheerrrrit'); +window.addEventListener("load", () => { + setTimeout(() => { + document.getElementById("loader").classList.add("hide"); + }, 2500); +}); + diff --git a/style.css b/style.css new file mode 100644 index 000000000..7fd4e46c0 --- /dev/null +++ b/style.css @@ -0,0 +1,105 @@ +:root{ + --bg:#f6f8ff; --card:#fff; --text:#243447; --muted:#6b7a91; --accent:#0079ff; + --glass: rgba(255,255,255,0.6); +} +*{box-sizing:border-box; font-family:'Space Mono', monospace} +body{margin:0; background:linear-gradient(180deg,#eaf0ff 0%,var(--bg)100%); color:var(--text); min-height:100vh} +.app{max-width:1100px; margin:20px auto; padding:18px} +header{display:flex; justify-content:space-between; align-items:center; gap:12px} +.titleBlock h1{margin:0; font-size:18px} +.subtitle{margin:0; font-size:12px; color:var(--muted)} +.controls{display:flex; gap:8px; align-items:center} +.controls input{padding:10px 12px; border-radius:8px; border:1px solid #d6e0ff; width:320px} +.controls button{background:var(--accent); color:#fff; border:none; padding:9px 12px; border-radius:8px; cursor:pointer} +.grid{display:grid; grid-template-columns:360px 1fr; gap:14px; margin-top:14px} +.card{background:var(--card); padding:14px; border-radius:12px; box-shadow:0 8px 30px rgba(20,30,60,0.06)} +.leftCard{display:flex; flex-direction:column; gap:12px} +.profileTop{display:flex; align-items:center; gap:12px} +.avatar{width:84px; height:84px; border-radius:12px; overflow:hidden; background:#eee; flex-shrink:0} +.avatar img{width:100%; height:100%; object-fit:cover} +.profileMeta{flex:1} +.fullname{font-weight:700} +.username{color:var(--accent); text-decoration:none} +.badgeWrap{text-align:right} +.badge{background:linear-gradient(90deg,#f0f6ff,#eef9ff); padding:6px 8px; border-radius:999px; font-weight:700} + +.bio{line-height:1.35; color:var(--muted)} + +.statsRow{display:flex; gap:10px; margin-top:6px} +.stat{flex:1; background:#f6f9ff; padding:8px; border-radius:8px; text-align:center} +.small{font-size:12px} +.muted{color:var(--muted)} +.big{font-weight:700; font-size:18px} + +.actions{display:flex; gap:8px; flex-wrap:wrap} +.actions button{background:#fff; border:1px solid #e6eefc; padding:8px 10px; border-radius:8px; cursor:pointer} + +.section{margin-top:8px} +.sectionTitle{margin-bottom:8px} + +.langWrap{display:flex; gap:8px; flex-wrap:wrap} +.pill{background:#f4f8ff; padding:6px 8px; border-radius:999px; font-size:12px} + +.repos-list{max-height:220px; overflow:auto; margin-top:6px} +.repo{padding:8px; border-radius:8px; border:1px solid #f0f4ff; margin-bottom:8px; display:flex; justify-content:space-between} +.repo a{color:var(--accent); text-decoration:none} + +.history{display:flex; gap:6px; flex-wrap:wrap} +.history .pill{cursor:pointer} + +.rightColumn{display:flex; flex-direction:column; gap:12px} +.cardHeader{display:flex; justify-content:space-between; align-items:center} +.typing .dot{display:inline-block;width:6px;height:6px;background:var(--accent); border-radius:6px;margin-right:4px; animation:blink 1s infinite} +@keyframes blink{0%{opacity:0.25}50%{opacity:1}100%{opacity:0.25}} + +.analyticsGrid{display:grid; grid-template-columns:repeat(2,1fr); gap:10px; margin-top:12px} +.analytic{padding:12px; border-radius:10px} +.highlight{background:linear-gradient(90deg,#fff9eb,#fff);} + +.chartRow{display:flex; gap:12px; margin-top:12px} +.chartWrap{flex:1} +.mapWrap{width:260px} + +.map{height:200px; border-radius:8px; overflow:hidden} + +.cardFooter{display:flex; justify-content:space-between; align-items:center; margin-top:12px} +.footerButtons button{background:#fff; border:1px solid #e6eefc; padding:8px 10px; border-radius:8px; cursor:pointer} + +.timelineCard .timeline{max-height:200px; overflow:auto; margin-top:8px} +.leaderboard .row{display:flex; justify-content:space-between; padding:6px 0; align-items:center} + +.modal{position:fixed; inset:0; display:none; align-items:center; justify-content:center; background:rgba(0,0,0,0.45); z-index:1000} +.modal.show{display:flex} +.modalPanel{width:720px; max-width:95%; background:#fff; border-radius:12px; padding:16px; max-height:80vh; overflow:auto} + +/* responsive */ +@media (max-width:920px){ + .grid{grid-template-columns:1fr} + .controls input{width:160px} + .mapWrap{display:none} +} +/* ========== ANIMATIONS / MICRO-INTERACTIONS ========== */ +button:hover { transform: scale(1.04); transition: 0.2s ease; } +.card { transition: all 0.3s ease; } +.card:hover { transform: translateY(-4px); box-shadow: 0 12px 40px rgba(20,30,60,0.12); } +.avatar img { transition: transform 0.4s ease; } +.avatar img:hover { transform: scale(1.08) rotate(2deg); } +.timelineCard .timeline div { animation: fadein 0.5s ease; } +@keyframes fadein { from { opacity: 0; transform: translateY(5px);} to { opacity: 1; transform: translateY(0);} } + +/* Splash Loader */ +#loader { + position: fixed; + inset: 0; + background: linear-gradient(180deg,#eaf0ff,#f6f8ff); + display: flex; + align-items: center; + justify-content: center; + z-index: 2000; + transition: opacity 0.6s ease; +} +#loader.hide { opacity: 0; pointer-events: none; } +.loader-content { text-align: center; } +.loader-content img { width: 90px; height: 90px; margin-bottom: 12px; } +.spin { animation: spin 2s linear infinite; } +@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } diff --git a/sw.js b/sw.js new file mode 100644 index 000000000..46f62bde9 --- /dev/null +++ b/sw.js @@ -0,0 +1,12 @@ +// sw.js - minimal service worker skeleton (drop in site root if you want basic offline cache) +const CACHE_NAME = 'devdetective-v1'; +const OFFLINE_FILES = ['/', '/index.html', '/style.css', '/script.js']; + +self.addEventListener('install', (evt) => { + evt.waitUntil(caches.open(CACHE_NAME).then(cache => cache.addAll(OFFLINE_FILES))); + self.skipWaiting(); +}); + +self.addEventListener('fetch', (evt) => { + evt.respondWith(caches.match(evt.request).then(res => res || fetch(evt.request))); +}); From 1f49278d4bcf8d619fde88eab391d7aaab4089d6 Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Thu, 23 Oct 2025 18:43:32 +0530 Subject: [PATCH 03/11] Revise README with project details and instructions Updated README to include features, tech stack, and local run instructions. --- README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e70aafbc..dcc705864 100644 --- a/README.md +++ b/README.md @@ -1 +1,37 @@ -🕵️‍♂️ Dev Detective — A modern GitHub Profile Analyzer built using HTML, CSS & JavaScript. +# 🕵️‍♂️ Dev Detective + +A modern GitHub Profile Analyzer built with **HTML**, **CSS**, and **JavaScript**. +Includes profile search, charts, maps, voice input, comments, badges, leaderboard, +offline PWA support, and animated UI — perfect for learning web APIs & building +polished mini-projects. 🚀 + +--- + +## ✨ Features +- GitHub profile & repo search +- Voice search (Speech API) +- Charts (Chart.js) & maps (Leaflet) +- Mood-based themes +- PWA install + offline cache +- Leaderboard, badges, and comments +- Responsive animated UI + +--- + +## 🧠 Tech Stack +- **Frontend:** HTML5, CSS3, JavaScript (Vanilla) +- **Libraries:** Chart.js, Leaflet.js +- **APIs:** GitHub REST API, OpenStreetMap + +--- + +## 🚀 Run Locally +```bash +# clone repo +git clone https://github.com/sudheerrrrit/Dev-Detective.git +cd Dev-Detective + +# open in browser +start index.html # or double-click +run : + From 2c296389676633d3b04b4cc8572a9c0b4ca425a1 Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Thu, 23 Oct 2025 18:44:01 +0530 Subject: [PATCH 04/11] Add live demo link to README Add link to the live demo of the project. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dcc705864..5820b4bad 100644 --- a/README.md +++ b/README.md @@ -34,4 +34,4 @@ cd Dev-Detective # open in browser start index.html # or double-click run : - +https://dev-detective-lyart.vercel.app/ From de31487e5897f311c67f319b97895333f6479d23 Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Thu, 23 Oct 2025 18:44:36 +0530 Subject: [PATCH 05/11] Update README with application link Added link to the Dev Detective application. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5820b4bad..5403e5b03 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # 🕵️‍♂️ Dev Detective +Check Hare : https://dev-detective-lyart.vercel.app/ + A modern GitHub Profile Analyzer built with **HTML**, **CSS**, and **JavaScript**. Includes profile search, charts, maps, voice input, comments, badges, leaderboard, offline PWA support, and animated UI — perfect for learning web APIs & building From 0ded5d9212db6d9e6d1ca28078f74160827b7062 Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Thu, 23 Oct 2025 18:59:22 +0530 Subject: [PATCH 06/11] Remove inline styles from title tag --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index b5ece8f9c..afdfee1e9 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - DevDetective + DevDetective From fc0b875c8ed153c933c364bcf6af1a26c4fc91be Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Sat, 25 Oct 2025 19:33:25 +0530 Subject: [PATCH 07/11] Enhance README with project features overview Add brief description of project features --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5403e5b03..59283406f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Check Hare : https://dev-detective-lyart.vercel.app/ + A modern GitHub Profile Analyzer built with **HTML**, **CSS**, and **JavaScript**. Includes profile search, charts, maps, voice input, comments, badges, leaderboard, offline PWA support, and animated UI — perfect for learning web APIs & building From b16fafb485084ffbddd5f9716b956de7564f742a Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Sat, 25 Oct 2025 19:37:43 +0530 Subject: [PATCH 08/11] Update README with project features and description --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 59283406f..816cd99e1 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Check Hare : https://dev-detective-lyart.vercel.app/ A modern GitHub Profile Analyzer built with **HTML**, **CSS**, and **JavaScript**. Includes profile search, charts, maps, voice input, comments, badges, leaderboard, offline PWA support, and animated UI — perfect for learning web APIs & building + polished mini-projects. 🚀 --- From e0aa671d188ddc18f4258650b2538aa3ff7c6433 Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Sat, 25 Oct 2025 21:17:09 +0530 Subject: [PATCH 09/11] updated readme Co-Authored-By: sudheer yadav <165470710+sudheerrit@users.noreply.github.com> --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 816cd99e1..8fef66e1f 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ polished mini-projects. 🚀 ## ✨ Features - GitHub profile & repo search + - Voice search (Speech API) - Charts (Chart.js) & maps (Leaflet) - Mood-based themes From b677ac02251e3f4e132b4f5ec1bb50908a25cff5 Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Fri, 13 Feb 2026 13:20:31 +0000 Subject: [PATCH 10/11] "u" --- .vscode/settings.json | 3 + README.md | 107 +- index.html | 384 ++++-- manifest.json | 24 +- script.js | 2836 ++++++++++++++++++++++++++++++++++++----- style.css | 942 ++++++++++++-- sw.js | 73 +- 7 files changed, 3813 insertions(+), 556 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6f3a2913e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/README.md b/README.md index 8fef66e1f..a9f8c1c26 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,89 @@ -# 🕵️‍♂️ Dev Detective +# DevDetective - GitHub Portfolio Analyzer & Enhancer -Check Hare : https://dev-detective-lyart.vercel.app/ +DevDetective is a production-oriented static web app that evaluates a GitHub profile like a recruiter. +It fetches GitHub data, computes weighted category scores, highlights strengths and red flags, +and generates actionable portfolio improvement suggestions. +## Features -A modern GitHub Profile Analyzer built with **HTML**, **CSS**, and **JavaScript**. -Includes profile search, charts, maps, voice input, comments, badges, leaderboard, -offline PWA support, and animated UI — perfect for learning web APIs & building +- Accepts a GitHub username or profile URL +- Supports optional GitHub PAT for higher rate limits and GraphQL pinned repositories +- Fetches public repositories, stars, forks, watchers, README presence, language signals, and recency +- Estimates activity consistency using `pushed_at` timestamps +- Fetches authored PR and issue counts via GitHub Search API +- Computes a weighted `GitHub Portfolio Score (0-100)` +- Computes `Hireability Score` and `Portfolio Readiness Level` +- Returns 7 sub-scores: Documentation Quality, Code Activity / Consistency, Project Popularity, Repository Completeness, Language Diversity, Recent Activity, Impact Signals +- Shows transparent scoring inputs and formula context in UI and report output +- Generates recruiter-style insights: Strengths, Red Flags, Actionable Suggestions (minimum 5) +- Adds AI Recruiter Simulation verdict and feedback signals +- Detects hidden portfolio risks (concentration, conversion, collaboration, momentum) +- Recommends a career path with confidence score and next-skill targets +- Generates a personalized multi-week improvement roadmap +- Includes language, repository-importance, subscore radar, and activity-bucket charts +- Includes pinned repository panel and ranked repository table +- Supports light/dark mode persistence +- Exports a downloadable recruiter-ready Markdown report +- Supports offline shell caching through service worker -polished mini-projects. 🚀 +## Optional GitHub Token Setup ---- +Token is optional, but recommended for better API limits and pinned repositories. -## ✨ Features -- GitHub profile & repo search +1. Go to GitHub Settings > Developer settings > Personal access tokens. +2. Create a token with minimal read access for public data. +3. Paste it into the app's token input. -- Voice search (Speech API) -- Charts (Chart.js) & maps (Leaflet) -- Mood-based themes -- PWA install + offline cache -- Leaderboard, badges, and comments -- Responsive animated UI +Token handling: ---- +- Stored in browser localStorage key: `devdetective_token` +- No backend storage is used in this project -## 🧠 Tech Stack -- **Frontend:** HTML5, CSS3, JavaScript (Vanilla) -- **Libraries:** Chart.js, Leaflet.js -- **APIs:** GitHub REST API, OpenStreetMap +## Run Locally ---- +This project is plain HTML/CSS/JS. Serve the repository root with any static server. + +### Option 1: Python -## 🚀 Run Locally ```bash -# clone repo -git clone https://github.com/sudheerrrrit/Dev-Detective.git -cd Dev-Detective - -# open in browser -start index.html # or double-click -run : -https://dev-detective-lyart.vercel.app/ +python3 -m http.server 5500 +``` + +Open: + +```text +http://localhost:5500 +``` + +### Option 2: VS Code Live Server or any static server + +Serve the repository root and open `index.html`. + +## Rate Limits and Reliability + +- Handles GitHub API errors with clear user-facing messages +- Shows rate-limit reset time when available +- Aborts in-flight requests when a new analysis starts +- Retries once on transient network failures +- Caches completed analyses for 10 minutes in `devdetective_cache_{username}` + +## Scoring Model Summary + +Overall score combines these weighted categories: + +- Documentation Quality (18%) +- Code Activity / Consistency (17%) +- Project Popularity (15%) +- Repository Completeness (14%) +- Language Diversity (10%) +- Recent Activity (14%) +- Impact Signals (12%) + +Each sub-score is normalized to `0-100`, then combined by weight. + +## Known Limitations + +- Commit frequency is approximated using repository `pushed_at` (not full commit history traversal) +- README and language deep checks are limited to top repositories for API efficiency +- Pinned repo retrieval via GraphQL requires token; fallback ranking is used otherwise +- Output quality depends on public GitHub metadata completeness diff --git a/index.html b/index.html index afdfee1e9..3834c591a 100644 --- a/index.html +++ b/index.html @@ -2,150 +2,322 @@ - - DevDetective + + + + DevDetective | GitHub Portfolio Analyzer - - - + + + + + + + - + - + - -
-
- logo -

Loading DevDetective...

-
-
- -
-
-
-

Dev Detective

-

GitHub profile inspector • mini-portfolio • analytics

-
-
- - - - +
+
+
+ +
+

DevDetective

+

GitHub Portfolio Analyzer and Recruiter Insight Engine

+
-
- - +

+ Run an analysis to generate recruiter-style portfolio evaluation. +

+ +
+
Public Repos-
+
Followers-
+
Following-
+
Last Push-
+
Authored PRs-
+
Authored Issues-
+
+ + +
+
+

Pinned Repositories

+ fallback +
+
    +
    + +
    +

    Search History

    +
    +
    + - -
    -
    -
    +
    +
    +
    +
    + -- +
    -
    Profile Insights
    -
    No profile selected
    +

    GitHub Portfolio Score

    +

    Grade -

    +

    + Analyze a profile to generate weighted recruiter evaluation. +

    +
    +
    + +
    +
    + Hireability Score + -- + Weighted recruiter-fit index
    -
    +
    + Portfolio Readiness + -- + Analyze a profile to classify readiness +
    +
    + -
    -
    Avg Stars
    -
    Most Starred Repo
    -
    Dev Score
    -
    Activity (14d)
    +
    +
    Documentation Quality--
    +
    Code Activity / Consistency--
    +
    Project Popularity--
    +
    Repository Completeness--
    +
    Language Diversity--
    +
    Recent Activity--
    +
    Impact Signals--
    +
    -
    -
    -
    -
    Location
    -
    +
    +

    Scoring Transparency

    +

    + Each subscore is calculated from real GitHub metrics and weighted into the final score. +

    +
    +
    +
    + Documentation Quality (18%) +

    0.75 × README Coverage + 0.25 × Description Coverage

    +
    + -- +
    +
    +
    + Code Activity / Consistency (17%) +

    0.60 × Active Months (last 6) + 0.40 × Repos Updated in 90 Days

    +
    + -- +
    +
    +
    + Project Popularity (15%) +

    0.60 × Stars/Repo + 0.25 × Forks/Repo + 0.15 × Watchers/Repo (capped)

    +
    + -- +
    +
    +
    + Repository Completeness (14%) +

    0.50 × Non-empty + 0.30 × Homepage + 0.20 × Topics

    +
    + -- +
    +
    +
    + Language Diversity (10%) +

    0.70 × Unique Languages + 0.30 × Shannon Entropy

    +
    + -- +
    +
    +
    + Recent Activity (14%) +

    0.70 × Recency Bucket + 0.30 × Repos Updated in 30 Days

    +
    + -- +
    +
    +
    + Impact Signals (12%) +

    0.35 × Top Repo Stars + 0.25 × Followers + 0.25 × PRs + 0.15 × Issues

    +
    + --
    +
    -
    -
    - - - - +
    +
    +

    AI Recruiter Simulation

    + Pending +
    +

    + Run an analysis to get recruiter-style interview feedback simulation. +

    +
      +
      + +
      +
      +
      +

      Strengths

      +
        +
        +
        +

        Red Flags

        +
          +
          +
          +

          Actionable Suggestions

          +
            -
            + -
            -
            Activity Timeline
            -
            -
            +
            +

            Hidden Risk Detection

            +
              +
              -
              -
              Developer Leaderboard (local)
              -
              -
              +
              +
              +

              Career Path Recommendation

              + -- +
              +

              Run an analysis to generate role fit

              +

              + Role recommendations are inferred from your repository stack, impact signals, and activity profile. +

              +
                +
                + +
                +

                Personalized Improvement Roadmap

                +
                  +
                  + +
                  +

                  Visual Analytics

                  +
                  +
                  +

                  Language Usage

                  + +
                  +
                  +

                  Repository Importance

                  + +
                  +
                  +

                  Portfolio Dimension Radar

                  + +
                  +
                  +

                  Repository Activity Buckets

                  + +
                  +
                  +
                  + +
                  +

                  Top Repository Ranking

                  +
                  + + + + + + + + + + + +
                  RepositoryImportanceStars / Forks / WatchersLast PushREADME
                  +
                  +
                  - - -
                  - - diff --git a/manifest.json b/manifest.json index 970e3f773..768bdc1f8 100644 --- a/manifest.json +++ b/manifest.json @@ -1,12 +1,26 @@ { - "name": "DevDetective", + "name": "DevDetective GitHub Portfolio Analyzer", "short_name": "DevDetective", + "description": "Recruiter-style GitHub portfolio analyzer with weighted scoring, insights, and actionable suggestions.", "start_url": "./index.html", "display": "standalone", - "background_color": "#f6f8ff", - "theme_color": "#0079ff", + "background_color": "#f3f7ff", + "theme_color": "#1f6feb", "icons": [ - { "src": "images/favicon-16x16.png", "sizes": "192x192", "type": "image/png" }, - { "src": "images/favicon-32x32.png", "sizes": "512x512", "type": "image/png" } + { + "src": "Images/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "Images/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "Images/apple-touch-icon.png", + "sizes": "180x180", + "type": "image/png" + } ] } diff --git a/script.js b/script.js index d4a70cc65..2cb6959fd 100644 --- a/script.js +++ b/script.js @@ -1,336 +1,2562 @@ -/* DevDetective v3 - script.js - Put in same folder as index.html & style.css -*/ -const API_ROOT = 'https://api.github.com/users/'; -const el = id => document.getElementById(id); - -// elements -const input = el('usernameInput'); -const searchBtn = el('searchBtn'); -const voiceBtn = el('voiceBtn'); -const modal = el('modal'); -const modalPanel = el('modalPanel'); - -let lastProfile = null; -let pinned = JSON.parse(localStorage.getItem('devdetective_pinned') || '[]'); -let history = JSON.parse(localStorage.getItem('devdetective_history') || '[]'); -let leaderboard = JSON.parse(localStorage.getItem('devdetective_lb') || '[]'); - -let langChart = null; -let map = null; - -// init -renderHistory(); -renderLeaderboard(); - -// events -searchBtn.addEventListener('click', () => startSearch(input.value.trim())); -input.addEventListener('keydown', (e) => { if (e.key === 'Enter') startSearch(input.value.trim()); }); - -// voice search -voiceBtn.addEventListener('click', () => { - if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) { alert('Voice not supported'); return; } - const SR = window.SpeechRecognition || window.webkitSpeechRecognition; - const r = new SR(); r.lang = 'en-US'; r.interimResults = false; r.start(); - r.onresult = (ev) => { const txt = ev.results[0][0].transcript.trim(); input.value = txt.replace(/\s+/g, ''); startSearch(input.value); } +const GITHUB_API_ROOT = "https://api.github.com"; +const GITHUB_GRAPHQL_ENDPOINT = "https://api.github.com/graphql"; + +const MAX_REPO_PAGES = 3; +const MAX_DEEP_REPOS = 30; +const CONCURRENCY_LIMIT = 5; +const CACHE_TTL_MS = 10 * 60 * 1000; +const HISTORY_LIMIT = 8; + +const STORAGE_KEYS = Object.freeze({ + history: "devdetective_history", + theme: "devdetective_theme", + token: "devdetective_token" +}); + +const CACHE_PREFIX = "devdetective_cache_v2_"; + +const WEIGHTS = Object.freeze({ + documentationQuality: 18, + codeActivityConsistency: 17, + projectPopularity: 15, + repositoryCompleteness: 14, + languageDiversity: 10, + recentActivity: 14, + impactSignals: 12 +}); + +const SUBSCORE_ID_MAP = Object.freeze({ + documentationQuality: "subDocumentationQuality", + codeActivityConsistency: "subCodeActivityConsistency", + projectPopularity: "subProjectPopularity", + repositoryCompleteness: "subRepositoryCompleteness", + languageDiversity: "subLanguageDiversity", + recentActivity: "subRecentActivity", + impactSignals: "subImpactSignals" }); -// keyboard shortcut -document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); input.focus(); } }); +const SCORING_EXPLAIN_ID_MAP = Object.freeze({ + documentationQuality: "explainDocumentationQuality", + codeActivityConsistency: "explainCodeActivityConsistency", + projectPopularity: "explainProjectPopularity", + repositoryCompleteness: "explainRepositoryCompleteness", + languageDiversity: "explainLanguageDiversity", + recentActivity: "explainRecentActivity", + impactSignals: "explainImpactSignals" +}); + +const state = { + currentUsername: "", + token: "", + analysisResult: null, + abortController: null, + charts: { + language: null, + importance: null, + subscoreRadar: null, + activity: null + }, + history: readJsonStorage(STORAGE_KEYS.history, []) +}; + +const ui = { + profileInput: document.getElementById("profileInput"), + tokenInput: document.getElementById("tokenInput"), + analyzeBtn: document.getElementById("analyzeBtn"), + themeToggleBtn: document.getElementById("themeToggleBtn"), + downloadReportBtn: document.getElementById("downloadReportBtn"), + + loadingState: document.getElementById("loadingState"), + loadingText: document.getElementById("loadingText"), + errorBanner: document.getElementById("errorBanner"), + rateLimitBanner: document.getElementById("rateLimitBanner"), + + avatarImg: document.getElementById("avatarImg"), + profileName: document.getElementById("profileName"), + profileHandle: document.getElementById("profileHandle"), + profileLink: document.getElementById("profileLink"), + profileBio: document.getElementById("profileBio"), + + statRepos: document.getElementById("statRepos"), + statFollowers: document.getElementById("statFollowers"), + statFollowing: document.getElementById("statFollowing"), + statLastPush: document.getElementById("statLastPush"), + statPrCount: document.getElementById("statPrCount"), + statIssueCount: document.getElementById("statIssueCount"), + + pinnedSourceBadge: document.getElementById("pinnedSourceBadge"), + pinnedReposList: document.getElementById("pinnedReposList"), + historyList: document.getElementById("historyList"), + + overallScoreRing: document.getElementById("overallScoreRing"), + overallScore: document.getElementById("overallScore"), + scoreGrade: document.getElementById("scoreGrade"), + scoreSummary: document.getElementById("scoreSummary"), + hireabilityScore: document.getElementById("hireabilityScore"), + hireabilityHint: document.getElementById("hireabilityHint"), + readinessLevel: document.getElementById("readinessLevel"), + readinessHint: document.getElementById("readinessHint"), + readinessBar: document.getElementById("readinessBar"), + + strengthsList: document.getElementById("strengthsList"), + redFlagsList: document.getElementById("redFlagsList"), + suggestionsList: document.getElementById("suggestionsList"), + hiddenRisksList: document.getElementById("hiddenRisksList"), + aiRecruiterVerdict: document.getElementById("aiRecruiterVerdict"), + aiRecruiterSummary: document.getElementById("aiRecruiterSummary"), + aiRecruiterSignals: document.getElementById("aiRecruiterSignals"), + careerPathTitle: document.getElementById("careerPathTitle"), + careerPathSummary: document.getElementById("careerPathSummary"), + careerConfidence: document.getElementById("careerConfidence"), + careerSkillsList: document.getElementById("careerSkillsList"), + roadmapList: document.getElementById("roadmapList"), + + repoRankingTable: document.getElementById("repoRankingTable"), + + languageChart: document.getElementById("languageChart"), + importanceChart: document.getElementById("importanceChart"), + subscoreRadarChart: document.getElementById("subscoreRadarChart"), + activityChart: document.getElementById("activityChart") +}; + +class GitHubError extends Error { + constructor(type, message, details = {}) { + super(message); + this.name = "GitHubError"; + this.type = type; + this.details = details; + } +} + +init(); + +function init() { + bindEvents(); + + state.token = localStorage.getItem(STORAGE_KEYS.token) || ""; + ui.tokenInput.value = state.token; + + const savedTheme = localStorage.getItem(STORAGE_KEYS.theme) || "light"; + applyTheme(savedTheme); + + renderHistory(); + renderPinnedRepos({ source: "fallback", items: [] }); + + renderInsightList(ui.strengthsList, ["No profile analyzed yet."], "neutral"); + renderInsightList(ui.redFlagsList, ["No profile analyzed yet."], "neutral"); + renderInsightList(ui.suggestionsList, ["No profile analyzed yet."], "neutral"); + renderInsightList(ui.hiddenRisksList, ["No hidden risk analysis yet."], "neutral"); + renderInsightList(ui.aiRecruiterSignals, ["Run an analysis to simulate recruiter feedback."], "neutral"); + renderRoadmap(["Roadmap will appear after analysis."]); + renderCareerPath({ + title: "Run an analysis to generate role fit", + confidence: 0, + summary: "Career path recommendations are generated from your public GitHub portfolio signals.", + nextSkills: ["Add repositories and analyze profile to unlock recommendations."] + }); + ui.aiRecruiterVerdict.textContent = "Pending"; + ui.aiRecruiterVerdict.className = "chip chip-neutral"; + ui.aiRecruiterSummary.textContent = "Run an analysis to get recruiter-style interview feedback simulation."; + ui.hireabilityScore.textContent = "--"; + ui.hireabilityHint.textContent = "Weighted recruiter-fit index"; + ui.readinessLevel.textContent = "--"; + ui.readinessHint.textContent = "Analyze a profile to classify readiness"; + ui.readinessBar.style.width = "0%"; + + if (state.history[0]) { + ui.profileInput.value = state.history[0]; + } +} + +function bindEvents() { + ui.analyzeBtn.addEventListener("click", handleAnalyze); + + ui.profileInput.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + handleAnalyze(); + } + }); + + ui.themeToggleBtn.addEventListener("click", () => { + const currentTheme = document.documentElement.getAttribute("data-theme") || "light"; + const nextTheme = currentTheme === "dark" ? "light" : "dark"; + applyTheme(nextTheme); + + if (state.analysisResult) { + renderCharts(state.analysisResult); + } + }); + + ui.downloadReportBtn.addEventListener("click", downloadMarkdownReport); + + const persistToken = () => { + state.token = ui.tokenInput.value.trim(); + if (state.token) { + localStorage.setItem(STORAGE_KEYS.token, state.token); + } else { + localStorage.removeItem(STORAGE_KEYS.token); + } + }; + + ui.tokenInput.addEventListener("change", persistToken); + ui.tokenInput.addEventListener("blur", persistToken); +} + +async function handleAnalyze() { + clearBanners(); + + const parsed = parseProfileInput(ui.profileInput.value); + if (!parsed.ok) { + showError(parsed.error); + return; + } + + const username = parsed.username; + state.currentUsername = username; + ui.profileInput.value = username; + + state.token = ui.tokenInput.value.trim(); + if (state.token) { + localStorage.setItem(STORAGE_KEYS.token, state.token); + } else { + localStorage.removeItem(STORAGE_KEYS.token); + } + + if (state.abortController) { + state.abortController.abort(); + } + + const controller = new AbortController(); + state.abortController = controller; + + setLoading(true, "Preparing analysis..."); + + try { + let analysis = getCachedAnalysis(username, Boolean(state.token)); + + if (analysis) { + setLoading(true, "Loaded cached analysis from the last 10 minutes..."); + } else { + analysis = await runAnalysisPipeline(username, state.token, controller.signal); + setCachedAnalysis(username, Boolean(state.token), analysis); + } + + if (controller.signal.aborted) { + return; + } + + state.analysisResult = analysis; + ui.downloadReportBtn.disabled = false; + + saveHistory(username); + renderHistory(); + renderAnalysis(analysis); + } catch (error) { + if (error.name === "AbortError") { + return; + } + + if (error instanceof GitHubError && error.type === "Unauthorized" && state.token) { + showError("Token appears invalid. Retrying once without token."); + try { + const fallbackAnalysis = await runAnalysisPipeline(username, "", controller.signal); + if (controller.signal.aborted) { + return; + } + + state.analysisResult = fallbackAnalysis; + ui.downloadReportBtn.disabled = false; + + saveHistory(username); + renderHistory(); + renderAnalysis(fallbackAnalysis); + } catch (fallbackError) { + if (fallbackError.name !== "AbortError") { + handleAnalysisError(fallbackError); + } + } + } else { + handleAnalysisError(error); + } + } finally { + if (state.abortController === controller) { + state.abortController = null; + } + setLoading(false); + } +} + +function handleAnalysisError(error) { + if (!(error instanceof GitHubError)) { + showError("Unexpected error while analyzing the profile."); + return; + } + + if (error.type === "NotFound") { + showError("Profile not found. Enter a valid GitHub username or GitHub profile URL."); + return; + } + + if (error.type === "RateLimited") { + showError("GitHub API rate limit reached."); + showRateLimit(error.details.resetAt || null); + return; + } + + if (error.type === "Unauthorized") { + showError("GitHub token is invalid or expired. Update or clear the token and retry."); + return; + } + + if (error.type === "Network") { + showError("Network issue while contacting GitHub. Please retry."); + return; + } + + showError(error.message || "GitHub API request failed."); +} + +function parseProfileInput(rawInput) { + const value = (rawInput || "").trim(); + + if (!value) { + return { ok: false, error: "Enter a GitHub username or profile URL to analyze." }; + } + + let candidate = value; + + if (/github\.com\//i.test(candidate) && !/^https?:\/\//i.test(candidate)) { + candidate = `https://${candidate}`; + } + + if (/^https?:\/\//i.test(candidate)) { + try { + const url = new URL(candidate); + const host = url.hostname.toLowerCase(); + if (host !== "github.com" && host !== "www.github.com") { + return { ok: false, error: "URL must be a valid github.com profile URL." }; + } + + const segments = url.pathname.split("/").filter(Boolean); + if (!segments.length) { + return { ok: false, error: "GitHub URL is missing a username." }; + } + + candidate = segments[0]; + } catch { + return { ok: false, error: "Invalid URL format. Use a username or a github.com profile URL." }; + } + } + + candidate = candidate.replace(/^@+/, "").trim(); + + if (candidate.endsWith(".git")) { + candidate = candidate.slice(0, -4); + } + + const isValid = /^[A-Za-z0-9-]{1,39}$/.test(candidate) && !candidate.startsWith("-") && !candidate.endsWith("-"); + + if (!isValid) { + return { ok: false, error: "Invalid GitHub username format." }; + } + + return { ok: true, username: candidate.toLowerCase() }; +} + +async function runAnalysisPipeline(username, token, signal) { + setLoading(true, "Fetching profile..."); + const profileResponse = await githubRequest(`/users/${encodeURIComponent(username)}`, { token, signal }); + const profile = profileResponse.data; + + setLoading(true, "Fetching public repositories..."); + const allRepos = await fetchAllRepositories(profile.login, token, signal); + const scorableRepos = allRepos.filter((repo) => !repo.fork); + + setLoading(true, "Collecting repository language and README data..."); + const deepEnrichment = await enrichTopRepositories(profile.login, scorableRepos, token, signal); + const deepMap = new Map(deepEnrichment.items.map((item) => [item.id, item])); + + const enrichedScorableRepos = scorableRepos.map((repo) => { + const extra = deepMap.get(repo.id); + return { + ...repo, + _languageBytes: extra ? extra.languageBytes : null, + _languageChecked: extra ? extra.languageChecked : false, + _hasReadme: extra ? extra.hasReadme : null, + _readmeChecked: extra ? extra.readmeChecked : false + }; + }); + + setLoading(true, "Fetching issue and PR contribution counts..."); + const contributions = await fetchContributionSignals(profile.login, token, signal); + + setLoading(true, "Checking pinned repositories..."); + const pinnedFromGraphql = token ? await fetchPinnedRepositories(profile.login, token, signal) : null; + + setLoading(true, "Calculating recruiter score and insights..."); + return buildAnalysisResult({ + profile, + scorableRepos: enrichedScorableRepos, + contributions, + partial: deepEnrichment.partial, + pinnedFromGraphql + }); +} + +async function fetchAllRepositories(username, token, signal) { + const repos = []; + + for (let page = 1; page <= MAX_REPO_PAGES; page += 1) { + const path = `/users/${encodeURIComponent(username)}/repos?type=owner&sort=updated&per_page=100&page=${page}`; + const response = await githubRequest(path, { token, signal }); + const data = Array.isArray(response.data) ? response.data : []; + + repos.push(...data); + + if (data.length < 100) { + break; + } + } + + return repos; +} + +async function enrichTopRepositories(owner, scorableRepos, token, signal) { + const candidates = [...scorableRepos] + .sort((a, b) => preRankImportanceScore(b) - preRankImportanceScore(a)) + .slice(0, MAX_DEEP_REPOS); + + const partial = { + deepRepoCount: candidates.length, + languageChecked: 0, + readmeChecked: 0, + languageFailures: 0, + readmeFailures: 0 + }; + + const items = await mapWithConcurrency(candidates, CONCURRENCY_LIMIT, async (repo) => { + return fetchRepoDeepMeta(owner, repo, token, signal, partial); + }); + + return { items, partial }; +} + +async function fetchRepoDeepMeta(owner, repo, token, signal, partial) { + const details = { + id: repo.id, + languageBytes: null, + languageChecked: false, + hasReadme: null, + readmeChecked: false + }; + + try { + const languagesResponse = await githubRequest( + `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo.name)}/languages`, + { token, signal } + ); + details.languageBytes = languagesResponse.data && typeof languagesResponse.data === "object" ? languagesResponse.data : {}; + details.languageChecked = true; + partial.languageChecked += 1; + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + partial.languageFailures += 1; + } + + try { + await githubRequest( + `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo.name)}/readme`, + { token, signal } + ); + details.hasReadme = true; + details.readmeChecked = true; + partial.readmeChecked += 1; + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + + if (error instanceof GitHubError && error.type === "NotFound") { + details.hasReadme = false; + details.readmeChecked = true; + partial.readmeChecked += 1; + } else { + partial.readmeFailures += 1; + } + } + + return details; +} + +async function fetchContributionSignals(username, token, signal) { + const [prCount, issueCount] = await Promise.all([ + fetchSearchCount(`author:${username} type:pr`, token, signal), + fetchSearchCount(`author:${username} type:issue`, token, signal) + ]); + + return { prCount, issueCount }; +} + +async function fetchSearchCount(query, token, signal) { + const path = `/search/issues?q=${encodeURIComponent(query)}&per_page=1`; + const response = await githubRequest(path, { token, signal }); + return Number(response.data && response.data.total_count) || 0; +} + +async function fetchPinnedRepositories(username, token, signal) { + const query = ` + query ($login: String!) { + user(login: $login) { + pinnedItems(first: 6, types: REPOSITORY) { + nodes { + ... on Repository { + name + url + stargazerCount + } + } + } + } + } + `; + + try { + const data = await githubGraphQL(query, { login: username }, token, signal); + const nodes = data && data.user && data.user.pinnedItems ? data.user.pinnedItems.nodes : []; + + if (!Array.isArray(nodes)) { + return null; + } + + return nodes + .filter((node) => node && node.name && node.url) + .map((node) => ({ + name: node.name, + url: node.url, + stars: Number(node.stargazerCount) || 0 + })); + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + return null; + } +} + +function buildAnalysisResult({ profile, scorableRepos, contributions, partial, pinnedFromGraphql }) { + const now = Date.now(); + const scorableCount = scorableRepos.length; + + const totalStars = scorableRepos.reduce((sum, repo) => sum + (Number(repo.stargazers_count) || 0), 0); + const totalForks = scorableRepos.reduce((sum, repo) => sum + (Number(repo.forks_count) || 0), 0); + const totalWatchers = scorableRepos.reduce((sum, repo) => sum + (Number(repo.watchers_count) || 0), 0); + + const descriptionCoverage = safeRatio( + scorableRepos.filter((repo) => Boolean((repo.description || "").trim())).length, + scorableCount + ); + + const readmeScannedRepos = scorableRepos.filter((repo) => repo._readmeChecked); + const readmeCoverage = safeRatio( + readmeScannedRepos.filter((repo) => repo._hasReadme === true).length, + readmeScannedRepos.length + ); + + const nonEmptyRepoRatio = safeRatio( + scorableRepos.filter((repo) => (Number(repo.size) || 0) > 0).length, + scorableCount + ); + + const homepageRatio = safeRatio( + scorableRepos.filter((repo) => Boolean((repo.homepage || "").trim())).length, + scorableCount + ); + + const topicsRatio = safeRatio( + scorableRepos.filter((repo) => Array.isArray(repo.topics) && repo.topics.length > 0).length, + scorableCount + ); + + const pushedDays = scorableRepos + .map((repo) => daysSinceDate(repo.pushed_at, now)) + .filter((days) => Number.isFinite(days)); + + const reposUpdated30d = pushedDays.filter((days) => days <= 30).length; + const reposUpdated90d = pushedDays.filter((days) => days <= 90).length; + const reposInactive180d = pushedDays.filter((days) => days > 180).length; + + const activityBuckets = { + updated30d: pushedDays.filter((days) => days <= 30).length, + updated31to90d: pushedDays.filter((days) => days > 30 && days <= 90).length, + updated91to180d: pushedDays.filter((days) => days > 90 && days <= 180).length, + updated181plus: pushedDays.filter((days) => days > 180).length + }; + + const lastPushRepo = [...scorableRepos] + .filter((repo) => repo.pushed_at) + .sort((a, b) => new Date(b.pushed_at).getTime() - new Date(a.pushed_at).getTime())[0] || null; + + const daysSinceLastPush = lastPushRepo ? daysSinceDate(lastPushRepo.pushed_at, now) : 9999; + + const activity = computeActivityInLastSixMonths(scorableRepos); + const activeMonthsLast6Ratio = activity.ratio; + + const starsPerRepo = scorableCount ? totalStars / scorableCount : 0; + const forksPerRepo = scorableCount ? totalForks / scorableCount : 0; + const watchersPerRepo = scorableCount ? totalWatchers / scorableCount : 0; + + const languageTotals = aggregateLanguageTotals(scorableRepos); + const languageEntries = Object.entries(languageTotals).sort((a, b) => b[1] - a[1]); + const uniqueLanguages = languageEntries.length; + const normalizedShannonEntropy = computeNormalizedEntropy(languageTotals); + const totalLanguageBytes = languageEntries.reduce((sum, [, bytes]) => sum + (Number(bytes) || 0), 0); + const dominantLanguage = languageEntries[0] ? languageEntries[0][0] : "Unknown"; + const dominantLanguageShare = + totalLanguageBytes > 0 && languageEntries[0] ? cap01((Number(languageEntries[0][1]) || 0) / totalLanguageBytes) : 0; + const topLanguages = languageEntries.slice(0, 5).map(([language]) => language); + + const topRepoByStars = [...scorableRepos].sort( + (a, b) => (Number(b.stargazers_count) || 0) - (Number(a.stargazers_count) || 0) + )[0] || null; + + const topRepoStars = topRepoByStars ? Number(topRepoByStars.stargazers_count) || 0 : 0; + + const reposUpdated90dRatio = safeRatio(reposUpdated90d, scorableCount); + const reposUpdated30dRatio = safeRatio(reposUpdated30d, scorableCount); + + const recencyBucket = getRecencyBucket(daysSinceLastPush); + + const subscores = { + documentationQuality: scoreFromRatio(0.75 * readmeCoverage + 0.25 * descriptionCoverage), + codeActivityConsistency: scoreFromRatio(0.6 * activeMonthsLast6Ratio + 0.4 * reposUpdated90dRatio), + projectPopularity: scoreFromRatio( + 0.6 * cap01(starsPerRepo / 50) + + 0.25 * cap01(forksPerRepo / 20) + + 0.15 * cap01(watchersPerRepo / 20) + ), + repositoryCompleteness: scoreFromRatio( + 0.5 * nonEmptyRepoRatio + + 0.3 * homepageRatio + + 0.2 * topicsRatio + ), + languageDiversity: scoreFromRatio( + 0.7 * cap01(uniqueLanguages / 8) + + 0.3 * normalizedShannonEntropy + ), + recentActivity: scoreFromRatio(0.7 * recencyBucket + 0.3 * reposUpdated30dRatio), + impactSignals: scoreFromRatio( + 0.35 * cap01(topRepoStars / 300) + + 0.25 * cap01((Number(profile.followers) || 0) / 500) + + 0.25 * cap01(contributions.prCount / 200) + + 0.15 * cap01(contributions.issueCount / 100) + ) + }; + + const overallScore = clampToRange( + Math.round( + (subscores.documentationQuality * WEIGHTS.documentationQuality + + subscores.codeActivityConsistency * WEIGHTS.codeActivityConsistency + + subscores.projectPopularity * WEIGHTS.projectPopularity + + subscores.repositoryCompleteness * WEIGHTS.repositoryCompleteness + + subscores.languageDiversity * WEIGHTS.languageDiversity + + subscores.recentActivity * WEIGHTS.recentActivity + + subscores.impactSignals * WEIGHTS.impactSignals) / 100 + ), + 0, + 100 + ); + + const rankedRepos = buildRankedRepositories(scorableRepos); + + const pinnedRepos = pinnedFromGraphql && pinnedFromGraphql.length + ? { + source: "graphql", + items: pinnedFromGraphql + } + : { + source: "fallback", + items: rankedRepos.slice(0, 6).map((repo) => ({ + name: repo.name, + url: repo.url, + stars: repo.stars + })) + }; + + const metrics = { + scorableRepoCount: scorableCount, + totalStars, + totalForks, + totalWatchers, + readmeCoverage, + reposUpdated30d, + reposUpdated90d, + daysSinceLastPush, + authoredPRCount: contributions.prCount, + authoredIssueCount: contributions.issueCount, + uniqueLanguages, + topLanguages, + dominantLanguage, + dominantLanguageShare, + + descriptionCoverage, + descriptionlessRatio: cap01(1 - descriptionCoverage), + activeMonthsLast6: activity.activeMonths, + activeMonthsLast6Ratio, + reposInactive180d, + reposInactive180dRatio: safeRatio(reposInactive180d, scorableCount), + emptyRepoRatio: cap01(1 - nonEmptyRepoRatio), + nonEmptyRepoRatio, + homepageRatio, + topicsRatio, + starsPerRepo, + forksPerRepo, + watchersPerRepo, + reposUpdated30dRatio, + reposUpdated90dRatio, + recencyBucket, + topRepoStars, + normalizedShannonEntropy, + readmeSampleSize: readmeScannedRepos.length, + lastPushDate: lastPushRepo ? lastPushRepo.pushed_at : null, + partialReadmeFailures: partial.readmeFailures, + partialLanguageFailures: partial.languageFailures, + deepRepoCount: partial.deepRepoCount, + activityBuckets + }; + + const strengths = buildStrengths(subscores, metrics, rankedRepos); + const redFlags = buildRedFlags(subscores, metrics); + const suggestions = buildSuggestions(subscores, metrics, rankedRepos, pinnedRepos); + const hiddenRisks = buildHiddenRisks(subscores, metrics, rankedRepos); + const hireabilityScore = calculateHireabilityScore(subscores, overallScore, hiddenRisks); + const readiness = classifyReadiness(hireabilityScore, overallScore); + const careerPath = buildCareerPathRecommendation(metrics, rankedRepos, subscores); + const improvementRoadmap = buildImprovementRoadmap(subscores, metrics, rankedRepos, careerPath, hiddenRisks); + const recruiterSimulation = buildRecruiterSimulation({ + overallScore, + hireabilityScore, + readiness, + subscores, + metrics, + strengths, + redFlags, + hiddenRisks, + rankedRepos + }); + + const grade = scoreToGrade(overallScore); + const scoreSummary = buildScoreSummary(overallScore, grade, subscores, metrics, hireabilityScore, readiness.label); + + return { + profile: { + login: profile.login, + name: profile.name || profile.login, + htmlUrl: profile.html_url, + followers: Number(profile.followers) || 0, + following: Number(profile.following) || 0, + publicRepos: Number(profile.public_repos) || 0, + avatarUrl: profile.avatar_url, + bio: profile.bio || "No bio provided.", + createdAt: profile.created_at, + updatedAt: profile.updated_at + }, + generatedAt: new Date().toISOString(), + weights: WEIGHTS, + subscores, + overallScore, + hireabilityScore, + readiness, + readinessLevel: readiness.label, + metrics, + strengths, + redFlags, + suggestions, + hiddenRisks, + recruiterSimulation, + careerPath, + improvementRoadmap, + pinnedRepos, + rankedRepos: rankedRepos.map((repo) => ({ + name: repo.name, + url: repo.url, + importance: repo.importance, + stars: repo.stars, + forks: repo.forks, + watchers: repo.watchers, + pushedAt: repo.pushedAt, + hasReadme: repo.hasReadme, + language: repo.language, + homepage: repo.homepage, + topicsCount: repo.topicsCount, + hasDescription: repo.hasDescription, + isEmpty: repo.isEmpty, + readmeKnown: repo.readmeKnown + })), + languageTotals, + grade, + scoreSummary + }; +} + +function aggregateLanguageTotals(repos) { + const totals = {}; + + repos.forEach((repo) => { + let hasDetailedBreakdown = false; + + if (repo._languageBytes && typeof repo._languageBytes === "object") { + const entries = Object.entries(repo._languageBytes); + if (entries.length) { + entries.forEach(([language, bytes]) => { + totals[language] = (totals[language] || 0) + (Number(bytes) || 0); + }); + hasDetailedBreakdown = true; + } + } + + if (!hasDetailedBreakdown && repo.language) { + totals[repo.language] = (totals[repo.language] || 0) + 1000; + } + }); + + return totals; +} + +function computeNormalizedEntropy(totals) { + const values = Object.values(totals).filter((value) => value > 0); + const total = values.reduce((sum, value) => sum + value, 0); + + if (total <= 0 || values.length <= 1) { + return 0; + } + + const entropy = values.reduce((sum, value) => { + const p = value / total; + return sum - p * Math.log2(p); + }, 0); + + const maxEntropy = Math.log2(values.length); + return maxEntropy > 0 ? entropy / maxEntropy : 0; +} + +function buildRankedRepositories(repos) { + if (!repos.length) { + return []; + } + + const ranked = repos.map((repo) => { + const ageDays = daysSinceDate(repo.pushed_at); + + const recencyBoost = ageDays <= 30 ? 15 : ageDays <= 90 ? 8 : ageDays <= 180 ? 4 : 0; + const readmeBoost = repo._hasReadme === true ? 8 : 0; + const homepageBoost = (repo.homepage || "").trim() ? 4 : 0; + const topicsBoost = Array.isArray(repo.topics) && repo.topics.length > 0 ? 3 : 0; + const descriptionBoost = (repo.description || "").trim() ? 3 : 0; + const sizeBoost = (Number(repo.size) || 0) > 0 ? 2 : 0; + + const rawImportance = + (Number(repo.stargazers_count) || 0) * 4 + + (Number(repo.forks_count) || 0) * 3 + + (Number(repo.watchers_count) || 0) * 2 + + recencyBoost + + readmeBoost + + homepageBoost + + topicsBoost + + descriptionBoost + + sizeBoost; + + return { + name: repo.name, + url: repo.html_url, + rawImportance, + stars: Number(repo.stargazers_count) || 0, + forks: Number(repo.forks_count) || 0, + watchers: Number(repo.watchers_count) || 0, + pushedAt: repo.pushed_at, + hasReadme: repo._hasReadme === true, + readmeKnown: Boolean(repo._readmeChecked), + language: repo.language || "Unknown", + homepage: (repo.homepage || "").trim(), + topicsCount: Array.isArray(repo.topics) ? repo.topics.length : 0, + hasDescription: Boolean((repo.description || "").trim()), + isEmpty: (Number(repo.size) || 0) === 0 + }; + }); + + const maxRaw = Math.max(...ranked.map((repo) => repo.rawImportance), 1); + + ranked.forEach((repo) => { + repo.importance = clampToRange(Math.round((repo.rawImportance / maxRaw) * 100), 0, 100); + }); + + return ranked.sort((a, b) => b.importance - a.importance || b.stars - a.stars); +} + +function buildStrengths(subscores, metrics, rankedRepos) { + const strengths = []; + + if (subscores.codeActivityConsistency >= 70) { + strengths.push( + `Strong activity consistency with ${metrics.activeMonthsLast6}/6 active months and ${metrics.reposUpdated90d} repositories updated in the last 90 days.` + ); + } + + if (subscores.projectPopularity >= 70) { + const top = rankedRepos[0]; + strengths.push( + `Good popularity signals: ${metrics.totalStars} total stars and ${top ? `${top.name} as a leading project` : "multiple visible projects"}.` + ); + } + + if (subscores.languageDiversity >= 70) { + strengths.push(`Diverse technical stack with ${metrics.uniqueLanguages} detected languages.`); + } + + if (subscores.recentActivity >= 70) { + strengths.push(`Recent contribution momentum: latest push was ${metrics.daysSinceLastPush} day(s) ago.`); + } + + if (subscores.documentationQuality >= 70) { + strengths.push( + `Documentation quality is strong with ${(metrics.readmeCoverage * 100).toFixed(0)}% README coverage in sampled repositories.` + ); + } + + if (subscores.impactSignals >= 70) { + strengths.push( + `Impact signals are healthy with ${metrics.authoredPRCount} authored PRs and ${metrics.authoredIssueCount} authored issues.` + ); + } + + if (!strengths.length) { + strengths.push( + `Public portfolio is visible (${metrics.scorableRepoCount} non-fork repositories) but still needs stronger recruiter-facing signals.` + ); + } + + return strengths.slice(0, 6); +} + +function buildRedFlags(subscores, metrics) { + const redFlags = []; + + if (metrics.readmeCoverage < 0.5) { + redFlags.push( + `Low README coverage (${(metrics.readmeCoverage * 100).toFixed(0)}%) in sampled repositories makes project intent harder to evaluate.` + ); + } + + if (metrics.reposInactive180dRatio > 0.5) { + redFlags.push( + `${metrics.reposInactive180d} of ${metrics.scorableRepoCount} repositories have been inactive for more than 180 days.` + ); + } + + if (metrics.emptyRepoRatio > 0.3) { + redFlags.push( + `${(metrics.emptyRepoRatio * 100).toFixed(0)}% of repositories look empty or near-empty based on repository size.` + ); + } + + if (metrics.descriptionlessRatio > 0.4) { + redFlags.push( + `${(metrics.descriptionlessRatio * 100).toFixed(0)}% of repositories have missing descriptions, which weakens recruiter readability.` + ); + } + + if (metrics.daysSinceLastPush > 90) { + redFlags.push(`No recent pushes in the last 90 days (latest push was ${metrics.daysSinceLastPush} day(s) ago).`); + } + + if (subscores.impactSignals < 40) { + redFlags.push( + `Impact signals are weak (${subscores.impactSignals}/100) due to low follower, PR, issue, or top-repo traction.` + ); + } + + if (!redFlags.length) { + redFlags.push("No major red flags detected from the available public signals."); + } + + return redFlags.slice(0, 6); +} + +function buildSuggestions(subscores, metrics, rankedRepos, pinnedRepos) { + const suggestions = []; + + const missingReadmeRepos = rankedRepos + .filter((repo) => repo.readmeKnown && !repo.hasReadme) + .slice(0, 3) + .map((repo) => repo.name); + + const staleRepos = rankedRepos + .filter((repo) => daysSinceDate(repo.pushedAt) > 180) + .slice(0, 3) + .map((repo) => repo.name); + + const noHomepageRepos = rankedRepos + .filter((repo) => !repo.homepage) + .slice(0, 3) + .map((repo) => repo.name); + + const emptyRepos = rankedRepos + .filter((repo) => repo.isEmpty) + .slice(0, 3) + .map((repo) => repo.name); + + const missingDescriptionRepos = rankedRepos + .filter((repo) => !repo.hasDescription) + .slice(0, 3) + .map((repo) => repo.name); + + if (missingReadmeRepos.length) { + suggestions.push({ + priority: 120 - subscores.documentationQuality, + text: `Add README files to ${joinRepoNames(missingReadmeRepos)} with problem statement, setup, usage, and outcomes.` + }); + } + + if (staleRepos.length) { + suggestions.push({ + priority: 120 - subscores.recentActivity, + text: `Update or archive stale repositories (${joinRepoNames(staleRepos)}) so recruiters see a maintained portfolio.` + }); + } + + if (noHomepageRepos.length) { + suggestions.push({ + priority: 110 - subscores.repositoryCompleteness, + text: `Add live demo or homepage links for ${joinRepoNames(noHomepageRepos)} to improve project completeness.` + }); + } + + if (emptyRepos.length) { + suggestions.push({ + priority: 108 - subscores.repositoryCompleteness, + text: `Complete or archive near-empty repositories (${joinRepoNames(emptyRepos)}) to reduce noise in your public profile.` + }); + } + + if (missingDescriptionRepos.length || metrics.descriptionlessRatio > 0.4) { + const target = missingDescriptionRepos.length ? joinRepoNames(missingDescriptionRepos) : "your weakest repos"; + suggestions.push({ + priority: 109 - subscores.documentationQuality, + text: `Improve project descriptions for ${target} with concise problem, stack, and outcomes so recruiters can scan faster.` + }); + } + + if (subscores.codeActivityConsistency < 70) { + suggestions.push({ + priority: 105 - subscores.codeActivityConsistency, + text: `Improve commit consistency: target at least 1 meaningful commit per week for 8 weeks and aim for 4/6 active months.` + }); + } + + if (subscores.impactSignals < 70) { + suggestions.push({ + priority: 105 - subscores.impactSignals, + text: `Increase impact signals by targeting 2 authored PRs and 2 authored issues per month on relevant repositories.` + }); + } + + if (pinnedRepos.source === "fallback") { + const suggestedPins = rankedRepos.slice(0, 3).map((repo) => repo.name); + suggestions.push({ + priority: 102, + text: `Pin your strongest repositories (${joinRepoNames(suggestedPins)}) so recruiters immediately see your best work.` + }); + } + + if (metrics.topicsRatio < 0.5) { + suggestions.push({ + priority: 95, + text: "Add GitHub topics/tags to your key repositories to improve discovery and communicate stack relevance quickly." + }); + } + + if (metrics.uniqueLanguages < 3) { + suggestions.push({ + priority: 88, + text: "Showcase at least one additional production-quality project in a different language or framework to broaden stack signals." + }); + } + + const sorted = suggestions + .sort((a, b) => b.priority - a.priority) + .map((entry) => entry.text) + .filter((text, index, arr) => arr.indexOf(text) === index); + + const defaults = [ + `Raise README coverage from ${(metrics.readmeCoverage * 100).toFixed(0)}% to at least 80% in your top repositories.`, + `Set a monthly maintenance pass to close stale issues and refresh pinned projects with recent commits.`, + `Improve repository completeness by ensuring every flagship repo has README, topics, and a demo/homepage link.`, + `Create a monthly portfolio changelog in one pinned repository to highlight recent improvements and impact.`, + `Publish measurable project outcomes (users, performance, business value) in your top README files.` + ]; + + while (sorted.length < 5 && defaults.length) { + sorted.push(defaults.shift()); + } + + return sorted.slice(0, 7); +} + +function buildHiddenRisks(subscores, metrics, rankedRepos) { + const risks = []; + + if (metrics.dominantLanguageShare > 0.78 && metrics.uniqueLanguages >= 2) { + risks.push( + `Stack concentration risk: ${metrics.dominantLanguage} accounts for ${formatPercent(metrics.dominantLanguageShare)} of detected language volume.` + ); + } + + const topWithoutHomepage = rankedRepos + .slice(0, 5) + .filter((repo) => !repo.homepage) + .map((repo) => repo.name); + + if (topWithoutHomepage.length >= 3) { + risks.push( + `Conversion risk: ${topWithoutHomepage.length} of your top repositories lack demo/homepage links (${joinRepoNames(topWithoutHomepage.slice(0, 3))}).` + ); + } + + const starConcentration = metrics.totalStars > 0 ? metrics.topRepoStars / metrics.totalStars : 0; + if (starConcentration > 0.85 && metrics.totalStars >= 20 && rankedRepos.length >= 4) { + risks.push( + `Brand concentration risk: one repository drives ${formatPercent(starConcentration)} of total stars, so portfolio impact is overly dependent on a single project.` + ); + } + + if (metrics.authoredPRCount < 5 && metrics.scorableRepoCount >= 10) { + risks.push( + `Collaboration signal risk: only ${metrics.authoredPRCount} authored PRs across a portfolio of ${metrics.scorableRepoCount} repositories.` + ); + } + + if (metrics.reposUpdated90dRatio > 0.45 && metrics.reposUpdated30dRatio < 0.15) { + risks.push( + "Momentum decay risk: older recent activity exists, but updates in the last 30 days are sparse." + ); + } + + if (subscores.repositoryCompleteness < 50 && metrics.topicsRatio < 0.35) { + risks.push( + "Discoverability risk: weak metadata coverage (topics and project links) can reduce recruiter confidence during quick profile scans." + ); + } + + if (!risks.length) { + risks.push("No hidden structural risks detected beyond the visible red flags."); + } + + return risks.slice(0, 6); +} + +function calculateHireabilityScore(subscores, overallScore, hiddenRisks) { + const riskCount = hiddenRisks.filter((item) => !/^No hidden/i.test(item)).length; + const penalty = Math.min(riskCount * 4, 16); + + const raw = + overallScore * 0.45 + + subscores.impactSignals * 0.2 + + subscores.recentActivity * 0.15 + + subscores.documentationQuality * 0.1 + + subscores.repositoryCompleteness * 0.1; + + return clampToRange(Math.round(raw - penalty), 0, 100); +} + +function classifyReadiness(hireabilityScore, overallScore) { + const blended = clampToRange(Math.round((hireabilityScore + overallScore) / 2), 0, 100); + + if (blended >= 85) { + return { + label: "Recruiter-Ready", + severity: "good", + percent: blended, + summary: "Portfolio can usually pass recruiter screens without major concerns." + }; + } + + if (blended >= 70) { + return { + label: "Interview-Ready", + severity: "good", + percent: blended, + summary: "Strong enough for interview pipelines with minor polish opportunities." + }; + } + + if (blended >= 55) { + return { + label: "Emerging", + severity: "warn", + percent: blended, + summary: "Promising portfolio that needs stronger consistency and presentation signals." + }; + } + + return { + label: "Foundation Stage", + severity: "risk", + percent: blended, + summary: "Core work is visible, but recruiter confidence is currently limited." + }; +} + +function buildRecruiterSimulation({ + overallScore, + hireabilityScore, + readiness, + subscores, + metrics, + strengths, + redFlags, + hiddenRisks, + rankedRepos +}) { + let verdict = "Not Ready for Interview Loop"; + let level = "risk"; + + if (hireabilityScore >= 82) { + verdict = "Strong Consider"; + level = "good"; + } else if (hireabilityScore >= 68) { + verdict = "Proceed to Technical Screen"; + level = "warn"; + } else if (hireabilityScore >= 52) { + verdict = "Potential with Portfolio Polish"; + level = "warn"; + } + + const leadingRepo = rankedRepos[0]; + const summary = `${verdict}: overall ${overallScore}/100 and hireability ${hireabilityScore}/100. Current readiness is ${readiness.label}. ${ + leadingRepo ? `Top signal comes from ${leadingRepo.name} (${leadingRepo.importance}/100 importance).` : "No standout repository identified yet." + }`; + + const signals = []; + if (strengths[0]) { + signals.push(`Positive signal: ${strengths[0]}`); + } + if (metrics.totalStars > 0) { + signals.push(`Market traction: ${metrics.totalStars} total stars across ${metrics.scorableRepoCount} scored repositories.`); + } else { + signals.push("Market traction is minimal; add demos and visibility to increase external validation."); + } + const firstRedFlag = redFlags.find((item) => !/No major red flags/i.test(item)); + if (firstRedFlag) { + signals.push(`Primary concern: ${firstRedFlag}`); + } + const firstHiddenRisk = hiddenRisks.find((item) => !/^No hidden/i.test(item)); + if (firstHiddenRisk) { + signals.push(`Hidden concern: ${firstHiddenRisk}`); + } + if (subscores.impactSignals < 60) { + signals.push("Interview risk: impact signals are below benchmark for competitive product roles."); + } + + return { + verdict, + level, + summary, + signals: signals.slice(0, 6) + }; +} + +function buildCareerPathRecommendation(metrics, rankedRepos, subscores) { + const languages = (metrics.topLanguages || []).map((lang) => lang.toLowerCase()); + const hasAnyLanguage = (list) => list.some((lang) => languages.includes(lang)); + + let title = "Generalist Software Engineer"; + let summary = "Your repositories indicate broad engineering capability across multiple project types."; + let nextSkills = [ + "Create 2 case-study READMEs that highlight architecture decisions and measurable outcomes.", + "Add live demos to your top projects to improve recruiter conversion.", + "Contribute at least 2 PRs/month to repositories related to your target role." + ]; + + if (hasAnyLanguage(["javascript", "typescript"])) { + title = "Full-Stack JavaScript Engineer"; + summary = "Your language mix and repository profile align best with product-focused full-stack roles."; + nextSkills = [ + "Ship one end-to-end project with production deployment, auth, and monitoring.", + "Document system architecture and tradeoffs for your top JavaScript/TypeScript repositories.", + "Add test coverage and CI status badges to your top 3 repositories." + ]; + } else if (hasAnyLanguage(["python"])) { + title = "Data / AI Engineer"; + summary = "Python-heavy activity indicates strong alignment with data and AI engineering tracks."; + nextSkills = [ + "Publish one reproducible ML/data project with dataset, metrics, and inference/demo endpoint.", + "Add evaluation methodology and model limitations to README docs.", + "Showcase pipeline automation and observability in at least one repository." + ]; + } else if (hasAnyLanguage(["java", "kotlin", "scala"])) { + title = "Backend Platform Engineer"; + summary = "JVM-oriented repositories and contribution signals fit backend and platform engineering roles."; + nextSkills = [ + "Demonstrate API design quality with versioned contracts and load/performance notes.", + "Add reliability signals: retries, circuit breakers, and structured logging.", + "Publish a backend project with deployment and scalability benchmarks." + ]; + } else if (hasAnyLanguage(["go", "rust", "c", "c++"])) { + title = "Systems / Infrastructure Engineer"; + summary = "Your dominant languages suggest strongest fit for systems and infrastructure engineering paths."; + nextSkills = [ + "Build one performance-focused project with clear latency/throughput benchmarks.", + "Document low-level design choices and profiling evidence in README.", + "Add automation scripts for build/test/release workflows." + ]; + } else if (hasAnyLanguage(["swift", "objective-c", "dart"])) { + title = "Mobile Application Engineer"; + summary = "Language signals indicate strongest fit for modern mobile development roles."; + nextSkills = [ + "Publish a shipped-quality mobile app with store-ready documentation and screenshots.", + "Add crash/error monitoring strategy and release notes cadence.", + "Showcase offline support and performance considerations." + ]; + } + + const confidence = clampToRange( + Math.round( + 52 + + metrics.dominantLanguageShare * 18 + + Math.min(metrics.uniqueLanguages, 5) * 4 + + (subscores.codeActivityConsistency >= 70 ? 6 : 0) + + (subscores.impactSignals >= 60 ? 6 : 0) + ), + 35, + 95 + ); + + const weakestSubscore = Object.entries(subscores).sort((a, b) => a[1] - b[1])[0][0]; + const weakestAdviceMap = { + documentationQuality: "Strengthen documentation quality to make your projects legible to non-engineers.", + codeActivityConsistency: "Establish a visible weekly commit cadence to reduce perceived delivery risk.", + projectPopularity: "Increase visibility through demos, developer posts, and open-source collaboration.", + repositoryCompleteness: "Add project metadata (description, topics, demos) to boost portfolio clarity.", + languageDiversity: "Add one adjacent-stack project to signal broader technical range.", + recentActivity: "Prioritize recent updates in top repositories to maintain recruiter confidence.", + impactSignals: "Increase authored PR and issue activity in relevant external repositories." + }; + + nextSkills.push(weakestAdviceMap[weakestSubscore]); + + if (rankedRepos[0] && rankedRepos[0].importance >= 80) { + nextSkills.push(`Position \`${rankedRepos[0].name}\` as flagship project with a complete case-study README.`); + } + + return { + title, + confidence, + summary, + nextSkills: dedupe(nextSkills).slice(0, 6) + }; +} + +function buildImprovementRoadmap(subscores, metrics, rankedRepos, careerPath, hiddenRisks) { + const roadmap = []; + const missingReadmeRepos = rankedRepos + .filter((repo) => repo.readmeKnown && !repo.hasReadme) + .slice(0, 2) + .map((repo) => repo.name); + const noHomepageRepos = rankedRepos + .filter((repo) => !repo.homepage) + .slice(0, 2) + .map((repo) => repo.name); + const staleRepos = rankedRepos + .filter((repo) => daysSinceDate(repo.pushedAt) > 180) + .slice(0, 2) + .map((repo) => repo.name); + + roadmap.push( + "Week 1: Set portfolio baseline by updating profile bio, pinning top repositories, and documenting measurable outcomes." + ); + + const deficitOrder = Object.entries(subscores) + .sort((a, b) => a[1] - b[1]) + .map(([key]) => key); + + deficitOrder.forEach((key) => { + if (key === "documentationQuality" && missingReadmeRepos.length) { + roadmap.push( + `Week 1-2: Add structured README files to ${joinRepoNames(missingReadmeRepos)} with problem, architecture, setup, and results.` + ); + } else if (key === "recentActivity" || key === "codeActivityConsistency") { + roadmap.push( + "Week 2-5: Maintain weekly commits (minimum 1 meaningful update/week) across at least 4 core repositories." + ); + } else if (key === "repositoryCompleteness" && noHomepageRepos.length) { + roadmap.push( + `Week 2-3: Add demo/homepage links and GitHub topics for ${joinRepoNames(noHomepageRepos)}.` + ); + } else if (key === "impactSignals") { + roadmap.push( + "Week 3-6: Target 8 authored PRs and 6 authored issues in repositories aligned to your target role." + ); + } else if (key === "projectPopularity") { + roadmap.push( + "Week 4: Publish concise demo posts and architecture threads to improve repository discoverability and star velocity." + ); + } else if (key === "languageDiversity" && metrics.uniqueLanguages < 4) { + roadmap.push( + "Week 5-7: Build one production-quality project in an adjacent stack to expand technical breadth signals." + ); + } + }); + + if (staleRepos.length) { + roadmap.push(`Week 3: Refresh or archive stale repositories (${joinRepoNames(staleRepos)}) to reduce portfolio noise.`); + } + + const firstHiddenRisk = hiddenRisks.find((item) => !/^No hidden/i.test(item)); + if (firstHiddenRisk) { + roadmap.push(`Week 4: Resolve hidden risk identified by analysis: ${firstHiddenRisk}`); + } + + roadmap.push( + `Week 8: Repackage top 3 projects for ${careerPath.title} positioning with recruiter-focused case studies and outcomes.` + ); + + const defaults = [ + "Set a monthly portfolio review reminder to keep all flagship repositories active and complete.", + "Add short demo videos/GIFs to your top repositories for faster recruiter evaluation." + ]; + + const uniqueRoadmap = dedupe(roadmap); + + while (uniqueRoadmap.length < 5 && defaults.length) { + uniqueRoadmap.push(defaults.shift()); + } + + return uniqueRoadmap.slice(0, 7); +} + +function buildScoreSummary(overallScore, grade, subscores, metrics, hireabilityScore, readinessLabel) { + const strongest = getNamedSubscore(Object.entries(subscores).sort((a, b) => b[1] - a[1])[0][0]); + const weakest = getNamedSubscore(Object.entries(subscores).sort((a, b) => a[1] - b[1])[0][0]); + + let summary = `Score ${overallScore}/100 (${grade}), hireability ${hireabilityScore}/100 (${readinessLabel}). Strongest area: ${strongest}. Biggest gap: ${weakest}.`; + + if (metrics.partialLanguageFailures || metrics.partialReadmeFailures) { + summary += ` Partial data warning: ${metrics.partialLanguageFailures + metrics.partialReadmeFailures} deep checks failed due to API limits or transient errors.`; + } + + return summary; +} + +function renderAnalysis(result) { + renderProfile(result); + renderScore(result); + renderSubscores(result.subscores); + renderScoringTransparency(result); + renderPinnedRepos(result.pinnedRepos); + renderRecruiterSimulation(result.recruiterSimulation); + renderInsightList(ui.strengthsList, result.strengths, "good"); + renderInsightList(ui.redFlagsList, result.redFlags, "risk"); + renderInsightList(ui.suggestionsList, result.suggestions, "warn"); + renderInsightList(ui.hiddenRisksList, result.hiddenRisks, "risk"); + renderCareerPath(result.careerPath); + renderRoadmap(result.improvementRoadmap); + renderRepoRanking(result.rankedRepos); + renderCharts(result); +} + +function renderProfile(result) { + const profile = result.profile; + const metrics = result.metrics; + + ui.avatarImg.src = profile.avatarUrl || "Images/logo.png"; + ui.avatarImg.alt = `${profile.login} avatar`; + + ui.profileName.textContent = profile.name; + ui.profileHandle.textContent = `@${profile.login}`; + + ui.profileLink.href = profile.htmlUrl; + ui.profileLink.textContent = profile.htmlUrl; + + ui.profileBio.textContent = profile.bio || "No bio provided."; + + ui.statRepos.textContent = String(profile.publicRepos); + ui.statFollowers.textContent = formatCompactNumber(profile.followers); + ui.statFollowing.textContent = formatCompactNumber(profile.following); + ui.statLastPush.textContent = metrics.lastPushDate ? formatDate(metrics.lastPushDate) : "No push data"; + ui.statPrCount.textContent = formatCompactNumber(metrics.authoredPRCount); + ui.statIssueCount.textContent = formatCompactNumber(metrics.authoredIssueCount); +} + +function renderScore(result) { + const severity = getSeverity(result.overallScore); + + ui.overallScore.textContent = String(result.overallScore); + ui.overallScoreRing.style.setProperty("--score-value", String(result.overallScore)); + ui.overallScoreRing.setAttribute("data-level", severity); + ui.scoreGrade.textContent = `Grade ${result.grade}`; + ui.scoreSummary.textContent = result.scoreSummary; + + const hireabilitySeverity = getSeverity(result.hireabilityScore); + ui.hireabilityScore.textContent = `${result.hireabilityScore}/100`; + ui.hireabilityScore.style.color = severityColor(hireabilitySeverity); + ui.hireabilityHint.textContent = "Calibrated from score, impact, recency, and hidden-risk penalties."; + + ui.readinessLevel.textContent = result.readiness.label; + ui.readinessLevel.style.color = severityColor(result.readiness.severity); + ui.readinessHint.textContent = result.readiness.summary; + ui.readinessBar.style.width = `${result.readiness.percent}%`; +} + +function renderSubscores(subscores) { + Object.entries(SUBSCORE_ID_MAP).forEach(([key, elementId]) => { + const element = document.getElementById(elementId); + const value = subscores[key]; + const severity = getSeverity(value); + + element.textContent = `${value}/100`; + element.className = `chip ${severityToChipClass(severity)}`; + }); +} + +function renderScoringTransparency(result) { + const { subscores, metrics } = result; + const details = { + documentationQuality: `README ${formatPercent(metrics.readmeCoverage)}, Desc ${formatPercent(metrics.descriptionCoverage)}`, + codeActivityConsistency: `${metrics.activeMonthsLast6}/6 months, ${formatPercent(metrics.reposUpdated90dRatio)} updated`, + projectPopularity: `Stars ${metrics.starsPerRepo.toFixed(1)}/repo, Forks ${metrics.forksPerRepo.toFixed(1)}/repo`, + repositoryCompleteness: `Non-empty ${formatPercent(metrics.nonEmptyRepoRatio)}, Topics ${formatPercent(metrics.topicsRatio)}`, + languageDiversity: `${metrics.uniqueLanguages} langs, Entropy ${metrics.normalizedShannonEntropy.toFixed(2)}`, + recentActivity: `${metrics.daysSinceLastPush}d last push, ${formatPercent(metrics.reposUpdated30dRatio)} updated`, + impactSignals: `Top repo ⭐ ${metrics.topRepoStars}, PRs ${metrics.authoredPRCount}, Issues ${metrics.authoredIssueCount}` + }; + + Object.entries(SCORING_EXPLAIN_ID_MAP).forEach(([key, elementId]) => { + const element = document.getElementById(elementId); + if (!element) { + return; + } + + const score = subscores[key]; + const severity = getSeverity(score); + element.textContent = `${score}/100 • ${details[key]}`; + element.className = `chip ${severityToChipClass(severity)}`; + }); +} + +function renderRecruiterSimulation(simulation) { + if (!simulation) { + ui.aiRecruiterVerdict.textContent = "Pending"; + ui.aiRecruiterVerdict.className = "chip chip-neutral"; + ui.aiRecruiterSummary.textContent = "Run an analysis to get recruiter-style interview feedback simulation."; + renderInsightList(ui.aiRecruiterSignals, ["No recruiter simulation available."], "neutral"); + return; + } + + ui.aiRecruiterVerdict.textContent = simulation.verdict; + ui.aiRecruiterVerdict.className = `chip ${severityToChipClass(simulation.level || "warn")}`; + ui.aiRecruiterSummary.textContent = simulation.summary; + renderInsightList(ui.aiRecruiterSignals, simulation.signals, simulation.level || "warn"); +} + +function renderCareerPath(careerPath) { + if (!careerPath) { + ui.careerPathTitle.textContent = "No career path recommendation"; + ui.careerPathSummary.textContent = "Run an analysis to unlock role fit recommendations."; + ui.careerConfidence.textContent = "--"; + ui.careerConfidence.className = "chip chip-neutral"; + renderInsightList(ui.careerSkillsList, ["No recommendations yet."], "neutral"); + return; + } + + ui.careerPathTitle.textContent = careerPath.title; + ui.careerPathSummary.textContent = careerPath.summary; + ui.careerConfidence.textContent = `${careerPath.confidence}% confidence`; + ui.careerConfidence.className = `chip ${severityToChipClass(getSeverity(careerPath.confidence))}`; + renderInsightList(ui.careerSkillsList, careerPath.nextSkills, "warn"); +} + +function renderRoadmap(roadmapItems) { + clearChildren(ui.roadmapList); + + if (!Array.isArray(roadmapItems) || !roadmapItems.length) { + const li = document.createElement("li"); + li.textContent = "No roadmap available."; + ui.roadmapList.appendChild(li); + return; + } + + roadmapItems.forEach((item) => { + const li = document.createElement("li"); + li.textContent = item; + ui.roadmapList.appendChild(li); + }); +} + +function renderPinnedRepos(pinnedRepos) { + clearChildren(ui.pinnedReposList); + + ui.pinnedSourceBadge.textContent = pinnedRepos.source; + ui.pinnedSourceBadge.className = `chip ${pinnedRepos.source === "graphql" ? "chip-good" : "chip-warn"}`; + + if (!pinnedRepos.items.length) { + appendEmptyState(ui.pinnedReposList, "No pinned repositories available."); + return; + } + + pinnedRepos.items.forEach((repo) => { + const li = document.createElement("li"); + li.className = "repo-item"; + + const left = document.createElement("div"); + const link = document.createElement("a"); + link.href = repo.url; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + link.textContent = repo.name; + link.className = "repo-link"; + + left.appendChild(link); + + const meta = document.createElement("div"); + meta.className = "repo-item-meta"; + meta.textContent = `Stars: ${formatCompactNumber(repo.stars)}`; + + li.appendChild(left); + li.appendChild(meta); + + ui.pinnedReposList.appendChild(li); + }); +} + +function renderInsightList(container, items, tone) { + clearChildren(container); + + if (!items || !items.length) { + appendEmptyState(container, "No insights available."); + return; + } -// modal close -modal.addEventListener('click', (e) => { if (e.target === modal) modal.classList.remove('show'); }); + items.forEach((item) => { + const li = document.createElement("li"); + li.textContent = item; -/* MAIN SEARCH FLOW */ -async function startSearch(username) { - if (!username) return; - showTyping(true); - try { - const profile = await fetch(API_ROOT + username).then(r => r.json()); - if (profile.message === 'Not Found') { alert('User not found'); showTyping(false); return; } - lastProfile = profile; - saveHistory(username); - await enrichAndRender(profile); - showTyping(false); - } catch (err) { console.error(err); showTyping(false); alert('Error fetching user'); } -} - -function showTyping(on) { - document.querySelectorAll('.dot').forEach(d => d.style.display = on ? 'inline-block' : 'none'); -} - -/* ENRICH WITH REPOS & LANGS */ -async function enrichAndRender(profile) { - const repos = await fetch(profile.repos_url + '?per_page=100').then(r => r.json()); - profile._repos = Array.isArray(repos) ? repos : []; - // languages - const langCount = {}; - profile._repos.forEach(r => { if (r.language) langCount[r.language] = (langCount[r.language] || 0) + 1; }); - profile._langCount = langCount; - renderProfile(profile); - renderRepos(profile._repos); - renderAnalytics(profile); - renderMap(profile); - renderTimeline(profile); - generateLeaderboardEntry(profile); -} - -/* RENDER PROFILE */ -function renderProfile(p) { - el('avatarImg').src = p.avatar_url; - el('fullName').innerText = p.name || p.login; - el('userLink').innerText = '@' + p.login; el('userLink').href = p.html_url; - el('joined').innerText = p.created_at ? 'Joined ' + new Date(p.created_at).toDateString() : 'Joined —'; - el('bio').innerText = p.bio || 'This profile has no bio.'; - el('statRepos').innerText = p.public_repos; - el('statFollowers').innerText = p.followers; - el('statFollowing').innerText = p.following; - el('lastSeen').innerText = new Date(p.updated_at || p.created_at).toLocaleString(); - el('mainBadge').innerText = computeMainBadge(p); - - // languages pills - const langWrap = el('languages'); langWrap.innerHTML = ''; - const langs = Object.entries(p._langCount).sort((a, b) => b[1] - a[1]); - langs.slice(0, 6).forEach(([lang, c]) => { const s = document.createElement('div'); s.className = 'pill'; s.innerText = `${lang} (${c})`; langWrap.appendChild(s); }); -} - -/* compute main badge */ -function computeMainBadge(p) { - if (p.followers > 500) return '🌟 Popular Dev'; - if (p.public_repos > 50) return '🧠 Open Source Wizard'; - if (p.public_repos < 5) return '🆕 Newbie'; - return '💻 Dev'; -} - -/* RENDER REPOS */ -function renderRepos(repos) { - const list = el('reposList'); list.innerHTML = ''; - repos.slice(0, 20).forEach(r => { - const div = document.createElement('div'); div.className = 'repo'; - div.innerHTML = `
                  ${r.name}
                  ${r.description || ''}
                  -
                  ⭐ ${r.stargazers_count}
                  Open
                  `; - list.appendChild(div); + if (tone === "good") { + li.classList.add("chip-good"); + } else if (tone === "warn") { + li.classList.add("chip-warn"); + } else if (tone === "risk") { + li.classList.add("chip-risk"); + } + + container.appendChild(li); + }); +} + +function renderRepoRanking(rankedRepos) { + clearChildren(ui.repoRankingTable); + + if (!rankedRepos.length) { + const row = document.createElement("tr"); + const cell = document.createElement("td"); + cell.colSpan = 5; + cell.textContent = "No repositories available for ranking."; + cell.className = "empty-state"; + row.appendChild(cell); + ui.repoRankingTable.appendChild(row); + return; + } + + rankedRepos.slice(0, 15).forEach((repo) => { + const row = document.createElement("tr"); + + const repoCell = document.createElement("td"); + const link = document.createElement("a"); + link.href = repo.url; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + link.textContent = repo.name; + link.className = "repo-link"; + + const langMeta = document.createElement("div"); + langMeta.className = "repo-item-meta"; + langMeta.textContent = repo.language; + + repoCell.appendChild(link); + repoCell.appendChild(langMeta); + + const importanceCell = document.createElement("td"); + const wrap = document.createElement("div"); + wrap.className = "importance-wrap"; + + const meter = document.createElement("div"); + meter.className = "importance-meter"; + const fill = document.createElement("span"); + fill.style.width = `${repo.importance}%`; + meter.appendChild(fill); + + const importanceText = document.createElement("span"); + importanceText.textContent = String(repo.importance); + + wrap.appendChild(meter); + wrap.appendChild(importanceText); + importanceCell.appendChild(wrap); + + const statsCell = document.createElement("td"); + statsCell.textContent = `${repo.stars} / ${repo.forks} / ${repo.watchers}`; + + const pushedCell = document.createElement("td"); + pushedCell.textContent = repo.pushedAt ? formatDate(repo.pushedAt) : "Unknown"; + + const readmeCell = document.createElement("td"); + const readmeChip = document.createElement("span"); + + if (repo.readmeKnown && repo.hasReadme) { + readmeChip.textContent = "Yes"; + readmeChip.className = "chip chip-good"; + } else if (repo.readmeKnown && !repo.hasReadme) { + readmeChip.textContent = "No"; + readmeChip.className = "chip chip-risk"; + } else { + readmeChip.textContent = "Unknown"; + readmeChip.className = "chip chip-neutral"; + } + + readmeCell.appendChild(readmeChip); + + row.appendChild(repoCell); + row.appendChild(importanceCell); + row.appendChild(statsCell); + row.appendChild(pushedCell); + row.appendChild(readmeCell); + + ui.repoRankingTable.appendChild(row); }); - // openRepos button - el('openReposBtn').onclick = () => { - if (!lastProfile) return; - const html = `

                  Top repos for ${lastProfile.login}

                  ${lastProfile._repos.slice(0, 20).map(r => - `
                  ${r.name} — ⭐ ${r.stargazers_count}
                  ${r.description||''}
                  ` - ).join('')}`; - openModal(html); +} + +function renderCharts(result) { + if ( + typeof Chart === "undefined" || + !ui.languageChart || + !ui.importanceChart || + !ui.subscoreRadarChart || + !ui.activityChart + ) { + return; + } + + const chartColors = [ + getCssVar("--chart-1"), + getCssVar("--chart-2"), + getCssVar("--chart-3"), + getCssVar("--chart-4"), + getCssVar("--chart-5"), + getCssVar("--chart-6") + ]; + + const axisColor = getCssVar("--muted"); + const borderColor = getCssVar("--border"); + + const languageEntries = Object.entries(result.languageTotals) + .sort((a, b) => b[1] - a[1]) + .slice(0, 8); + + const languageLabels = languageEntries.map(([label]) => label); + const languageValues = languageEntries.map(([, value]) => value); + + const hasLanguageData = languageLabels.length > 0; + + const languageData = { + labels: hasLanguageData ? languageLabels : ["No data"], + datasets: [{ + data: hasLanguageData ? languageValues : [1], + backgroundColor: hasLanguageData ? chartColors : [borderColor], + borderWidth: 1, + borderColor + }] }; + + if (state.charts.language) { + state.charts.language.destroy(); + } + + state.charts.language = new Chart(ui.languageChart, { + type: "doughnut", + data: languageData, + options: { + maintainAspectRatio: false, + plugins: { + legend: { + labels: { color: axisColor } + } + } + } + }); + + const importanceRepos = result.rankedRepos.slice(0, 10); + + if (state.charts.importance) { + state.charts.importance.destroy(); + } + + state.charts.importance = new Chart(ui.importanceChart, { + type: "bar", + data: { + labels: importanceRepos.map((repo) => repo.name), + datasets: [{ + label: "Importance", + data: importanceRepos.map((repo) => repo.importance), + backgroundColor: chartColors[0], + borderColor: chartColors[1], + borderWidth: 1 + }] + }, + options: { + maintainAspectRatio: false, + scales: { + x: { + ticks: { color: axisColor }, + grid: { color: borderColor } + }, + y: { + beginAtZero: true, + max: 100, + ticks: { color: axisColor }, + grid: { color: borderColor } + } + }, + plugins: { + legend: { + labels: { color: axisColor } + } + } + } + }); + + const radarLabels = Object.keys(result.subscores).map((key) => getNamedSubscore(key)); + const radarValues = Object.values(result.subscores); + + if (state.charts.subscoreRadar) { + state.charts.subscoreRadar.destroy(); + } + + state.charts.subscoreRadar = new Chart(ui.subscoreRadarChart, { + type: "radar", + data: { + labels: radarLabels, + datasets: [{ + label: "Portfolio Dimensions", + data: radarValues, + backgroundColor: `${chartColors[0]}40`, + borderColor: chartColors[0], + borderWidth: 2, + pointBackgroundColor: chartColors[1] + }] + }, + options: { + maintainAspectRatio: false, + scales: { + r: { + min: 0, + max: 100, + ticks: { color: axisColor, backdropColor: "transparent" }, + angleLines: { color: borderColor }, + grid: { color: borderColor }, + pointLabels: { color: axisColor } + } + }, + plugins: { + legend: { + labels: { color: axisColor } + } + } + } + }); + + const bucketLabels = ["0-30d", "31-90d", "91-180d", "181d+"]; + const bucketValues = [ + result.metrics.activityBuckets.updated30d, + result.metrics.activityBuckets.updated31to90d, + result.metrics.activityBuckets.updated91to180d, + result.metrics.activityBuckets.updated181plus + ]; + + if (state.charts.activity) { + state.charts.activity.destroy(); + } + + state.charts.activity = new Chart(ui.activityChart, { + type: "bar", + data: { + labels: bucketLabels, + datasets: [{ + label: "Repo Count", + data: bucketValues, + backgroundColor: [chartColors[1], chartColors[2], chartColors[3], chartColors[4]], + borderWidth: 1, + borderColor + }] + }, + options: { + maintainAspectRatio: false, + scales: { + x: { + ticks: { color: axisColor }, + grid: { color: borderColor } + }, + y: { + beginAtZero: true, + ticks: { color: axisColor, precision: 0 }, + grid: { color: borderColor } + } + }, + plugins: { + legend: { + labels: { color: axisColor } + } + } + } + }); } -/* HISTORY & LEADERBOARD */ -function saveHistory(username) { - history = [username].concat(history.filter(u => u !== username)).slice(0, 8); - localStorage.setItem('devdetective_history', JSON.stringify(history)); - renderHistory(); +function downloadMarkdownReport() { + if (!state.analysisResult) { + showError("Run an analysis before downloading a report."); + return; + } + + const markdown = buildMarkdownReport(state.analysisResult); + const blob = new Blob([markdown], { type: "text/markdown;charset=utf-8" }); + const url = URL.createObjectURL(blob); + + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = `${state.analysisResult.profile.login}-portfolio-report.md`; + anchor.click(); + + URL.revokeObjectURL(url); +} + +function buildMarkdownReport(result) { + const lines = []; + + lines.push("# GitHub Portfolio Analysis Report"); + lines.push(""); + lines.push(`- Generated: ${new Date(result.generatedAt).toLocaleString()}`); + lines.push(`- Profile: [${result.profile.name} (@${result.profile.login})](${result.profile.htmlUrl})`); + lines.push(`- Overall Score: **${result.overallScore}/100 (${result.grade})**`); + lines.push(`- Hireability Score: **${result.hireabilityScore}/100**`); + lines.push(`- Portfolio Readiness: **${result.readiness.label}**`); + lines.push(""); + lines.push("## Score Summary"); + lines.push(result.scoreSummary); + lines.push(""); + + lines.push("## Subscores"); + lines.push("| Category | Score | Weight |"); + lines.push("| --- | ---: | ---: |"); + + Object.entries(result.subscores).forEach(([key, score]) => { + lines.push(`| ${escapeMarkdown(getNamedSubscore(key))} | ${score} | ${result.weights[key]}% |`); + }); + + lines.push(""); + lines.push("## Scoring Inputs (Transparency)"); + lines.push(`- Documentation Quality Inputs: README coverage ${formatPercent(result.metrics.readmeCoverage)}, description coverage ${formatPercent(result.metrics.descriptionCoverage)}`); + lines.push(`- Activity Inputs: active months ${result.metrics.activeMonthsLast6}/6, repos updated in 90 days ${formatPercent(result.metrics.reposUpdated90dRatio)}`); + lines.push(`- Popularity Inputs: stars/repo ${result.metrics.starsPerRepo.toFixed(2)}, forks/repo ${result.metrics.forksPerRepo.toFixed(2)}, watchers/repo ${result.metrics.watchersPerRepo.toFixed(2)}`); + lines.push(`- Completeness Inputs: non-empty ${formatPercent(result.metrics.nonEmptyRepoRatio)}, homepage ${formatPercent(result.metrics.homepageRatio)}, topics ${formatPercent(result.metrics.topicsRatio)}`); + lines.push(`- Diversity Inputs: unique languages ${result.metrics.uniqueLanguages}, entropy ${result.metrics.normalizedShannonEntropy.toFixed(2)}`); + lines.push(`- Recency Inputs: days since last push ${result.metrics.daysSinceLastPush}, repos updated in 30 days ${formatPercent(result.metrics.reposUpdated30dRatio)}`); + lines.push(`- Impact Inputs: top repo stars ${result.metrics.topRepoStars}, followers ${result.profile.followers}, PRs ${result.metrics.authoredPRCount}, issues ${result.metrics.authoredIssueCount}`); + lines.push(""); + lines.push("## Strengths"); + result.strengths.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Red Flags"); + result.redFlags.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Actionable Suggestions"); + result.suggestions.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## AI Recruiter Simulation"); + lines.push(`- Verdict: **${escapeMarkdown(result.recruiterSimulation.verdict)}**`); + lines.push(`- Summary: ${escapeMarkdown(result.recruiterSimulation.summary)}`); + lines.push("- Signals:"); + result.recruiterSimulation.signals.forEach((item) => lines.push(` - ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Hidden Risks"); + result.hiddenRisks.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Career Path Recommendation"); + lines.push(`- Suggested Path: **${escapeMarkdown(result.careerPath.title)}**`); + lines.push(`- Confidence: ${result.careerPath.confidence}%`); + lines.push(`- Rationale: ${escapeMarkdown(result.careerPath.summary)}`); + lines.push("- Next Skill Targets:"); + result.careerPath.nextSkills.forEach((item) => lines.push(` - ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Personalized Improvement Roadmap"); + result.improvementRoadmap.forEach((item, index) => lines.push(`${index + 1}. ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Top Ranked Repositories"); + lines.push("| Repo | Importance | Stars | Forks | Watchers | Last Push | README |"); + lines.push("| --- | ---: | ---: | ---: | ---: | --- | --- |"); + + result.rankedRepos.slice(0, 10).forEach((repo) => { + lines.push( + `| [${escapeMarkdown(repo.name)}](${repo.url}) | ${repo.importance} | ${repo.stars} | ${repo.forks} | ${repo.watchers} | ${formatDate(repo.pushedAt)} | ${repo.readmeKnown ? (repo.hasReadme ? "Yes" : "No") : "Unknown"} |` + ); + }); + + lines.push(""); + lines.push("## Pinned Repositories"); + lines.push(`- Source: ${result.pinnedRepos.source}`); + + result.pinnedRepos.items.forEach((repo) => { + lines.push(`- [${escapeMarkdown(repo.name)}](${repo.url}) - ${repo.stars} stars`); + }); + + lines.push(""); + lines.push("## Key Metrics"); + lines.push(`- Scorable repositories: ${result.metrics.scorableRepoCount}`); + lines.push(`- Total stars/forks/watchers: ${result.metrics.totalStars}/${result.metrics.totalForks}/${result.metrics.totalWatchers}`); + lines.push(`- README coverage (sampled): ${(result.metrics.readmeCoverage * 100).toFixed(1)}%`); + lines.push(`- Repositories updated in 30d/90d: ${result.metrics.reposUpdated30d}/${result.metrics.reposUpdated90d}`); + lines.push(`- Days since last push: ${result.metrics.daysSinceLastPush}`); + lines.push(`- Authored PRs/issues: ${result.metrics.authoredPRCount}/${result.metrics.authoredIssueCount}`); + lines.push(`- Unique languages: ${result.metrics.uniqueLanguages}`); + lines.push(`- Dominant language share: ${formatPercent(result.metrics.dominantLanguageShare)} (${result.metrics.dominantLanguage})`); + lines.push(`- Activity buckets (0-30/31-90/91-180/181+ days): ${result.metrics.activityBuckets.updated30d}/${result.metrics.activityBuckets.updated31to90d}/${result.metrics.activityBuckets.updated91to180d}/${result.metrics.activityBuckets.updated181plus}`); + + return lines.join("\n"); } + function renderHistory() { - const h = el('history'); h.innerHTML = ''; - history.forEach(u => { const b = document.createElement('div'); b.className = 'pill'; b.innerText = u; b.onclick = () => startSearch(u); h.appendChild(b); }); -} - -function generateLeaderboardEntry(p) { - leaderboard = leaderboard.filter(x => x.login !== p.login); - leaderboard.unshift({ login: p.login, followers: p.followers, repos: p.public_repos, avatar: p.avatar_url }); - leaderboard = leaderboard.slice(0, 10); - localStorage.setItem('devdetective_lb', JSON.stringify(leaderboard)); - renderLeaderboard(); -} -function renderLeaderboard() { - const box = el('leaderboard'); box.innerHTML = ''; - leaderboard.forEach((u, idx) => { - const d = document.createElement('div'); d.className = 'row'; - d.style.alignItems = 'center'; d.style.justifyContent = 'space-between'; - d.style.padding = '6px 0'; - d.innerHTML = `
                  -
                  ${u.login}
                  ${u.followers} followers
                  #${idx+1}
                  `; - box.appendChild(d); + clearChildren(ui.historyList); + + if (!state.history.length) { + const empty = document.createElement("p"); + empty.className = "empty-state"; + empty.textContent = "No recent searches yet."; + ui.historyList.appendChild(empty); + return; + } + + state.history.forEach((username) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = "history-chip"; + button.textContent = username; + button.addEventListener("click", () => { + ui.profileInput.value = username; + handleAnalyze(); + }); + + ui.historyList.appendChild(button); }); } -/* ANALYTICS & CHART */ -function renderAnalytics(p) { - const langs = p._langCount; - const labels = Object.keys(langs); - const data = Object.values(langs); - if (!langChart) { - const ctx = document.getElementById('langChart').getContext('2d'); - langChart = new Chart(ctx, { type: 'doughnut', data: { labels, datasets: [{ data }] }, options: { plugins: { legend: { position: 'bottom' } } } }); +function saveHistory(username) { + const clean = (username || "").trim().toLowerCase(); + if (!clean) { + return; + } + + state.history = [clean, ...state.history.filter((item) => item !== clean)].slice(0, HISTORY_LIMIT); + localStorage.setItem(STORAGE_KEYS.history, JSON.stringify(state.history)); +} + +function setLoading(isLoading, message = "") { + ui.analyzeBtn.disabled = isLoading; + + if (isLoading) { + ui.loadingState.classList.remove("hidden"); + ui.loadingText.textContent = message || "Analyzing profile..."; } else { - langChart.data.labels = labels; langChart.data.datasets[0].data = data; langChart.update(); - } - const avg = p._repos.length ? Math.round(p._repos.reduce((s, r) => s + r.stargazers_count, 0) / p._repos.length) : 0; - el('avgStars').innerText = avg; - const top = p._repos.slice().sort((a, b) => b.stargazers_count - a.stargazers_count)[0]; - el('topRepo').innerText = top ? top.name : '—'; - const score = Math.round((p.followers * 0.3 + p.public_repos * 2 + (avg) * 1.5)); - el('devScore').innerText = score; - const now = Date.now(); const recent = p._repos.filter(r => (now - new Date(r.updated_at)) / 86400000 < 14).length; - el('streak').innerText = recent + ' updates (14d)'; - el('insightSummary').innerText = generateSummary(p, avg); -} - -/* simple rule-based AI summary */ -async function generateSummary(p, avgStars) { - const prompt = `Summarize this GitHub profile: ${p.name || p.login}, main language ${Object.keys(p._langCount)[0]}. - ${p.followers} followers, ${p.public_repos} repos, average stars ${avgStars}. Write in 1 sentence.`; + ui.loadingState.classList.add("hidden"); + ui.loadingText.textContent = "Analyzing profile..."; + } +} + +function clearBanners() { + ui.errorBanner.classList.add("hidden"); + ui.errorBanner.textContent = ""; + + ui.rateLimitBanner.classList.add("hidden"); + ui.rateLimitBanner.textContent = ""; +} + +function showError(message) { + ui.errorBanner.textContent = message; + ui.errorBanner.classList.remove("hidden"); +} + +function showRateLimit(resetAt) { + const when = resetAt ? new Date(resetAt).toLocaleString() : "the API reset window"; + ui.rateLimitBanner.textContent = `Rate limited by GitHub API. Retry after ${when}.`; + ui.rateLimitBanner.classList.remove("hidden"); +} + +function applyTheme(theme) { + const normalized = theme === "dark" ? "dark" : "light"; + document.documentElement.setAttribute("data-theme", normalized); + localStorage.setItem(STORAGE_KEYS.theme, normalized); + ui.themeToggleBtn.textContent = normalized === "dark" ? "Switch to Light" : "Switch to Dark"; +} + +function getCachedAnalysis(username, tokenMode) { + const key = `${CACHE_PREFIX}${username.toLowerCase()}`; + const payload = readJsonStorage(key, null); + + if (!payload || typeof payload !== "object") { + return null; + } + + const isFresh = Number(payload.savedAt) && Date.now() - Number(payload.savedAt) <= CACHE_TTL_MS; + const sameMode = Boolean(payload.tokenMode) === Boolean(tokenMode); + + const hasExpectedShape = + payload.analysis && + typeof payload.analysis === "object" && + typeof payload.analysis.overallScore === "number" && + typeof payload.analysis.hireabilityScore === "number" && + payload.analysis.readiness && + payload.analysis.recruiterSimulation && + Array.isArray(payload.analysis.improvementRoadmap); + + if (!isFresh || !sameMode || !hasExpectedShape) { + localStorage.removeItem(key); + return null; + } + + return payload.analysis; +} + +function setCachedAnalysis(username, tokenMode, analysis) { + const key = `${CACHE_PREFIX}${username.toLowerCase()}`; + localStorage.setItem( + key, + JSON.stringify({ + savedAt: Date.now(), + tokenMode: Boolean(tokenMode), + analysis + }) + ); +} + +async function githubRequest(path, options = {}) { + const { + token = "", + method = "GET", + signal, + retry = 1, + body, + accept = "application/vnd.github+json" + } = options; + + const url = path.startsWith("http") ? path : `${GITHUB_API_ROOT}${path}`; + + let attempt = 0; + while (attempt <= retry) { + attempt += 1; + + try { + const response = await fetch(url, { + method, + signal, + headers: { + Accept: accept, + "X-GitHub-Api-Version": "2022-11-28", + ...(token ? { Authorization: `Bearer ${token}` } : {}) + }, + ...(body ? { body: JSON.stringify(body) } : {}) + }); + + const rateInfo = extractRateInfo(response.headers); + const contentType = response.headers.get("content-type") || ""; + + if (!response.ok) { + let errorPayload = null; + try { + errorPayload = contentType.includes("application/json") + ? await response.json() + : { message: await response.text() }; + } catch { + errorPayload = { message: `GitHub API error (${response.status})` }; + } + + const message = + (errorPayload && typeof errorPayload.message === "string" && errorPayload.message.trim()) || + `GitHub API error (${response.status})`; + + if (response.status === 404) { + throw new GitHubError("NotFound", message, { status: response.status, rateInfo }); + } + + if (response.status === 401) { + throw new GitHubError("Unauthorized", message, { status: response.status, rateInfo }); + } + + const rateLimited = + response.status === 429 || + (response.status === 403 && (rateInfo.remaining === 0 || /rate limit/i.test(message))); + + if (rateLimited) { + throw new GitHubError("RateLimited", message, { + status: response.status, + resetAt: rateInfo.resetAt, + rateInfo + }); + } + + throw new GitHubError("Api", message, { status: response.status, rateInfo }); + } + + if (response.status === 204) { + return { data: null, headers: response.headers, rateInfo }; + } + + let data; + if (contentType.includes("application/json")) { + data = await response.json(); + } else { + data = await response.text(); + } + + return { data, headers: response.headers, rateInfo }; + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + + if (error instanceof GitHubError) { + throw error; + } + + if (attempt > retry) { + throw new GitHubError("Network", "Failed to reach GitHub API.", { cause: error }); + } + } + } + + throw new GitHubError("Network", "Failed to reach GitHub API."); +} + +async function githubGraphQL(query, variables, token, signal) { + if (!token) { + throw new GitHubError("Unauthorized", "GitHub token is required for GraphQL requests."); + } + + let response; try { - const r = await fetch("https://api.openai.com/v1/chat/completions", { + response = await fetch(GITHUB_GRAPHQL_ENDPOINT, { method: "POST", + signal, headers: { + Accept: "application/vnd.github+json", "Content-Type": "application/json", - "Authorization": "Bearer YOUR_OPENAI_API_KEY" + Authorization: `Bearer ${token}`, + "X-GitHub-Api-Version": "2022-11-28" }, - body: JSON.stringify({ - model: "gpt-4o-mini", - messages: [{ role: "user", content: prompt }], - max_tokens: 40 - }) - }).then(r => r.json()); - return r.choices?.[0]?.message?.content || "AI summary unavailable."; - } catch (e) { console.warn(e); return "AI summary unavailable."; } -} - -/* MAP (Nominatim geocoding + Leaflet) */ -function renderMap(p) { - try { - if (!map) { map = L.map('map', { attributionControl: false }).setView([20, 0], 2); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); } - const loc = p.location; if (!loc) { map.setView([20, 0], 2); return; } - fetch('https://nominatim.openstreetmap.org/search?format=json&q=' + encodeURIComponent(loc)).then(r => r.json()).then(data => { - if (data && data[0]) { - const lat = data[0].lat, lon = data[0].lon; - map.setView([lat, lon], 5); - L.marker([lat, lon]).addTo(map).bindPopup(p.login + ' — ' + (p.location || '')).openPopup(); - } - }).catch(e => console.warn(e)); - } catch (e) { console.warn(e); } -} - -/* TIMELINE (recent repo updates) */ -function renderTimeline(p) { - const t = el('timeline'); t.innerHTML = ''; - const items = p._repos.slice(0, 8).map(r => ({ title: r.name, when: new Date(r.updated_at).toLocaleString(), desc: r.description || '' })); - items.forEach(it => { - const d = document.createElement('div'); d.className = 'small muted'; d.style.padding = '8px 0'; - d.innerHTML = `${it.title}${it.when}
                  ${it.desc}
                  `; - t.appendChild(d); - }); + body: JSON.stringify({ query, variables }) + }); + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + throw new GitHubError("Network", "Failed to reach GitHub GraphQL API.", { cause: error }); + } + + const rateInfo = extractRateInfo(response.headers); + const payload = await response.json(); + + if (!response.ok) { + if (response.status === 401) { + throw new GitHubError("Unauthorized", "GitHub token is invalid for GraphQL API.", { + status: response.status, + rateInfo + }); + } + + const message = (payload && payload.message) || `GitHub GraphQL error (${response.status})`; + throw new GitHubError("Api", message, { status: response.status, rateInfo }); + } + + if (payload.errors && payload.errors.length) { + throw new GitHubError("Api", payload.errors[0].message || "GraphQL query failed.", { + errors: payload.errors, + rateInfo + }); + } + + return payload.data; } -/* MODAL helpers */ -function openModal(html) { modal.classList.add('show'); modalPanel.innerHTML = html; } -function closeModal() { modal.classList.remove('show'); } +function extractRateInfo(headers) { + const remaining = Number(headers.get("x-ratelimit-remaining")); + const resetEpoch = Number(headers.get("x-ratelimit-reset")); -/* PINS */ -el('pinBtn').addEventListener('click', () => { - if (!lastProfile) return; - if (!pinned.includes(lastProfile.login)) pinned.push(lastProfile.login); - localStorage.setItem('devdetective_pinned', JSON.stringify(pinned)); - alert('Pinned ' + lastProfile.login); -}); + return { + remaining: Number.isFinite(remaining) ? remaining : null, + resetAt: Number.isFinite(resetEpoch) && resetEpoch > 0 ? new Date(resetEpoch * 1000).toISOString() : null + }; +} -/* DOWNLOAD JSON */ -el('downloadJsonBtn').addEventListener('click', () => { - if (!lastProfile) return; - const data = JSON.stringify(lastProfile, null, 2); const url = URL.createObjectURL(new Blob([data], { type: 'application/json' })); - const a = document.createElement('a'); a.href = url; a.download = lastProfile.login + '.json'; a.click(); URL.revokeObjectURL(url); -}); +async function mapWithConcurrency(items, concurrency, worker) { + if (!items.length) { + return []; + } -/* EXPORT DEV CARD (canvas to PNG) */ -el('exportCardBtn').addEventListener('click', () => { - if (!lastProfile) return; - const c = document.createElement('canvas'); c.width = 1000; c.height = 520; const ctx = c.getContext('2d'); - ctx.fillStyle = '#0f1724'; ctx.fillRect(0, 0, c.width, c.height); - ctx.fillStyle = '#fff'; ctx.font = 'bold 36px monospace'; ctx.fillText(lastProfile.name || lastProfile.login, 160, 80); - const img = new Image(); img.crossOrigin = 'anonymous'; - img.onload = function () { - ctx.drawImage(img, 40, 40, 100, 100); - ctx.font = '16px monospace'; ctx.fillText('@' + lastProfile.login, 160, 120); ctx.font = '14px monospace'; - wrapText(ctx, lastProfile.bio || '', 160, 160, 760, 20); - const a = document.createElement('a'); a.href = c.toDataURL('image/png'); a.download = lastProfile.login + '_card.png'; a.click(); + const results = new Array(items.length); + let currentIndex = 0; + + const runner = async () => { + while (currentIndex < items.length) { + const index = currentIndex; + currentIndex += 1; + results[index] = await worker(items[index], index); + } }; - img.src = lastProfile.avatar_url; -}); -/* small text wrap helper */ -function wrapText(ctx, text, x, y, maxWidth, lineHeight) { - const words = text.split(' '); - let line = ''; - for (let n = 0; n < words.length; n++) { - const testLine = line + words[n] + ' '; - const metrics = ctx.measureText(testLine); - const testWidth = metrics.width; - if (testWidth > maxWidth && n > 0) { ctx.fillText(line, x, y); line = words[n] + ' '; y += lineHeight; } else { line = testLine; } - } - ctx.fillText(line, x, y); -} - -/* RESUME (open printable HTML) */ -el('resumeBtn').addEventListener('click', () => { - if (!lastProfile) return; - const win = window.open('', '_blank'); - const html = `Resume - ${lastProfile.name || lastProfile.login} - -

                  ${lastProfile.name || lastProfile.login}

                  ${lastProfile.bio || ''}

                  Followers: ${lastProfile.followers} | Repos: ${lastProfile.public_repos}

                  -

                  Top repos

                    ${lastProfile._repos.slice(0, 6).map(r => '
                  • ' + r.name + ' — ⭐' + r.stargazers_count + '
                  • ').join('')}
                  `; - win.document.write(html); win.document.close(); win.print(); -}); + const workers = []; + const count = Math.min(concurrency, items.length); + for (let i = 0; i < count; i += 1) { + workers.push(runner()); + } -/* BADGES */ -el('badgeBtn').addEventListener('click', () => { - if (!lastProfile) return; - const badges = computeBadges(lastProfile); alert('Badges: ' + (badges.length ? badges.join(', ') : 'None')); -}); -function computeBadges(p) { - const out = []; - if (p.public_repos > 30) out.push('Open Source Wizard'); - if (p.followers > 200) out.push('Popular'); - if (p.followers > 50 && p.public_repos > 10) out.push('Influencer'); - if ((p._repos || []).some(r => r.stargazers_count > 50)) out.push('Starred Repo'); - if (Object.keys(p._langCount).length > 3) out.push('Polyglot'); - return out; -} - -/* COMMENTS (localStorage) */ -let comments = JSON.parse(localStorage.getItem('devdetective_comments') || '{}'); -el('commentToggle').addEventListener('click', () => { - if (!lastProfile) return; const k = lastProfile.login; const c = comments[k] || []; - openModal(`

                  Comments for ${k}

                  ${c.map(x => `
                  ${x}
                  `).join('')}
                  -
                  `); - document.getElementById('addCmt').onclick = () => { const t = document.getElementById('cmtText').value.trim(); if (!t) return; comments[k] = comments[k] || []; comments[k].unshift(t); localStorage.setItem('devdetective_comments', JSON.stringify(comments)); modal.classList.remove('show'); alert('Comment added'); }; - document.getElementById('closeC').onclick = () => modal.classList.remove('show'); -}); + await Promise.all(workers); + return results; +} + +function preRankImportanceScore(repo) { + const days = daysSinceDate(repo.pushed_at); + const freshness = days <= 30 ? 10 : days <= 90 ? 6 : days <= 180 ? 3 : 0; + + return ( + (Number(repo.stargazers_count) || 0) * 3 + + (Number(repo.forks_count) || 0) * 2 + + (Number(repo.watchers_count) || 0) + + freshness + ); +} + +function computeActivityInLastSixMonths(repos) { + const now = new Date(); + const monthKeys = []; + + for (let i = 0; i < 6; i += 1) { + const date = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - i, 1)); + monthKeys.push(`${date.getUTCFullYear()}-${date.getUTCMonth() + 1}`); + } + + const activeSet = new Set(); + + repos.forEach((repo) => { + if (!repo.pushed_at) { + return; + } + + const pushed = new Date(repo.pushed_at); + const key = `${pushed.getUTCFullYear()}-${pushed.getUTCMonth() + 1}`; + if (monthKeys.includes(key)) { + activeSet.add(key); + } + }); -/* COMPARE */ -el('compareOpenBtn').addEventListener('click', () => { - openModal(`

                  Compare Developers

                  -
                  `); - document.getElementById('doCompare').onclick = async () => { - const u1 = document.getElementById('c1').value.trim(), u2 = document.getElementById('c2').value.trim(); - if (!u1 || !u2) return; - const a = await fetch(API_ROOT + u1).then(r => r.json()); const b = await fetch(API_ROOT + u2).then(r => r.json()); - const html = `

                  ${a.login}

                  Followers: ${a.followers}
                  Repos: ${a.public_repos}
                  -

                  ${b.login}

                  Followers: ${b.followers}
                  Repos: ${b.public_repos}
                  -
                  Winner: ${ (a.followers + a.public_repos * 2) > (b.followers + b.public_repos * 2) ? a.login : b.login }
                  `; - document.getElementById('cmpRes').innerHTML = html; + const activeMonths = activeSet.size; + return { + activeMonths, + ratio: activeMonths / 6 }; -}); +} -/* THEME APPLY (mood) */ -el('themeApplyBtn').addEventListener('click', () => { if (!lastProfile) return; const mainLang = Object.keys(lastProfile._langCount)[0] || 'Unknown'; applyMoodTheme(mainLang); }); -function applyMoodTheme(lang) { - if (['JavaScript', 'TypeScript'].includes(lang)) { document.documentElement.style.setProperty('--accent', '#f7df1e'); document.body.style.background = 'linear-gradient(180deg,#fff7d9,#fefae0)'; } - else if (['Python'].includes(lang)) { document.documentElement.style.setProperty('--accent', '#306998'); document.body.style.background = 'linear-gradient(180deg,#eaf6ff,#e9f2ff)'; } - else { document.documentElement.style.setProperty('--accent', '#0079ff'); document.body.style.background = 'linear-gradient(180deg,#eaf0ff,#f6f8ff)'; } -} -function openModal(html) { modal.classList.add('show'); modalPanel.innerHTML = html; } -startSearch('sudheerrrrit'); -window.addEventListener("load", () => { - setTimeout(() => { - document.getElementById("loader").classList.add("hide"); - }, 2500); -}); +function getRecencyBucket(daysSinceLastPush) { + if (daysSinceLastPush <= 7) { + return 1; + } + if (daysSinceLastPush <= 30) { + return 0.8; + } + if (daysSinceLastPush <= 90) { + return 0.6; + } + if (daysSinceLastPush <= 180) { + return 0.3; + } + return 0.1; +} + +function getSeverity(score) { + if (score >= 70) { + return "good"; + } + if (score >= 40) { + return "warn"; + } + return "risk"; +} + +function severityToChipClass(severity) { + if (severity === "good") { + return "chip-good"; + } + if (severity === "warn") { + return "chip-warn"; + } + return "chip-risk"; +} + +function severityColor(severity) { + if (severity === "good") { + return getCssVar("--good"); + } + if (severity === "warn") { + return getCssVar("--warn"); + } + return getCssVar("--risk"); +} + +function scoreToGrade(score) { + if (score >= 90) { + return "A+"; + } + if (score >= 80) { + return "A"; + } + if (score >= 70) { + return "B"; + } + if (score >= 60) { + return "C"; + } + if (score >= 45) { + return "D"; + } + return "E"; +} + +function getNamedSubscore(key) { + const labels = { + documentationQuality: "Documentation Quality", + codeActivityConsistency: "Code Activity / Consistency", + projectPopularity: "Project Popularity", + repositoryCompleteness: "Repository Completeness", + languageDiversity: "Language Diversity", + recentActivity: "Recent Activity", + impactSignals: "Impact Signals" + }; + + return labels[key] || key; +} + +function safeRatio(numerator, denominator) { + if (!denominator) { + return 0; + } + return numerator / denominator; +} + +function scoreFromRatio(ratio) { + return clampToRange(Math.round(cap01(ratio) * 100), 0, 100); +} + +function cap01(value) { + if (!Number.isFinite(value)) { + return 0; + } + return Math.max(0, Math.min(1, value)); +} + +function clampToRange(value, min, max) { + return Math.min(max, Math.max(min, value)); +} + +function formatDate(value) { + if (!value) { + return "Unknown"; + } + const date = new Date(value); + if (Number.isNaN(date.getTime())) { + return "Unknown"; + } + + return date.toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric" + }); +} + +function daysSinceDate(value, nowMs = Date.now()) { + if (!value) { + return Number.POSITIVE_INFINITY; + } + + const date = new Date(value); + if (Number.isNaN(date.getTime())) { + return Number.POSITIVE_INFINITY; + } + + return Math.floor((nowMs - date.getTime()) / 86400000); +} + +function formatCompactNumber(value) { + const num = Number(value) || 0; + return new Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 1 }).format(num); +} + +function formatPercent(value) { + return `${Math.round(cap01(value) * 100)}%`; +} + +function joinRepoNames(names) { + if (!names.length) { + return "target repositories"; + } + return names.map((name) => `\`${name}\``).join(", "); +} + +function dedupe(items) { + return items.filter((item, index) => items.indexOf(item) === index); +} + +function readJsonStorage(key, fallback) { + try { + const raw = localStorage.getItem(key); + if (!raw) { + return fallback; + } + return JSON.parse(raw); + } catch { + return fallback; + } +} + +function clearChildren(node) { + while (node.firstChild) { + node.removeChild(node.firstChild); + } +} + +function appendEmptyState(container, text) { + const li = document.createElement("li"); + li.className = "empty-state"; + li.textContent = text; + container.appendChild(li); +} + +function getCssVar(name) { + return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); +} + +function escapeMarkdown(text) { + return String(text).replace(/[|]/g, "\\|"); +} diff --git a/style.css b/style.css index 7fd4e46c0..4c3599fda 100644 --- a/style.css +++ b/style.css @@ -1,105 +1,841 @@ -:root{ - --bg:#f6f8ff; --card:#fff; --text:#243447; --muted:#6b7a91; --accent:#0079ff; - --glass: rgba(255,255,255,0.6); -} -*{box-sizing:border-box; font-family:'Space Mono', monospace} -body{margin:0; background:linear-gradient(180deg,#eaf0ff 0%,var(--bg)100%); color:var(--text); min-height:100vh} -.app{max-width:1100px; margin:20px auto; padding:18px} -header{display:flex; justify-content:space-between; align-items:center; gap:12px} -.titleBlock h1{margin:0; font-size:18px} -.subtitle{margin:0; font-size:12px; color:var(--muted)} -.controls{display:flex; gap:8px; align-items:center} -.controls input{padding:10px 12px; border-radius:8px; border:1px solid #d6e0ff; width:320px} -.controls button{background:var(--accent); color:#fff; border:none; padding:9px 12px; border-radius:8px; cursor:pointer} -.grid{display:grid; grid-template-columns:360px 1fr; gap:14px; margin-top:14px} -.card{background:var(--card); padding:14px; border-radius:12px; box-shadow:0 8px 30px rgba(20,30,60,0.06)} -.leftCard{display:flex; flex-direction:column; gap:12px} -.profileTop{display:flex; align-items:center; gap:12px} -.avatar{width:84px; height:84px; border-radius:12px; overflow:hidden; background:#eee; flex-shrink:0} -.avatar img{width:100%; height:100%; object-fit:cover} -.profileMeta{flex:1} -.fullname{font-weight:700} -.username{color:var(--accent); text-decoration:none} -.badgeWrap{text-align:right} -.badge{background:linear-gradient(90deg,#f0f6ff,#eef9ff); padding:6px 8px; border-radius:999px; font-weight:700} - -.bio{line-height:1.35; color:var(--muted)} - -.statsRow{display:flex; gap:10px; margin-top:6px} -.stat{flex:1; background:#f6f9ff; padding:8px; border-radius:8px; text-align:center} -.small{font-size:12px} -.muted{color:var(--muted)} -.big{font-weight:700; font-size:18px} - -.actions{display:flex; gap:8px; flex-wrap:wrap} -.actions button{background:#fff; border:1px solid #e6eefc; padding:8px 10px; border-radius:8px; cursor:pointer} - -.section{margin-top:8px} -.sectionTitle{margin-bottom:8px} - -.langWrap{display:flex; gap:8px; flex-wrap:wrap} -.pill{background:#f4f8ff; padding:6px 8px; border-radius:999px; font-size:12px} - -.repos-list{max-height:220px; overflow:auto; margin-top:6px} -.repo{padding:8px; border-radius:8px; border:1px solid #f0f4ff; margin-bottom:8px; display:flex; justify-content:space-between} -.repo a{color:var(--accent); text-decoration:none} - -.history{display:flex; gap:6px; flex-wrap:wrap} -.history .pill{cursor:pointer} - -.rightColumn{display:flex; flex-direction:column; gap:12px} -.cardHeader{display:flex; justify-content:space-between; align-items:center} -.typing .dot{display:inline-block;width:6px;height:6px;background:var(--accent); border-radius:6px;margin-right:4px; animation:blink 1s infinite} -@keyframes blink{0%{opacity:0.25}50%{opacity:1}100%{opacity:0.25}} - -.analyticsGrid{display:grid; grid-template-columns:repeat(2,1fr); gap:10px; margin-top:12px} -.analytic{padding:12px; border-radius:10px} -.highlight{background:linear-gradient(90deg,#fff9eb,#fff);} - -.chartRow{display:flex; gap:12px; margin-top:12px} -.chartWrap{flex:1} -.mapWrap{width:260px} - -.map{height:200px; border-radius:8px; overflow:hidden} - -.cardFooter{display:flex; justify-content:space-between; align-items:center; margin-top:12px} -.footerButtons button{background:#fff; border:1px solid #e6eefc; padding:8px 10px; border-radius:8px; cursor:pointer} - -.timelineCard .timeline{max-height:200px; overflow:auto; margin-top:8px} -.leaderboard .row{display:flex; justify-content:space-between; padding:6px 0; align-items:center} - -.modal{position:fixed; inset:0; display:none; align-items:center; justify-content:center; background:rgba(0,0,0,0.45); z-index:1000} -.modal.show{display:flex} -.modalPanel{width:720px; max-width:95%; background:#fff; border-radius:12px; padding:16px; max-height:80vh; overflow:auto} - -/* responsive */ -@media (max-width:920px){ - .grid{grid-template-columns:1fr} - .controls input{width:160px} - .mapWrap{display:none} -} -/* ========== ANIMATIONS / MICRO-INTERACTIONS ========== */ -button:hover { transform: scale(1.04); transition: 0.2s ease; } -.card { transition: all 0.3s ease; } -.card:hover { transform: translateY(-4px); box-shadow: 0 12px 40px rgba(20,30,60,0.12); } -.avatar img { transition: transform 0.4s ease; } -.avatar img:hover { transform: scale(1.08) rotate(2deg); } -.timelineCard .timeline div { animation: fadein 0.5s ease; } -@keyframes fadein { from { opacity: 0; transform: translateY(5px);} to { opacity: 1; transform: translateY(0);} } - -/* Splash Loader */ -#loader { - position: fixed; - inset: 0; - background: linear-gradient(180deg,#eaf0ff,#f6f8ff); +:root { + --bg: #f3f7ff; + --bg-gradient-a: #f8fbff; + --bg-gradient-b: #e8f0ff; + --surface: #ffffff; + --surface-soft: #f5f8ff; + --text: #1e2a39; + --muted: #5f6f83; + --border: #dce6f8; + --accent: #1f6feb; + --accent-strong: #144fb6; + --good: #177245; + --warn: #a06000; + --risk: #a3213d; + --shadow: 0 20px 45px rgba(21, 38, 74, 0.1); + --chart-1: #1f6feb; + --chart-2: #0f9d58; + --chart-3: #f2994a; + --chart-4: #6f42c1; + --chart-5: #e83e8c; + --chart-6: #00838f; +} + +:root[data-theme="dark"] { + --bg: #0f1624; + --bg-gradient-a: #121b2c; + --bg-gradient-b: #0b111d; + --surface: #182235; + --surface-soft: #1e2b42; + --text: #ebf1ff; + --muted: #98aac4; + --border: #2a3a58; + --accent: #58a6ff; + --accent-strong: #3f8ce8; + --good: #4fd694; + --warn: #f0ad4e; + --risk: #ff7b91; + --shadow: 0 24px 50px rgba(0, 0, 0, 0.4); + --chart-1: #58a6ff; + --chart-2: #4fd694; + --chart-3: #f0ad4e; + --chart-4: #c297ff; + --chart-5: #ff9ccd; + --chart-6: #56cfd6; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + font-family: "Manrope", "Segoe UI", sans-serif; + color: var(--text); + background: + radial-gradient(circle at 5% 0%, rgba(31, 111, 235, 0.12), transparent 35%), + linear-gradient(180deg, var(--bg-gradient-a) 0%, var(--bg-gradient-b) 100%); +} + +h1, +h2, +h3, +h4, +button, +input, +code { + font-family: "Space Mono", monospace; +} + +a { + color: var(--accent); +} + +.muted { + color: var(--muted); +} + +.hidden { + display: none !important; +} + +.app-shell { + width: min(1200px, 100% - 2rem); + margin: 1.2rem auto 2.4rem; +} + +.topbar { + margin-bottom: 1rem; +} + +.brand { + display: flex; + gap: 1rem; + align-items: center; +} + +.brand-logo { + width: 56px; + height: 56px; + object-fit: contain; + border-radius: 12px; + background: var(--surface); + border: 1px solid var(--border); + box-shadow: var(--shadow); +} + +.brand h1 { + margin: 0; + font-size: clamp(1.2rem, 2vw, 1.6rem); +} + +.brand p { + margin: 0.25rem 0 0; + color: var(--muted); + font-size: 0.9rem; +} + +.card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 16px; + box-shadow: var(--shadow); + padding: 1rem; + animation: card-in 220ms ease-out; + transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease; +} + +.card:hover { + transform: translateY(-2px); + border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); + box-shadow: 0 28px 52px rgba(16, 33, 70, 0.14); +} + +@keyframes card-in { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.controls-card { + margin-bottom: 0.85rem; +} + +.controls-grid { + display: grid; + grid-template-columns: 1.2fr 1fr auto; + gap: 0.8rem; + align-items: end; +} + +.field { + display: flex; + flex-direction: column; + gap: 0.45rem; +} + +.field label { + font-size: 0.78rem; + color: var(--muted); +} + +input { + width: 100%; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface-soft); + color: var(--text); + padding: 0.62rem 0.72rem; + font-size: 0.88rem; +} + +input:focus { + border-color: var(--accent); + outline: none; + box-shadow: 0 0 0 3px rgba(31, 111, 235, 0.15); +} + +.hint { + margin: 0; + color: var(--muted); + font-size: 0.72rem; + line-height: 1.35; +} + +.button-group { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + justify-content: flex-end; +} + +.btn { + border: 1px solid var(--border); + background: var(--surface-soft); + color: var(--text); + border-radius: 10px; + font-size: 0.78rem; + padding: 0.62rem 0.82rem; + cursor: pointer; + transition: transform 140ms ease, border-color 140ms ease; +} + +.btn:hover:not(:disabled) { + transform: translateY(-1px); + border-color: var(--accent); +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-primary { + background: linear-gradient(120deg, var(--accent), var(--accent-strong)); + border-color: transparent; + color: #ffffff; +} + +.status-stack { + display: grid; + gap: 0.5rem; + margin-bottom: 0.9rem; +} + +.status { + border-radius: 12px; + padding: 0.65rem 0.8rem; + border: 1px solid; + display: flex; + align-items: center; + gap: 0.55rem; + font-size: 0.82rem; +} + +.status-loading { + color: var(--accent-strong); + background: color-mix(in srgb, var(--accent) 10%, transparent); + border-color: color-mix(in srgb, var(--accent) 28%, var(--border)); +} + +.status-error { + color: var(--risk); + background: color-mix(in srgb, var(--risk) 10%, transparent); + border-color: color-mix(in srgb, var(--risk) 32%, var(--border)); +} + +.status-warn { + color: var(--warn); + background: color-mix(in srgb, var(--warn) 10%, transparent); + border-color: color-mix(in srgb, var(--warn) 32%, var(--border)); +} + +.spinner { + width: 14px; + height: 14px; + border-radius: 50%; + border: 2px solid transparent; + border-top-color: currentColor; + border-right-color: currentColor; + animation: spin 0.9s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.layout { + display: grid; + grid-template-columns: minmax(300px, 0.9fr) minmax(420px, 1.45fr); + gap: 0.9rem; +} + +.left-column, +.right-column { + display: grid; + gap: 0.85rem; + align-content: start; +} + +.profile-header { + display: flex; + align-items: center; + gap: 0.8rem; +} + +.avatar { + width: 76px; + height: 76px; + border-radius: 14px; + border: 1px solid var(--border); + object-fit: cover; + background: var(--surface-soft); +} + +.profile-meta h2 { + margin: 0; + font-size: 1.03rem; +} + +.profile-meta p { + margin: 0.2rem 0; + font-size: 0.85rem; +} + +.profile-meta a { + font-size: 0.82rem; +} + +.profile-card #profileBio { + margin: 0.9rem 0 0.6rem; + font-size: 0.88rem; + line-height: 1.45; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.55rem; +} + +.stat-card { + background: var(--surface-soft); + border: 1px solid var(--border); + border-radius: 10px; + padding: 0.55rem 0.6rem; + display: grid; + gap: 0.3rem; +} + +.stat-card span { + color: var(--muted); + font-size: 0.72rem; +} + +.stat-card strong { + font-size: 1rem; +} + +.card-title-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; +} + +.section-title { + margin: 0; + font-size: 1rem; +} + +.item-list, +.insight-list { + margin: 0.65rem 0 0; + padding: 0; + list-style: none; + display: grid; + gap: 0.5rem; +} + +.item-list li, +.insight-list li { + border: 1px solid var(--border); + background: var(--surface-soft); + border-radius: 10px; + padding: 0.55rem 0.65rem; + font-size: 0.82rem; + line-height: 1.4; + transition: transform 160ms ease, border-color 160ms ease; +} + +.item-list li:hover, +.insight-list li:hover { + transform: translateX(2px); + border-color: color-mix(in srgb, var(--accent) 28%, var(--border)); +} + +.repo-item { + display: flex; + justify-content: space-between; + gap: 0.6rem; + align-items: center; +} + +.repo-item-meta { + color: var(--muted); + font-size: 0.74rem; +} + +.history-list { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; + margin-top: 0.65rem; +} + +.history-chip { + border: 1px solid var(--border); + background: var(--surface-soft); + color: var(--text); + border-radius: 999px; + padding: 0.38rem 0.66rem; + font-size: 0.73rem; + cursor: pointer; +} + +.history-chip:hover { + border-color: var(--accent); +} + +.score-head { + display: grid; + grid-template-columns: auto 1fr; + gap: 0.9rem; + align-items: center; +} + +.score-head h2 { + margin: 0; + font-size: 1rem; +} + +.score-grade { + margin: 0.35rem 0; + font-size: 0.92rem; +} + +.score-ring { + --score-value: 0; + --ring-color: var(--accent); + width: 128px; + height: 128px; + border-radius: 50%; + position: relative; + display: grid; + place-items: center; + background: conic-gradient(var(--ring-color) calc(var(--score-value) * 1%), var(--border) 0); + transition: background 220ms ease; +} + +.score-ring::before { + content: ""; + position: absolute; + inset: 12px; + border-radius: 50%; + background: var(--surface); + border: 1px solid var(--border); +} + +.score-ring[data-level="good"] { + --ring-color: var(--good); +} + +.score-ring[data-level="warn"] { + --ring-color: var(--warn); +} + +.score-ring[data-level="risk"] { + --ring-color: var(--risk); +} + +.score-value { + position: relative; + z-index: 1; + font-size: 1.6rem; + font-weight: 800; +} + +.premium-score-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.55rem; + margin-top: 0.75rem; +} + +.premium-score-item { + border: 1px solid var(--border); + border-radius: 12px; + background: linear-gradient(145deg, var(--surface-soft), color-mix(in srgb, var(--surface-soft) 75%, var(--surface))); + padding: 0.55rem 0.62rem; + display: grid; + gap: 0.2rem; +} + +.premium-score-item span { + color: var(--muted); + font-size: 0.7rem; +} + +.premium-score-item strong { + font-size: 1rem; + letter-spacing: 0.02em; +} + +.premium-score-item small { + color: var(--muted); + font-size: 0.68rem; + line-height: 1.35; +} + +.readiness-track { + margin-top: 0.6rem; + height: 8px; + border-radius: 999px; + background: color-mix(in srgb, var(--accent) 14%, var(--surface-soft)); + overflow: hidden; +} + +.readiness-track span { + display: block; + width: 0; + height: 100%; + border-radius: 999px; + background: linear-gradient(120deg, var(--accent), var(--accent-strong)); + transition: width 280ms ease; +} + +.subscore-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.5rem; + margin-top: 0.95rem; +} + +.subscore-item { + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface-soft); + padding: 0.45rem 0.55rem; display: flex; + justify-content: space-between; + align-items: center; + gap: 0.45rem; + font-size: 0.74rem; +} + +.chip { + display: inline-flex; align-items: center; - justify-content: center; - z-index: 2000; - transition: opacity 0.6s ease; -} -#loader.hide { opacity: 0; pointer-events: none; } -.loader-content { text-align: center; } -.loader-content img { width: 90px; height: 90px; margin-bottom: 12px; } -.spin { animation: spin 2s linear infinite; } -@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } + border-radius: 999px; + padding: 0.22rem 0.55rem; + font-size: 0.7rem; + border: 1px solid; + white-space: nowrap; +} + +.chip-neutral { + color: var(--muted); + border-color: var(--border); + background: transparent; +} + +.chip-good { + color: var(--good); + border-color: color-mix(in srgb, var(--good) 50%, var(--border)); + background: color-mix(in srgb, var(--good) 12%, transparent); +} + +.chip-warn { + color: var(--warn); + border-color: color-mix(in srgb, var(--warn) 55%, var(--border)); + background: color-mix(in srgb, var(--warn) 12%, transparent); +} + +.chip-risk { + color: var(--risk); + border-color: color-mix(in srgb, var(--risk) 55%, var(--border)); + background: color-mix(in srgb, var(--risk) 12%, transparent); +} + +.insights-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.8rem; +} + +.insights-grid h3 { + margin: 0; + font-size: 0.92rem; +} + +.recruiter-sim-card { + background: + radial-gradient(circle at 90% 0%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 38%), + var(--surface); +} + +#aiRecruiterSummary { + margin: 0.5rem 0 0.2rem; + line-height: 1.5; + font-size: 0.84rem; +} + +.career-title { + margin: 0.55rem 0 0.35rem; + font-size: 0.96rem; +} + +.roadmap-list { + margin: 0.72rem 0 0; + padding: 0; + list-style: none; + display: grid; + gap: 0.58rem; + counter-reset: roadmap; +} + +.roadmap-list li { + counter-increment: roadmap; + border: 1px solid var(--border); + border-radius: 12px; + background: var(--surface-soft); + padding: 0.6rem 0.65rem 0.6rem 2.2rem; + position: relative; + font-size: 0.82rem; + line-height: 1.45; +} + +.roadmap-list li::before { + content: counter(roadmap); + position: absolute; + left: 0.6rem; + top: 0.6rem; + width: 1.1rem; + height: 1.1rem; + border-radius: 50%; + display: grid; + place-items: center; + font-size: 0.66rem; + color: #fff; + background: linear-gradient(120deg, var(--accent), var(--accent-strong)); +} + +.transparency-note { + margin: 0.45rem 0 0.8rem; + font-size: 0.79rem; + line-height: 1.4; +} + +.formula-grid { + display: grid; + gap: 0.55rem; +} + +.formula-row { + display: flex; + justify-content: space-between; + gap: 0.8rem; + align-items: center; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface-soft); + padding: 0.55rem 0.65rem; +} + +.formula-row strong { + display: block; + font-size: 0.76rem; + margin-bottom: 0.2rem; +} + +.formula-row p { + margin: 0; + color: var(--muted); + font-size: 0.7rem; + line-height: 1.35; +} + +.formula-row .chip { + white-space: normal; + text-align: right; + max-width: 48%; +} + +.chart-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.7rem; + margin-top: 0.75rem; +} + +.chart-panel { + border: 1px solid var(--border); + border-radius: 12px; + padding: 0.55rem; + background: var(--surface-soft); +} + +.chart-panel h3 { + margin: 0 0 0.35rem; + font-size: 0.82rem; + color: var(--muted); +} + +.chart-panel canvas { + width: 100% !important; + height: 260px !important; +} + +.table-wrap { + overflow-x: auto; + margin-top: 0.75rem; +} + +.table-wrap table { + width: 100%; + border-collapse: collapse; + min-width: 680px; +} + +.table-wrap th, +.table-wrap td { + border-bottom: 1px solid var(--border); + padding: 0.55rem; + text-align: left; + font-size: 0.78rem; + vertical-align: middle; +} + +.table-wrap th { + color: var(--muted); + font-weight: 700; +} + +.repo-link { + font-weight: 700; + text-decoration: none; +} + +.importance-wrap { + display: flex; + align-items: center; + gap: 0.45rem; +} + +.importance-meter { + width: 90px; + height: 8px; + border-radius: 999px; + background: color-mix(in srgb, var(--accent) 18%, var(--surface-soft)); + overflow: hidden; +} + +.importance-meter span { + display: block; + height: 100%; + background: linear-gradient(120deg, var(--accent), var(--accent-strong)); +} + +.empty-state { + color: var(--muted); + font-size: 0.78rem; +} + +@media (prefers-reduced-motion: reduce) { + * { + animation: none !important; + transition: none !important; + scroll-behavior: auto !important; + } +} + +@media (max-width: 1120px) { + .controls-grid { + grid-template-columns: 1fr; + } + + .button-group { + justify-content: flex-start; + } + + .layout { + grid-template-columns: 1fr; + } + + .insights-grid { + grid-template-columns: 1fr; + } + + .formula-row { + flex-direction: column; + align-items: flex-start; + } + + .formula-row .chip { + max-width: 100%; + text-align: left; + } +} + +@media (max-width: 760px) { + .app-shell { + width: calc(100% - 1rem); + margin-top: 0.7rem; + } + + .brand { + align-items: flex-start; + } + + .brand-logo { + width: 48px; + height: 48px; + } + + .stats-grid { + grid-template-columns: 1fr; + } + + .subscore-grid { + grid-template-columns: 1fr; + } + + .premium-score-grid { + grid-template-columns: 1fr; + } + + .score-ring { + width: 108px; + height: 108px; + } + + .score-ring::before { + inset: 10px; + } + + .score-value { + font-size: 1.35rem; + } + + .chart-grid { + grid-template-columns: 1fr; + } +} diff --git a/sw.js b/sw.js index 46f62bde9..0cc6a5c14 100644 --- a/sw.js +++ b/sw.js @@ -1,12 +1,71 @@ -// sw.js - minimal service worker skeleton (drop in site root if you want basic offline cache) -const CACHE_NAME = 'devdetective-v1'; -const OFFLINE_FILES = ['/', '/index.html', '/style.css', '/script.js']; +const CACHE_NAME = "devdetective-v3"; -self.addEventListener('install', (evt) => { - evt.waitUntil(caches.open(CACHE_NAME).then(cache => cache.addAll(OFFLINE_FILES))); +const STATIC_ASSETS = [ + "./", + "./index.html", + "./style.css", + "./script.js", + "./manifest.json", + "./Images/logo.png", + "./Images/favicon-32x32.png", + "./Images/favicon-16x16.png", + "./Images/apple-touch-icon.png", + "./Images/android-chrome-192x192.png", + "./Images/android-chrome-512x512.png" +]; + +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS)) + ); self.skipWaiting(); }); -self.addEventListener('fetch', (evt) => { - evt.respondWith(caches.match(evt.request).then(res => res || fetch(evt.request))); +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all( + keys + .filter((key) => key !== CACHE_NAME) + .map((key) => caches.delete(key)) + ) + ) + ); + self.clients.claim(); +}); + +self.addEventListener("fetch", (event) => { + const request = event.request; + + if (request.method !== "GET") { + return; + } + + const url = new URL(request.url); + + if (url.origin === "https://api.github.com") { + event.respondWith(fetch(request)); + return; + } + + event.respondWith( + caches.match(request).then((cached) => { + if (cached) { + return cached; + } + + return fetch(request).then((response) => { + const isStatic = + url.origin === self.location.origin && + ["document", "script", "style", "image", "font"].includes(request.destination); + + if (isStatic && response && response.status === 200) { + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(request, responseClone)); + } + + return response; + }); + }) + ); }); From df8f95798fc5b1fea3d7d6bb762546cd6677a7f2 Mon Sep 17 00:00:00 2001 From: Sudheer Yadav Date: Fri, 13 Feb 2026 14:27:40 +0000 Subject: [PATCH 11/11] update project --- .env.example | 13 + Images/mascot-detective.svg | 47 + README.md | 158 ++- api/_lib/analysis.js | 1003 ++++++++++++++ api/_lib/github.js | 448 ++++++ api/_lib/rate-limit.js | 92 ++ api/_lib/utils.js | 52 + api/analyze.js | 213 +++ index.html | 77 +- manifest.json | 2 +- package.json | 10 + script.js | 2562 ----------------------------------- src/config/constants.js | 54 + src/main.js | 381 ++++++ src/report/markdown.js | 104 ++ src/ui/charts.js | 179 +++ src/ui/elements.js | 62 + src/ui/render.js | 349 +++++ src/utils/core.js | 160 +++ style.css | 316 ++++- sw.js | 12 +- vercel.json | 71 + 22 files changed, 3691 insertions(+), 2674 deletions(-) create mode 100644 .env.example create mode 100644 Images/mascot-detective.svg create mode 100644 api/_lib/analysis.js create mode 100644 api/_lib/github.js create mode 100644 api/_lib/rate-limit.js create mode 100644 api/_lib/utils.js create mode 100644 api/analyze.js create mode 100644 package.json delete mode 100644 script.js create mode 100644 src/config/constants.js create mode 100644 src/main.js create mode 100644 src/report/markdown.js create mode 100644 src/ui/charts.js create mode 100644 src/ui/elements.js create mode 100644 src/ui/render.js create mode 100644 src/utils/core.js create mode 100644 vercel.json diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..e19ec6b2f --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Required in Vercel for higher GitHub rate limits and pinned GraphQL queries +GITHUB_TOKEN= + +# Optional API-level rate limiter using Vercel KV / Upstash REST +KV_REST_API_URL= +KV_REST_API_TOKEN= + +# Optional tuning +RATE_LIMIT_MAX_REQUESTS=30 +RATE_LIMIT_WINDOW_MS=60000 +ANALYSIS_CACHE_TTL_MS=300000 +GITHUB_CACHE_TTL_MS=120000 +GITHUB_REQUEST_TIMEOUT_MS=10000 diff --git a/Images/mascot-detective.svg b/Images/mascot-detective.svg new file mode 100644 index 000000000..4d0a5e4be --- /dev/null +++ b/Images/mascot-detective.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index a9f8c1c26..02e1df84f 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,117 @@ -# DevDetective - GitHub Portfolio Analyzer & Enhancer +# DevDetective - Vercel Production Build -DevDetective is a production-oriented static web app that evaluates a GitHub profile like a recruiter. -It fetches GitHub data, computes weighted category scores, highlights strengths and red flags, -and generates actionable portfolio improvement suggestions. +DevDetective is a GitHub Portfolio Analyzer & Enhancer optimized for Vercel serverless deployment. +It provides recruiter-style scoring, risk detection, roadmap generation, and downloadable reports using real GitHub data. -## Features +## Highlights -- Accepts a GitHub username or profile URL -- Supports optional GitHub PAT for higher rate limits and GraphQL pinned repositories -- Fetches public repositories, stars, forks, watchers, README presence, language signals, and recency -- Estimates activity consistency using `pushed_at` timestamps -- Fetches authored PR and issue counts via GitHub Search API -- Computes a weighted `GitHub Portfolio Score (0-100)` -- Computes `Hireability Score` and `Portfolio Readiness Level` -- Returns 7 sub-scores: Documentation Quality, Code Activity / Consistency, Project Popularity, Repository Completeness, Language Diversity, Recent Activity, Impact Signals -- Shows transparent scoring inputs and formula context in UI and report output -- Generates recruiter-style insights: Strengths, Red Flags, Actionable Suggestions (minimum 5) -- Adds AI Recruiter Simulation verdict and feedback signals -- Detects hidden portfolio risks (concentration, conversion, collaboration, momentum) -- Recommends a career path with confidence score and next-skill targets -- Generates a personalized multi-week improvement roadmap -- Includes language, repository-importance, subscore radar, and activity-bucket charts -- Includes pinned repository panel and ranked repository table -- Supports light/dark mode persistence -- Exports a downloadable recruiter-ready Markdown report -- Supports offline shell caching through service worker +- Accepts GitHub username, `@username`, or profile URL +- Uses real GitHub REST/GraphQL data through serverless API +- Objective portfolio score (`0-100`) + weighted category subscores +- Recruiter insights: strengths, red flags, hidden risks +- Hireability score + readiness level +- Top repository ranking and visual analytics dashboard +- Personalized improvement roadmap and career-path recommendation +- Downloadable markdown recruiter report +- Dark/light mode and responsive SaaS-style UI -## Optional GitHub Token Setup +## Vercel-Native Architecture -Token is optional, but recommended for better API limits and pinned repositories. +- Static frontend served via Vercel CDN +- Serverless API route: `api/analyze.js` +- Stateless request flow +- Optional distributed rate limiting using Vercel KV / Upstash REST +- API response caching + in-function cache + CDN caching headers +- Request de-duplication for concurrent same-user analysis calls -1. Go to GitHub Settings > Developer settings > Personal access tokens. -2. Create a token with minimal read access for public data. -3. Paste it into the app's token input. +## Project Structure -Token handling: +```text +. +├── api/ +│ ├── analyze.js +│ └── _lib/ +│ ├── analysis.js +│ ├── github.js +│ ├── rate-limit.js +│ └── utils.js +├── src/ +│ ├── main.js +│ ├── config/constants.js +│ ├── report/markdown.js +│ ├── ui/charts.js +│ ├── ui/elements.js +│ ├── ui/render.js +│ └── utils/core.js +├── index.html +├── style.css +├── sw.js +├── manifest.json +├── vercel.json +└── .env.example +``` -- Stored in browser localStorage key: `devdetective_token` -- No backend storage is used in this project +## Environment Variables -## Run Locally +Set in Vercel Project Settings -> Environment Variables. -This project is plain HTML/CSS/JS. Serve the repository root with any static server. +### Required (recommended strongly) -### Option 1: Python +- `GITHUB_TOKEN`: GitHub token for higher rate limits and GraphQL pinned repos -```bash -python3 -m http.server 5500 -``` +### Optional (for distributed rate limiting) -Open: +- `KV_REST_API_URL` +- `KV_REST_API_TOKEN` -```text -http://localhost:5500 -``` +### Optional tuning + +- `RATE_LIMIT_MAX_REQUESTS` (default `30`) +- `RATE_LIMIT_WINDOW_MS` (default `60000`) +- `ANALYSIS_CACHE_TTL_MS` (default `300000`) +- `GITHUB_CACHE_TTL_MS` (default `120000`) +- `GITHUB_REQUEST_TIMEOUT_MS` (default `10000`) + +## Deploy on Vercel -### Option 2: VS Code Live Server or any static server +1. Import this repository in Vercel. +2. Add environment variables above. +3. Deploy. -Serve the repository root and open `index.html`. +Vercel settings: -## Rate Limits and Reliability +- Build Command: **(none required)** +- Output Directory: **(none required)** +- Framework Preset: **Other** -- Handles GitHub API errors with clear user-facing messages -- Shows rate-limit reset time when available -- Aborts in-flight requests when a new analysis starts -- Retries once on transient network failures -- Caches completed analyses for 10 minutes in `devdetective_cache_{username}` +`vercel.json` already includes function config and production headers. -## Scoring Model Summary +## Local Run -Overall score combines these weighted categories: +### Static frontend check + +```bash +python3 -m http.server 5500 +``` + +Open `http://localhost:5500`. + +### Full Vercel-like local run (if Vercel CLI installed) + +```bash +vercel dev +``` -- Documentation Quality (18%) -- Code Activity / Consistency (17%) -- Project Popularity (15%) -- Repository Completeness (14%) -- Language Diversity (10%) -- Recent Activity (14%) -- Impact Signals (12%) +## Reliability & Security -Each sub-score is normalized to `0-100`, then combined by weight. +- Input sanitization for usernames/URLs +- API abuse protection with rate limiting +- 429 responses when limits are exceeded +- Graceful handling for invalid input, not found, network failures, and GitHub upstream failures +- No sensitive token exposed to browser +- Security headers configured via `vercel.json` -## Known Limitations +## Notes -- Commit frequency is approximated using repository `pushed_at` (not full commit history traversal) -- README and language deep checks are limited to top repositories for API efficiency -- Pinned repo retrieval via GraphQL requires token; fallback ranking is used otherwise -- Output quality depends on public GitHub metadata completeness +- Commit consistency is approximated via `pushed_at` metadata. +- Deep README/language checks are limited to top repos for performance and GitHub quota efficiency. diff --git a/api/_lib/analysis.js b/api/_lib/analysis.js new file mode 100644 index 000000000..defd0427a --- /dev/null +++ b/api/_lib/analysis.js @@ -0,0 +1,1003 @@ +import { WEIGHTS } from "../../src/config/constants.js"; +import { + cap01, + clampToRange, + safeRatio, + scoreFromRatio, + daysSinceDate, + joinRepoNames, + formatPercent, + dedupe +} from "../../src/utils/core.js"; + +export function getNamedSubscore(key) { + const labels = { + documentationQuality: "Documentation Quality", + codeActivityConsistency: "Code Activity / Consistency", + projectPopularity: "Project Popularity", + repositoryCompleteness: "Repository Completeness", + languageDiversity: "Language Diversity", + recentActivity: "Recent Activity", + impactSignals: "Impact Signals" + }; + + return labels[key] || key; +} + +export function preRankImportanceScore(repo) { + const days = daysSinceDate(repo.pushed_at); + const freshness = days <= 30 ? 10 : days <= 90 ? 6 : days <= 180 ? 3 : 0; + + return ( + (Number(repo.stargazers_count) || 0) * 3 + + (Number(repo.forks_count) || 0) * 2 + + (Number(repo.watchers_count) || 0) + + freshness + ); +} + +export function buildAnalysisResult({ profile, scorableRepos, contributions, partial, pinnedFromGraphql }) { + const now = Date.now(); + const scorableCount = scorableRepos.length; + + const totalStars = scorableRepos.reduce((sum, repo) => sum + (Number(repo.stargazers_count) || 0), 0); + const totalForks = scorableRepos.reduce((sum, repo) => sum + (Number(repo.forks_count) || 0), 0); + const totalWatchers = scorableRepos.reduce((sum, repo) => sum + (Number(repo.watchers_count) || 0), 0); + + const descriptionCoverage = safeRatio( + scorableRepos.filter((repo) => Boolean((repo.description || "").trim())).length, + scorableCount + ); + + const readmeScannedRepos = scorableRepos.filter((repo) => repo._readmeChecked); + const readmeCoverage = safeRatio( + readmeScannedRepos.filter((repo) => repo._hasReadme === true).length, + readmeScannedRepos.length + ); + + const nonEmptyRepoRatio = safeRatio( + scorableRepos.filter((repo) => (Number(repo.size) || 0) > 0).length, + scorableCount + ); + + const homepageRatio = safeRatio( + scorableRepos.filter((repo) => Boolean((repo.homepage || "").trim())).length, + scorableCount + ); + + const topicsRatio = safeRatio( + scorableRepos.filter((repo) => Array.isArray(repo.topics) && repo.topics.length > 0).length, + scorableCount + ); + + const pushedDays = scorableRepos + .map((repo) => daysSinceDate(repo.pushed_at, now)) + .filter((days) => Number.isFinite(days)); + + const reposUpdated30d = pushedDays.filter((days) => days <= 30).length; + const reposUpdated90d = pushedDays.filter((days) => days <= 90).length; + const reposInactive180d = pushedDays.filter((days) => days > 180).length; + + const activityBuckets = { + updated30d: pushedDays.filter((days) => days <= 30).length, + updated31to90d: pushedDays.filter((days) => days > 30 && days <= 90).length, + updated91to180d: pushedDays.filter((days) => days > 90 && days <= 180).length, + updated181plus: pushedDays.filter((days) => days > 180).length + }; + + const lastPushRepo = [...scorableRepos] + .filter((repo) => repo.pushed_at) + .sort((a, b) => new Date(b.pushed_at).getTime() - new Date(a.pushed_at).getTime())[0] || null; + + const daysSinceLastPush = lastPushRepo ? daysSinceDate(lastPushRepo.pushed_at, now) : 9999; + + const activity = computeActivityInLastSixMonths(scorableRepos); + const activeMonthsLast6Ratio = activity.ratio; + + const starsPerRepo = scorableCount ? totalStars / scorableCount : 0; + const forksPerRepo = scorableCount ? totalForks / scorableCount : 0; + const watchersPerRepo = scorableCount ? totalWatchers / scorableCount : 0; + + const languageTotals = aggregateLanguageTotals(scorableRepos); + const languageEntries = Object.entries(languageTotals).sort((a, b) => b[1] - a[1]); + const uniqueLanguages = languageEntries.length; + const normalizedShannonEntropy = computeNormalizedEntropy(languageTotals); + const totalLanguageBytes = languageEntries.reduce((sum, [, bytes]) => sum + (Number(bytes) || 0), 0); + const dominantLanguage = languageEntries[0] ? languageEntries[0][0] : "Unknown"; + const dominantLanguageShare = + totalLanguageBytes > 0 && languageEntries[0] ? cap01((Number(languageEntries[0][1]) || 0) / totalLanguageBytes) : 0; + const topLanguages = languageEntries.slice(0, 5).map(([language]) => language); + + const topRepoByStars = [...scorableRepos].sort( + (a, b) => (Number(b.stargazers_count) || 0) - (Number(a.stargazers_count) || 0) + )[0] || null; + + const topRepoStars = topRepoByStars ? Number(topRepoByStars.stargazers_count) || 0 : 0; + + const reposUpdated90dRatio = safeRatio(reposUpdated90d, scorableCount); + const reposUpdated30dRatio = safeRatio(reposUpdated30d, scorableCount); + const recencyBucket = getRecencyBucket(daysSinceLastPush); + + const subscores = { + documentationQuality: scoreFromRatio(0.75 * readmeCoverage + 0.25 * descriptionCoverage), + codeActivityConsistency: scoreFromRatio(0.6 * activeMonthsLast6Ratio + 0.4 * reposUpdated90dRatio), + projectPopularity: scoreFromRatio( + 0.6 * cap01(starsPerRepo / 50) + + 0.25 * cap01(forksPerRepo / 20) + + 0.15 * cap01(watchersPerRepo / 20) + ), + repositoryCompleteness: scoreFromRatio( + 0.5 * nonEmptyRepoRatio + + 0.3 * homepageRatio + + 0.2 * topicsRatio + ), + languageDiversity: scoreFromRatio( + 0.7 * cap01(uniqueLanguages / 8) + + 0.3 * normalizedShannonEntropy + ), + recentActivity: scoreFromRatio(0.7 * recencyBucket + 0.3 * reposUpdated30dRatio), + impactSignals: scoreFromRatio( + 0.35 * cap01(topRepoStars / 300) + + 0.25 * cap01((Number(profile.followers) || 0) / 500) + + 0.25 * cap01(contributions.prCount / 200) + + 0.15 * cap01(contributions.issueCount / 100) + ) + }; + + const overallScore = clampToRange( + Math.round( + (subscores.documentationQuality * WEIGHTS.documentationQuality + + subscores.codeActivityConsistency * WEIGHTS.codeActivityConsistency + + subscores.projectPopularity * WEIGHTS.projectPopularity + + subscores.repositoryCompleteness * WEIGHTS.repositoryCompleteness + + subscores.languageDiversity * WEIGHTS.languageDiversity + + subscores.recentActivity * WEIGHTS.recentActivity + + subscores.impactSignals * WEIGHTS.impactSignals) / 100 + ), + 0, + 100 + ); + + const rankedRepos = buildRankedRepositories(scorableRepos); + + const pinnedRepos = pinnedFromGraphql && pinnedFromGraphql.length + ? { + source: "graphql", + items: pinnedFromGraphql + } + : { + source: "fallback", + items: rankedRepos.slice(0, 6).map((repo) => ({ + name: repo.name, + url: repo.url, + stars: repo.stars + })) + }; + + const metrics = { + scorableRepoCount: scorableCount, + totalStars, + totalForks, + totalWatchers, + readmeCoverage, + reposUpdated30d, + reposUpdated90d, + daysSinceLastPush, + authoredPRCount: contributions.prCount, + authoredIssueCount: contributions.issueCount, + uniqueLanguages, + topLanguages, + dominantLanguage, + dominantLanguageShare, + + descriptionCoverage, + descriptionlessRatio: cap01(1 - descriptionCoverage), + activeMonthsLast6: activity.activeMonths, + activeMonthsLast6Ratio, + reposInactive180d, + reposInactive180dRatio: safeRatio(reposInactive180d, scorableCount), + emptyRepoRatio: cap01(1 - nonEmptyRepoRatio), + nonEmptyRepoRatio, + homepageRatio, + topicsRatio, + starsPerRepo, + forksPerRepo, + watchersPerRepo, + reposUpdated30dRatio, + reposUpdated90dRatio, + recencyBucket, + topRepoStars, + normalizedShannonEntropy, + readmeSampleSize: readmeScannedRepos.length, + lastPushDate: lastPushRepo ? lastPushRepo.pushed_at : null, + partialReadmeFailures: partial.readmeFailures, + partialLanguageFailures: partial.languageFailures, + deepRepoCount: partial.deepRepoCount, + activityBuckets + }; + + const strengths = buildStrengths(subscores, metrics, rankedRepos); + const redFlags = buildRedFlags(subscores, metrics); + const suggestions = buildSuggestions(subscores, metrics, rankedRepos, pinnedRepos); + const hiddenRisks = buildHiddenRisks(subscores, metrics, rankedRepos); + const hireabilityScore = calculateHireabilityScore(subscores, overallScore, hiddenRisks); + const readiness = classifyReadiness(hireabilityScore, overallScore); + const careerPath = buildCareerPathRecommendation(metrics, rankedRepos, subscores); + const improvementRoadmap = buildImprovementRoadmap(subscores, metrics, rankedRepos, careerPath, hiddenRisks); + const recruiterSimulation = buildRecruiterSimulation({ + overallScore, + hireabilityScore, + readiness, + subscores, + metrics, + strengths, + redFlags, + hiddenRisks, + rankedRepos + }); + + const grade = scoreToGrade(overallScore); + const scoreSummary = buildScoreSummary(overallScore, grade, subscores, metrics, hireabilityScore, readiness.label); + + return { + profile: { + login: profile.login, + name: profile.name || profile.login, + htmlUrl: profile.html_url, + followers: Number(profile.followers) || 0, + following: Number(profile.following) || 0, + publicRepos: Number(profile.public_repos) || 0, + avatarUrl: profile.avatar_url, + bio: profile.bio || "No bio provided.", + createdAt: profile.created_at, + updatedAt: profile.updated_at + }, + generatedAt: new Date().toISOString(), + weights: WEIGHTS, + subscores, + overallScore, + hireabilityScore, + readiness, + readinessLevel: readiness.label, + metrics, + strengths, + redFlags, + suggestions, + hiddenRisks, + recruiterSimulation, + careerPath, + improvementRoadmap, + pinnedRepos, + rankedRepos: rankedRepos.map((repo) => ({ + name: repo.name, + url: repo.url, + importance: repo.importance, + stars: repo.stars, + forks: repo.forks, + watchers: repo.watchers, + pushedAt: repo.pushedAt, + hasReadme: repo.hasReadme, + language: repo.language, + homepage: repo.homepage, + topicsCount: repo.topicsCount, + hasDescription: repo.hasDescription, + isEmpty: repo.isEmpty, + readmeKnown: repo.readmeKnown + })), + languageTotals, + grade, + scoreSummary + }; +} + +function aggregateLanguageTotals(repos) { + const totals = {}; + + repos.forEach((repo) => { + let hasDetailedBreakdown = false; + + if (repo._languageBytes && typeof repo._languageBytes === "object") { + const entries = Object.entries(repo._languageBytes); + if (entries.length) { + entries.forEach(([language, bytes]) => { + totals[language] = (totals[language] || 0) + (Number(bytes) || 0); + }); + hasDetailedBreakdown = true; + } + } + + if (!hasDetailedBreakdown && repo.language) { + totals[repo.language] = (totals[repo.language] || 0) + 1000; + } + }); + + return totals; +} + +function computeNormalizedEntropy(totals) { + const values = Object.values(totals).filter((value) => value > 0); + const total = values.reduce((sum, value) => sum + value, 0); + + if (total <= 0 || values.length <= 1) { + return 0; + } + + const entropy = values.reduce((sum, value) => { + const p = value / total; + return sum - p * Math.log2(p); + }, 0); + + const maxEntropy = Math.log2(values.length); + return maxEntropy > 0 ? entropy / maxEntropy : 0; +} + +function buildRankedRepositories(repos) { + if (!repos.length) { + return []; + } + + const ranked = repos.map((repo) => { + const ageDays = daysSinceDate(repo.pushed_at); + + const recencyBoost = ageDays <= 30 ? 15 : ageDays <= 90 ? 8 : ageDays <= 180 ? 4 : 0; + const readmeBoost = repo._hasReadme === true ? 8 : 0; + const homepageBoost = (repo.homepage || "").trim() ? 4 : 0; + const topicsBoost = Array.isArray(repo.topics) && repo.topics.length > 0 ? 3 : 0; + const descriptionBoost = (repo.description || "").trim() ? 3 : 0; + const sizeBoost = (Number(repo.size) || 0) > 0 ? 2 : 0; + + const rawImportance = + (Number(repo.stargazers_count) || 0) * 4 + + (Number(repo.forks_count) || 0) * 3 + + (Number(repo.watchers_count) || 0) * 2 + + recencyBoost + + readmeBoost + + homepageBoost + + topicsBoost + + descriptionBoost + + sizeBoost; + + return { + name: repo.name, + url: repo.html_url, + rawImportance, + stars: Number(repo.stargazers_count) || 0, + forks: Number(repo.forks_count) || 0, + watchers: Number(repo.watchers_count) || 0, + pushedAt: repo.pushed_at, + hasReadme: repo._hasReadme === true, + readmeKnown: Boolean(repo._readmeChecked), + language: repo.language || "Unknown", + homepage: (repo.homepage || "").trim(), + topicsCount: Array.isArray(repo.topics) ? repo.topics.length : 0, + hasDescription: Boolean((repo.description || "").trim()), + isEmpty: (Number(repo.size) || 0) === 0 + }; + }); + + const maxRaw = Math.max(...ranked.map((repo) => repo.rawImportance), 1); + + ranked.forEach((repo) => { + repo.importance = clampToRange(Math.round((repo.rawImportance / maxRaw) * 100), 0, 100); + }); + + return ranked.sort((a, b) => b.importance - a.importance || b.stars - a.stars); +} + +function buildStrengths(subscores, metrics, rankedRepos) { + const strengths = []; + + if (subscores.codeActivityConsistency >= 70) { + strengths.push( + `Strong activity consistency with ${metrics.activeMonthsLast6}/6 active months and ${metrics.reposUpdated90d} repositories updated in the last 90 days.` + ); + } + + if (subscores.projectPopularity >= 70) { + const top = rankedRepos[0]; + strengths.push( + `Good popularity signals: ${metrics.totalStars} total stars and ${top ? `${top.name} as a leading project` : "multiple visible projects"}.` + ); + } + + if (subscores.languageDiversity >= 70) { + strengths.push(`Diverse technical stack with ${metrics.uniqueLanguages} detected languages.`); + } + + if (subscores.recentActivity >= 70) { + strengths.push(`Recent contribution momentum: latest push was ${metrics.daysSinceLastPush} day(s) ago.`); + } + + if (subscores.documentationQuality >= 70) { + strengths.push( + `Documentation quality is strong with ${(metrics.readmeCoverage * 100).toFixed(0)}% README coverage in sampled repositories.` + ); + } + + if (subscores.impactSignals >= 70) { + strengths.push( + `Impact signals are healthy with ${metrics.authoredPRCount} authored PRs and ${metrics.authoredIssueCount} authored issues.` + ); + } + + if (!strengths.length) { + strengths.push( + `Public portfolio is visible (${metrics.scorableRepoCount} non-fork repositories) but still needs stronger recruiter-facing signals.` + ); + } + + return strengths.slice(0, 6); +} + +function buildRedFlags(subscores, metrics) { + const redFlags = []; + + if (metrics.readmeCoverage < 0.5) { + redFlags.push( + `Low README coverage (${(metrics.readmeCoverage * 100).toFixed(0)}%) in sampled repositories makes project intent harder to evaluate.` + ); + } + + if (metrics.reposInactive180dRatio > 0.5) { + redFlags.push( + `${metrics.reposInactive180d} of ${metrics.scorableRepoCount} repositories have been inactive for more than 180 days.` + ); + } + + if (metrics.emptyRepoRatio > 0.3) { + redFlags.push( + `${(metrics.emptyRepoRatio * 100).toFixed(0)}% of repositories look empty or near-empty based on repository size.` + ); + } + + if (metrics.descriptionlessRatio > 0.4) { + redFlags.push( + `${(metrics.descriptionlessRatio * 100).toFixed(0)}% of repositories have missing descriptions, which weakens recruiter readability.` + ); + } + + if (metrics.daysSinceLastPush > 90) { + redFlags.push(`No recent pushes in the last 90 days (latest push was ${metrics.daysSinceLastPush} day(s) ago).`); + } + + if (subscores.impactSignals < 40) { + redFlags.push( + `Impact signals are weak (${subscores.impactSignals}/100) due to low follower, PR, issue, or top-repo traction.` + ); + } + + if (!redFlags.length) { + redFlags.push("No major red flags detected from the available public signals."); + } + + return redFlags.slice(0, 6); +} + +function buildSuggestions(subscores, metrics, rankedRepos, pinnedRepos) { + const suggestions = []; + + const missingReadmeRepos = rankedRepos + .filter((repo) => repo.readmeKnown && !repo.hasReadme) + .slice(0, 3) + .map((repo) => repo.name); + + const staleRepos = rankedRepos + .filter((repo) => daysSinceDate(repo.pushedAt) > 180) + .slice(0, 3) + .map((repo) => repo.name); + + const noHomepageRepos = rankedRepos + .filter((repo) => !repo.homepage) + .slice(0, 3) + .map((repo) => repo.name); + + const emptyRepos = rankedRepos + .filter((repo) => repo.isEmpty) + .slice(0, 3) + .map((repo) => repo.name); + + const missingDescriptionRepos = rankedRepos + .filter((repo) => !repo.hasDescription) + .slice(0, 3) + .map((repo) => repo.name); + + if (missingReadmeRepos.length) { + suggestions.push({ + priority: 120 - subscores.documentationQuality, + text: `Add README files to ${joinRepoNames(missingReadmeRepos)} with problem statement, setup, usage, and outcomes.` + }); + } + + if (staleRepos.length) { + suggestions.push({ + priority: 120 - subscores.recentActivity, + text: `Update or archive stale repositories (${joinRepoNames(staleRepos)}) so recruiters see a maintained portfolio.` + }); + } + + if (noHomepageRepos.length) { + suggestions.push({ + priority: 110 - subscores.repositoryCompleteness, + text: `Add live demo or homepage links for ${joinRepoNames(noHomepageRepos)} to improve project completeness.` + }); + } + + if (emptyRepos.length) { + suggestions.push({ + priority: 108 - subscores.repositoryCompleteness, + text: `Complete or archive near-empty repositories (${joinRepoNames(emptyRepos)}) to reduce noise in your public profile.` + }); + } + + if (missingDescriptionRepos.length || metrics.descriptionlessRatio > 0.4) { + const target = missingDescriptionRepos.length ? joinRepoNames(missingDescriptionRepos) : "your weakest repos"; + suggestions.push({ + priority: 109 - subscores.documentationQuality, + text: `Improve project descriptions for ${target} with concise problem, stack, and outcomes so recruiters can scan faster.` + }); + } + + if (subscores.codeActivityConsistency < 70) { + suggestions.push({ + priority: 105 - subscores.codeActivityConsistency, + text: "Improve commit consistency: target at least 1 meaningful commit per week for 8 weeks and aim for 4/6 active months." + }); + } + + if (subscores.impactSignals < 70) { + suggestions.push({ + priority: 105 - subscores.impactSignals, + text: "Increase impact signals by targeting 2 authored PRs and 2 authored issues per month on relevant repositories." + }); + } + + if (pinnedRepos.source === "fallback") { + const suggestedPins = rankedRepos.slice(0, 3).map((repo) => repo.name); + suggestions.push({ + priority: 102, + text: `Pin your strongest repositories (${joinRepoNames(suggestedPins)}) so recruiters immediately see your best work.` + }); + } + + if (metrics.topicsRatio < 0.5) { + suggestions.push({ + priority: 95, + text: "Add GitHub topics/tags to your key repositories to improve discovery and communicate stack relevance quickly." + }); + } + + if (metrics.uniqueLanguages < 3) { + suggestions.push({ + priority: 88, + text: "Showcase at least one additional production-quality project in a different language or framework to broaden stack signals." + }); + } + + const sorted = suggestions + .sort((a, b) => b.priority - a.priority) + .map((entry) => entry.text) + .filter((text, index, arr) => arr.indexOf(text) === index); + + const defaults = [ + `Raise README coverage from ${(metrics.readmeCoverage * 100).toFixed(0)}% to at least 80% in your top repositories.`, + "Set a monthly maintenance pass to close stale issues and refresh pinned projects with recent commits.", + "Improve repository completeness by ensuring every flagship repo has README, topics, and a demo/homepage link.", + "Create a monthly portfolio changelog in one pinned repository to highlight recent improvements and impact.", + "Publish measurable project outcomes (users, performance, business value) in your top README files." + ]; + + while (sorted.length < 5 && defaults.length) { + sorted.push(defaults.shift()); + } + + return sorted.slice(0, 7); +} + +function buildHiddenRisks(subscores, metrics, rankedRepos) { + const risks = []; + + if (metrics.dominantLanguageShare > 0.78 && metrics.uniqueLanguages >= 2) { + risks.push( + `Stack concentration risk: ${metrics.dominantLanguage} accounts for ${formatPercent(metrics.dominantLanguageShare)} of detected language volume.` + ); + } + + const topWithoutHomepage = rankedRepos + .slice(0, 5) + .filter((repo) => !repo.homepage) + .map((repo) => repo.name); + + if (topWithoutHomepage.length >= 3) { + risks.push( + `Conversion risk: ${topWithoutHomepage.length} of your top repositories lack demo/homepage links (${joinRepoNames(topWithoutHomepage.slice(0, 3))}).` + ); + } + + const starConcentration = metrics.totalStars > 0 ? metrics.topRepoStars / metrics.totalStars : 0; + if (starConcentration > 0.85 && metrics.totalStars >= 20 && rankedRepos.length >= 4) { + risks.push( + `Brand concentration risk: one repository drives ${formatPercent(starConcentration)} of total stars, so portfolio impact is overly dependent on a single project.` + ); + } + + if (metrics.authoredPRCount < 5 && metrics.scorableRepoCount >= 10) { + risks.push( + `Collaboration signal risk: only ${metrics.authoredPRCount} authored PRs across a portfolio of ${metrics.scorableRepoCount} repositories.` + ); + } + + if (metrics.reposUpdated90dRatio > 0.45 && metrics.reposUpdated30dRatio < 0.15) { + risks.push( + "Momentum decay risk: older recent activity exists, but updates in the last 30 days are sparse." + ); + } + + if (subscores.repositoryCompleteness < 50 && metrics.topicsRatio < 0.35) { + risks.push( + "Discoverability risk: weak metadata coverage (topics and project links) can reduce recruiter confidence during quick profile scans." + ); + } + + if (!risks.length) { + risks.push("No hidden structural risks detected beyond the visible red flags."); + } + + return risks.slice(0, 6); +} + +function calculateHireabilityScore(subscores, overallScore, hiddenRisks) { + const riskCount = hiddenRisks.filter((item) => !/^No hidden/i.test(item)).length; + const penalty = Math.min(riskCount * 4, 16); + + const raw = + overallScore * 0.45 + + subscores.impactSignals * 0.2 + + subscores.recentActivity * 0.15 + + subscores.documentationQuality * 0.1 + + subscores.repositoryCompleteness * 0.1; + + return clampToRange(Math.round(raw - penalty), 0, 100); +} + +function classifyReadiness(hireabilityScore, overallScore) { + const blended = clampToRange(Math.round((hireabilityScore + overallScore) / 2), 0, 100); + + if (blended >= 85) { + return { + label: "Recruiter-Ready", + severity: "good", + percent: blended, + summary: "Portfolio can usually pass recruiter screens without major concerns." + }; + } + + if (blended >= 70) { + return { + label: "Interview-Ready", + severity: "good", + percent: blended, + summary: "Strong enough for interview pipelines with minor polish opportunities." + }; + } + + if (blended >= 55) { + return { + label: "Emerging", + severity: "warn", + percent: blended, + summary: "Promising portfolio that needs stronger consistency and presentation signals." + }; + } + + return { + label: "Foundation Stage", + severity: "risk", + percent: blended, + summary: "Core work is visible, but recruiter confidence is currently limited." + }; +} + +function buildRecruiterSimulation({ + overallScore, + hireabilityScore, + readiness, + subscores, + metrics, + strengths, + redFlags, + hiddenRisks, + rankedRepos +}) { + let verdict = "Not Ready for Interview Loop"; + let level = "risk"; + + if (hireabilityScore >= 82) { + verdict = "Strong Consider"; + level = "good"; + } else if (hireabilityScore >= 68) { + verdict = "Proceed to Technical Screen"; + level = "warn"; + } else if (hireabilityScore >= 52) { + verdict = "Potential with Portfolio Polish"; + level = "warn"; + } + + const leadingRepo = rankedRepos[0]; + const summary = `${verdict}: overall ${overallScore}/100 and hireability ${hireabilityScore}/100. Current readiness is ${readiness.label}. ${ + leadingRepo ? `Top signal comes from ${leadingRepo.name} (${leadingRepo.importance}/100 importance).` : "No standout repository identified yet." + }`; + + const signals = []; + if (strengths[0]) { + signals.push(`Positive signal: ${strengths[0]}`); + } + if (metrics.totalStars > 0) { + signals.push(`Market traction: ${metrics.totalStars} total stars across ${metrics.scorableRepoCount} scored repositories.`); + } else { + signals.push("Market traction is minimal; add demos and visibility to increase external validation."); + } + const firstRedFlag = redFlags.find((item) => !/No major red flags/i.test(item)); + if (firstRedFlag) { + signals.push(`Primary concern: ${firstRedFlag}`); + } + const firstHiddenRisk = hiddenRisks.find((item) => !/^No hidden/i.test(item)); + if (firstHiddenRisk) { + signals.push(`Hidden concern: ${firstHiddenRisk}`); + } + if (subscores.impactSignals < 60) { + signals.push("Interview risk: impact signals are below benchmark for competitive product roles."); + } + + return { + verdict, + level, + summary, + signals: signals.slice(0, 6) + }; +} + +function buildCareerPathRecommendation(metrics, rankedRepos, subscores) { + const languages = (metrics.topLanguages || []).map((lang) => lang.toLowerCase()); + const hasAnyLanguage = (list) => list.some((lang) => languages.includes(lang)); + + let title = "Generalist Software Engineer"; + let summary = "Your repositories indicate broad engineering capability across multiple project types."; + let nextSkills = [ + "Create 2 case-study READMEs that highlight architecture decisions and measurable outcomes.", + "Add live demos to your top projects to improve recruiter conversion.", + "Contribute at least 2 PRs/month to repositories related to your target role." + ]; + + if (hasAnyLanguage(["javascript", "typescript"])) { + title = "Full-Stack JavaScript Engineer"; + summary = "Your language mix and repository profile align best with product-focused full-stack roles."; + nextSkills = [ + "Ship one end-to-end project with production deployment, auth, and monitoring.", + "Document system architecture and tradeoffs for your top JavaScript/TypeScript repositories.", + "Add test coverage and CI status badges to your top 3 repositories." + ]; + } else if (hasAnyLanguage(["python"])) { + title = "Data / AI Engineer"; + summary = "Python-heavy activity indicates strong alignment with data and AI engineering tracks."; + nextSkills = [ + "Publish one reproducible ML/data project with dataset, metrics, and inference/demo endpoint.", + "Add evaluation methodology and model limitations to README docs.", + "Showcase pipeline automation and observability in at least one repository." + ]; + } else if (hasAnyLanguage(["java", "kotlin", "scala"])) { + title = "Backend Platform Engineer"; + summary = "JVM-oriented repositories and contribution signals fit backend and platform engineering roles."; + nextSkills = [ + "Demonstrate API design quality with versioned contracts and load/performance notes.", + "Add reliability signals: retries, circuit breakers, and structured logging.", + "Publish a backend project with deployment and scalability benchmarks." + ]; + } else if (hasAnyLanguage(["go", "rust", "c", "c++"])) { + title = "Systems / Infrastructure Engineer"; + summary = "Your dominant languages suggest strongest fit for systems and infrastructure engineering paths."; + nextSkills = [ + "Build one performance-focused project with clear latency/throughput benchmarks.", + "Document low-level design choices and profiling evidence in README.", + "Add automation scripts for build/test/release workflows." + ]; + } else if (hasAnyLanguage(["swift", "objective-c", "dart"])) { + title = "Mobile Application Engineer"; + summary = "Language signals indicate strongest fit for modern mobile development roles."; + nextSkills = [ + "Publish a shipped-quality mobile app with store-ready documentation and screenshots.", + "Add crash/error monitoring strategy and release notes cadence.", + "Showcase offline support and performance considerations." + ]; + } + + const confidence = clampToRange( + Math.round( + 52 + + metrics.dominantLanguageShare * 18 + + Math.min(metrics.uniqueLanguages, 5) * 4 + + (subscores.codeActivityConsistency >= 70 ? 6 : 0) + + (subscores.impactSignals >= 60 ? 6 : 0) + ), + 35, + 95 + ); + + const weakestSubscore = Object.entries(subscores).sort((a, b) => a[1] - b[1])[0][0]; + const weakestAdviceMap = { + documentationQuality: "Strengthen documentation quality to make your projects legible to non-engineers.", + codeActivityConsistency: "Establish a visible weekly commit cadence to reduce perceived delivery risk.", + projectPopularity: "Increase visibility through demos, developer posts, and open-source collaboration.", + repositoryCompleteness: "Add project metadata (description, topics, demos) to boost portfolio clarity.", + languageDiversity: "Add one adjacent-stack project to signal broader technical range.", + recentActivity: "Prioritize recent updates in top repositories to maintain recruiter confidence.", + impactSignals: "Increase authored PR and issue activity in relevant external repositories." + }; + + nextSkills.push(weakestAdviceMap[weakestSubscore]); + + if (rankedRepos[0] && rankedRepos[0].importance >= 80) { + nextSkills.push(`Position \`${rankedRepos[0].name}\` as flagship project with a complete case-study README.`); + } + + return { + title, + confidence, + summary, + nextSkills: dedupe(nextSkills).slice(0, 6) + }; +} + +function buildImprovementRoadmap(subscores, metrics, rankedRepos, careerPath, hiddenRisks) { + const roadmap = []; + const missingReadmeRepos = rankedRepos + .filter((repo) => repo.readmeKnown && !repo.hasReadme) + .slice(0, 2) + .map((repo) => repo.name); + const noHomepageRepos = rankedRepos + .filter((repo) => !repo.homepage) + .slice(0, 2) + .map((repo) => repo.name); + const staleRepos = rankedRepos + .filter((repo) => daysSinceDate(repo.pushedAt) > 180) + .slice(0, 2) + .map((repo) => repo.name); + + roadmap.push( + "Week 1: Set portfolio baseline by updating profile bio, pinning top repositories, and documenting measurable outcomes." + ); + + const deficitOrder = Object.entries(subscores) + .sort((a, b) => a[1] - b[1]) + .map(([key]) => key); + + deficitOrder.forEach((key) => { + if (key === "documentationQuality" && missingReadmeRepos.length) { + roadmap.push( + `Week 1-2: Add structured README files to ${joinRepoNames(missingReadmeRepos)} with problem, architecture, setup, and results.` + ); + } else if (key === "recentActivity" || key === "codeActivityConsistency") { + roadmap.push( + "Week 2-5: Maintain weekly commits (minimum 1 meaningful update/week) across at least 4 core repositories." + ); + } else if (key === "repositoryCompleteness" && noHomepageRepos.length) { + roadmap.push( + `Week 2-3: Add demo/homepage links and GitHub topics for ${joinRepoNames(noHomepageRepos)}.` + ); + } else if (key === "impactSignals") { + roadmap.push( + "Week 3-6: Target 8 authored PRs and 6 authored issues in repositories aligned to your target role." + ); + } else if (key === "projectPopularity") { + roadmap.push( + "Week 4: Publish concise demo posts and architecture threads to improve repository discoverability and star velocity." + ); + } else if (key === "languageDiversity" && metrics.uniqueLanguages < 4) { + roadmap.push( + "Week 5-7: Build one production-quality project in an adjacent stack to expand technical breadth signals." + ); + } + }); + + if (staleRepos.length) { + roadmap.push(`Week 3: Refresh or archive stale repositories (${joinRepoNames(staleRepos)}) to reduce portfolio noise.`); + } + + const firstHiddenRisk = hiddenRisks.find((item) => !/^No hidden/i.test(item)); + if (firstHiddenRisk) { + roadmap.push(`Week 4: Resolve hidden risk identified by analysis: ${firstHiddenRisk}`); + } + + roadmap.push( + `Week 8: Repackage top 3 projects for ${careerPath.title} positioning with recruiter-focused case studies and outcomes.` + ); + + const defaults = [ + "Set a monthly portfolio review reminder to keep all flagship repositories active and complete.", + "Add short demo videos/GIFs to your top repositories for faster recruiter evaluation." + ]; + + const uniqueRoadmap = dedupe(roadmap); + while (uniqueRoadmap.length < 5 && defaults.length) { + uniqueRoadmap.push(defaults.shift()); + } + + return uniqueRoadmap.slice(0, 7); +} + +function buildScoreSummary(overallScore, grade, subscores, metrics, hireabilityScore, readinessLabel) { + const strongest = getNamedSubscore(Object.entries(subscores).sort((a, b) => b[1] - a[1])[0][0]); + const weakest = getNamedSubscore(Object.entries(subscores).sort((a, b) => a[1] - b[1])[0][0]); + + let summary = `Score ${overallScore}/100 (${grade}), hireability ${hireabilityScore}/100 (${readinessLabel}). Strongest area: ${strongest}. Biggest gap: ${weakest}.`; + + if (metrics.partialLanguageFailures || metrics.partialReadmeFailures) { + summary += ` Partial data warning: ${metrics.partialLanguageFailures + metrics.partialReadmeFailures} deep checks failed due to API limits or transient errors.`; + } + + return summary; +} + +function computeActivityInLastSixMonths(repos) { + const now = new Date(); + const monthKeys = []; + + for (let i = 0; i < 6; i += 1) { + const date = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - i, 1)); + monthKeys.push(`${date.getUTCFullYear()}-${date.getUTCMonth() + 1}`); + } + + const activeSet = new Set(); + + repos.forEach((repo) => { + if (!repo.pushed_at) { + return; + } + + const pushed = new Date(repo.pushed_at); + const key = `${pushed.getUTCFullYear()}-${pushed.getUTCMonth() + 1}`; + if (monthKeys.includes(key)) { + activeSet.add(key); + } + }); + + const activeMonths = activeSet.size; + return { + activeMonths, + ratio: activeMonths / 6 + }; +} + +function getRecencyBucket(daysSinceLastPush) { + if (daysSinceLastPush <= 7) { + return 1; + } + if (daysSinceLastPush <= 30) { + return 0.8; + } + if (daysSinceLastPush <= 90) { + return 0.6; + } + if (daysSinceLastPush <= 180) { + return 0.3; + } + return 0.1; +} + +function scoreToGrade(score) { + if (score >= 90) { + return "A+"; + } + if (score >= 80) { + return "A"; + } + if (score >= 70) { + return "B"; + } + if (score >= 60) { + return "C"; + } + if (score >= 45) { + return "D"; + } + return "E"; +} diff --git a/api/_lib/github.js b/api/_lib/github.js new file mode 100644 index 000000000..258cdd0f9 --- /dev/null +++ b/api/_lib/github.js @@ -0,0 +1,448 @@ +import { buildAnalysisResult, preRankImportanceScore } from "./analysis.js"; +import { mapWithConcurrency, withTimeout } from "./utils.js"; + +const GITHUB_API_ROOT = "https://api.github.com"; +const GITHUB_GRAPHQL_ENDPOINT = "https://api.github.com/graphql"; + +const MAX_REPO_PAGES = 3; +const MAX_DEEP_REPOS = 30; +const CONCURRENCY_LIMIT = 5; +const REQUEST_TIMEOUT_MS = Number(process.env.GITHUB_REQUEST_TIMEOUT_MS || 10_000); +const GITHUB_CACHE_TTL_MS = Number(process.env.GITHUB_CACHE_TTL_MS || 120_000); + +const githubResponseCache = new Map(); + +export class GitHubError extends Error { + constructor(type, message, details = {}) { + super(message); + this.name = "GitHubError"; + this.type = type; + this.details = details; + } +} + +export function parseProfileInput(rawInput) { + const value = (rawInput || "").trim(); + + if (!value) { + return { ok: false, error: "Enter a GitHub username or profile URL." }; + } + + let candidate = value; + + if (/github\.com\//i.test(candidate) && !/^https?:\/\//i.test(candidate)) { + candidate = `https://${candidate}`; + } + + if (/^https?:\/\//i.test(candidate)) { + try { + const url = new URL(candidate); + const host = url.hostname.toLowerCase(); + if (host !== "github.com" && host !== "www.github.com") { + return { ok: false, error: "URL must be a valid github.com profile URL." }; + } + + const segments = url.pathname.split("/").filter(Boolean); + if (!segments.length) { + return { ok: false, error: "GitHub URL is missing a username." }; + } + + candidate = segments[0]; + } catch { + return { ok: false, error: "Invalid URL format. Use a username or github.com profile URL." }; + } + } + + candidate = candidate.replace(/^@+/, "").trim(); + if (candidate.endsWith(".git")) { + candidate = candidate.slice(0, -4); + } + + const isValid = /^[A-Za-z0-9-]{1,39}$/.test(candidate) && !candidate.startsWith("-") && !candidate.endsWith("-"); + + if (!isValid) { + return { ok: false, error: "Invalid GitHub username format." }; + } + + return { ok: true, username: candidate.toLowerCase() }; +} + +export async function runAnalysisPipeline(username, token, signal) { + const profileResponse = await githubRequest(`/users/${encodeURIComponent(username)}`, { token, signal }); + const profile = profileResponse.data; + + const allRepos = await fetchAllRepositories(profile.login, token, signal); + const scorableRepos = allRepos.filter((repo) => !repo.fork); + + const deepEnrichment = await enrichTopRepositories(profile.login, scorableRepos, token, signal); + const deepMap = new Map(deepEnrichment.items.map((item) => [item.id, item])); + + const enrichedScorableRepos = scorableRepos.map((repo) => { + const extra = deepMap.get(repo.id); + return { + ...repo, + _languageBytes: extra ? extra.languageBytes : null, + _languageChecked: extra ? extra.languageChecked : false, + _hasReadme: extra ? extra.hasReadme : null, + _readmeChecked: extra ? extra.readmeChecked : false + }; + }); + + const contributions = await fetchContributionSignals(profile.login, token, signal); + const pinnedFromGraphql = token ? await fetchPinnedRepositories(profile.login, token, signal) : null; + + return buildAnalysisResult({ + profile, + scorableRepos: enrichedScorableRepos, + contributions, + partial: deepEnrichment.partial, + pinnedFromGraphql + }); +} + +async function fetchAllRepositories(username, token, signal) { + const repos = []; + + for (let page = 1; page <= MAX_REPO_PAGES; page += 1) { + const path = `/users/${encodeURIComponent(username)}/repos?type=owner&sort=updated&per_page=100&page=${page}`; + const response = await githubRequest(path, { token, signal }); + const data = Array.isArray(response.data) ? response.data : []; + + repos.push(...data); + + if (data.length < 100) { + break; + } + } + + return repos; +} + +async function enrichTopRepositories(owner, scorableRepos, token, signal) { + const candidates = [...scorableRepos] + .sort((a, b) => preRankImportanceScore(b) - preRankImportanceScore(a)) + .slice(0, MAX_DEEP_REPOS); + + const partial = { + deepRepoCount: candidates.length, + languageChecked: 0, + readmeChecked: 0, + languageFailures: 0, + readmeFailures: 0 + }; + + const items = await mapWithConcurrency(candidates, CONCURRENCY_LIMIT, async (repo) => { + return fetchRepoDeepMeta(owner, repo, token, signal, partial); + }); + + return { items, partial }; +} + +async function fetchRepoDeepMeta(owner, repo, token, signal, partial) { + const details = { + id: repo.id, + languageBytes: null, + languageChecked: false, + hasReadme: null, + readmeChecked: false + }; + + try { + const languagesResponse = await githubRequest( + `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo.name)}/languages`, + { token, signal } + ); + details.languageBytes = languagesResponse.data && typeof languagesResponse.data === "object" ? languagesResponse.data : {}; + details.languageChecked = true; + partial.languageChecked += 1; + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + partial.languageFailures += 1; + } + + try { + await githubRequest( + `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo.name)}/readme`, + { token, signal } + ); + details.hasReadme = true; + details.readmeChecked = true; + partial.readmeChecked += 1; + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + + if (error instanceof GitHubError && error.type === "NotFound") { + details.hasReadme = false; + details.readmeChecked = true; + partial.readmeChecked += 1; + } else { + partial.readmeFailures += 1; + } + } + + return details; +} + +async function fetchContributionSignals(username, token, signal) { + const [prCount, issueCount] = await Promise.all([ + fetchSearchCount(`author:${username} type:pr`, token, signal), + fetchSearchCount(`author:${username} type:issue`, token, signal) + ]); + + return { prCount, issueCount }; +} + +async function fetchSearchCount(query, token, signal) { + const path = `/search/issues?q=${encodeURIComponent(query)}&per_page=1`; + const response = await githubRequest(path, { token, signal }); + return Number(response.data && response.data.total_count) || 0; +} + +async function fetchPinnedRepositories(username, token, signal) { + const query = ` + query ($login: String!) { + user(login: $login) { + pinnedItems(first: 6, types: REPOSITORY) { + nodes { + ... on Repository { + name + url + stargazerCount + } + } + } + } + } + `; + + try { + const data = await githubGraphQL(query, { login: username }, token, signal); + const nodes = data && data.user && data.user.pinnedItems ? data.user.pinnedItems.nodes : []; + + if (!Array.isArray(nodes)) { + return null; + } + + return nodes + .filter((node) => node && node.name && node.url) + .map((node) => ({ + name: node.name, + url: node.url, + stars: Number(node.stargazerCount) || 0 + })); + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + return null; + } +} + +async function githubRequest(path, options = {}) { + const { + token = "", + method = "GET", + signal, + retry = 1, + body, + accept = "application/vnd.github+json" + } = options; + + const url = path.startsWith("http") ? path : `${GITHUB_API_ROOT}${path}`; + const cacheKey = method === "GET" && !body ? url : null; + + if (cacheKey) { + const cached = getFromCache(cacheKey); + if (cached) { + return cached; + } + } + + let attempt = 0; + while (attempt <= retry) { + attempt += 1; + + const timeout = withTimeout(signal, REQUEST_TIMEOUT_MS); + + try { + const response = await fetch(url, { + method, + signal: timeout.signal, + headers: { + Accept: accept, + "X-GitHub-Api-Version": "2022-11-28", + ...(token ? { Authorization: `Bearer ${token}` } : {}) + }, + ...(body ? { body: JSON.stringify(body) } : {}) + }); + + const rateInfo = extractRateInfo(response.headers); + const contentType = response.headers.get("content-type") || ""; + + if (!response.ok) { + let errorPayload = null; + try { + errorPayload = contentType.includes("application/json") + ? await response.json() + : { message: await response.text() }; + } catch { + errorPayload = { message: `GitHub API error (${response.status})` }; + } + + const message = + (errorPayload && typeof errorPayload.message === "string" && errorPayload.message.trim()) || + `GitHub API error (${response.status})`; + + if (response.status === 404) { + throw new GitHubError("NotFound", message, { status: response.status, rateInfo }); + } + + if (response.status === 401) { + throw new GitHubError("Unauthorized", message, { status: response.status, rateInfo }); + } + + const rateLimited = + response.status === 429 || + (response.status === 403 && (rateInfo.remaining === 0 || /rate limit/i.test(message))); + + if (rateLimited) { + throw new GitHubError("RateLimited", message, { + status: response.status, + resetAt: rateInfo.resetAt, + rateInfo + }); + } + + throw new GitHubError("Api", message, { status: response.status, rateInfo }); + } + + if (response.status === 204) { + const result = { data: null, headers: response.headers, rateInfo }; + if (cacheKey) { + setToCache(cacheKey, result); + } + return result; + } + + const data = contentType.includes("application/json") ? await response.json() : await response.text(); + const result = { data, headers: response.headers, rateInfo }; + if (cacheKey) { + setToCache(cacheKey, result); + } + + return result; + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + + if (error instanceof GitHubError) { + throw error; + } + + if (attempt > retry) { + throw new GitHubError("Network", "Failed to reach GitHub API.", { cause: error }); + } + } finally { + timeout.dispose(); + } + } + + throw new GitHubError("Network", "Failed to reach GitHub API."); +} + +async function githubGraphQL(query, variables, token, signal) { + if (!token) { + throw new GitHubError("Unauthorized", "GitHub token is required for GraphQL requests."); + } + + const timeout = withTimeout(signal, REQUEST_TIMEOUT_MS); + + let response; + try { + response = await fetch(GITHUB_GRAPHQL_ENDPOINT, { + method: "POST", + signal: timeout.signal, + headers: { + Accept: "application/vnd.github+json", + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + "X-GitHub-Api-Version": "2022-11-28" + }, + body: JSON.stringify({ query, variables }) + }); + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + throw new GitHubError("Network", "Failed to reach GitHub GraphQL API.", { cause: error }); + } finally { + timeout.dispose(); + } + + const rateInfo = extractRateInfo(response.headers); + const payload = await response.json(); + + if (!response.ok) { + if (response.status === 401) { + throw new GitHubError("Unauthorized", "GitHub token is invalid for GraphQL API.", { + status: response.status, + rateInfo + }); + } + + const message = (payload && payload.message) || `GitHub GraphQL error (${response.status})`; + throw new GitHubError("Api", message, { status: response.status, rateInfo }); + } + + if (payload.errors && payload.errors.length) { + throw new GitHubError("Api", payload.errors[0].message || "GraphQL query failed.", { + errors: payload.errors, + rateInfo + }); + } + + return payload.data; +} + +function extractRateInfo(headers) { + const remaining = Number(headers.get("x-ratelimit-remaining")); + const resetEpoch = Number(headers.get("x-ratelimit-reset")); + + return { + remaining: Number.isFinite(remaining) ? remaining : null, + resetAt: Number.isFinite(resetEpoch) && resetEpoch > 0 ? new Date(resetEpoch * 1000).toISOString() : null + }; +} + +function getFromCache(key) { + const item = githubResponseCache.get(key); + if (!item) { + return null; + } + + if (Date.now() > item.expiresAt) { + githubResponseCache.delete(key); + return null; + } + + return item.value; +} + +function setToCache(key, value) { + githubResponseCache.set(key, { + value, + expiresAt: Date.now() + GITHUB_CACHE_TTL_MS + }); + + if (githubResponseCache.size > 500) { + const oldestKey = githubResponseCache.keys().next().value; + if (oldestKey) { + githubResponseCache.delete(oldestKey); + } + } +} diff --git a/api/_lib/rate-limit.js b/api/_lib/rate-limit.js new file mode 100644 index 000000000..0e06605d2 --- /dev/null +++ b/api/_lib/rate-limit.js @@ -0,0 +1,92 @@ +import { getClientIp } from "./utils.js"; + +const memoryBuckets = new Map(); + +const DEFAULT_WINDOW_MS = Number(process.env.RATE_LIMIT_WINDOW_MS || 60_000); +const DEFAULT_MAX_REQUESTS = Number(process.env.RATE_LIMIT_MAX_REQUESTS || 30); + +export async function enforceRateLimit(req) { + const ip = getClientIp(req); + const now = Date.now(); + + const windowMs = Number.isFinite(DEFAULT_WINDOW_MS) ? DEFAULT_WINDOW_MS : 60_000; + const maxRequests = Number.isFinite(DEFAULT_MAX_REQUESTS) ? DEFAULT_MAX_REQUESTS : 30; + + const kvUrl = process.env.KV_REST_API_URL; + const kvToken = process.env.KV_REST_API_TOKEN; + + if (kvUrl && kvToken) { + try { + return await enforceKvRateLimit({ kvUrl, kvToken, ip, now, windowMs, maxRequests }); + } catch { + // fall through to memory limiter + } + } + + return enforceMemoryRateLimit({ ip, now, windowMs, maxRequests }); +} + +async function enforceKvRateLimit({ kvUrl, kvToken, ip, now, windowMs, maxRequests }) { + const windowId = Math.floor(now / windowMs); + const key = `rl:${ip}:${windowId}`; + const resetAt = new Date((windowId + 1) * windowMs).toISOString(); + + const response = await fetch(`${kvUrl}/pipeline`, { + method: "POST", + headers: { + Authorization: `Bearer ${kvToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify([ + ["INCR", key], + ["PEXPIRE", key, windowMs] + ]) + }); + + if (!response.ok) { + throw new Error("KV rate limit request failed."); + } + + const payload = await response.json(); + const results = Array.isArray(payload?.result) ? payload.result : []; + const first = results[0]; + const count = Number(first?.result ?? first ?? 0); + + return { + allowed: count <= maxRequests, + count, + maxRequests, + resetAt, + source: "kv" + }; +} + +function enforceMemoryRateLimit({ ip, now, windowMs, maxRequests }) { + const bucket = memoryBuckets.get(ip); + + if (!bucket || now >= bucket.expiresAt) { + const next = { + count: 1, + expiresAt: now + windowMs + }; + memoryBuckets.set(ip, next); + return { + allowed: true, + count: next.count, + maxRequests, + resetAt: new Date(next.expiresAt).toISOString(), + source: "memory" + }; + } + + bucket.count += 1; + memoryBuckets.set(ip, bucket); + + return { + allowed: bucket.count <= maxRequests, + count: bucket.count, + maxRequests, + resetAt: new Date(bucket.expiresAt).toISOString(), + source: "memory" + }; +} diff --git a/api/_lib/utils.js b/api/_lib/utils.js new file mode 100644 index 000000000..4e8adedbc --- /dev/null +++ b/api/_lib/utils.js @@ -0,0 +1,52 @@ +export function mapWithConcurrency(items, concurrency, worker) { + if (!items.length) { + return Promise.resolve([]); + } + + const results = new Array(items.length); + let currentIndex = 0; + + const runner = async () => { + while (currentIndex < items.length) { + const index = currentIndex; + currentIndex += 1; + results[index] = await worker(items[index], index); + } + }; + + const workers = []; + const count = Math.min(concurrency, items.length); + for (let i = 0; i < count; i += 1) { + workers.push(runner()); + } + + return Promise.all(workers).then(() => results); +} + +export function withTimeout(signal, ms) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), ms); + + if (signal) { + signal.addEventListener("abort", () => controller.abort(), { once: true }); + } + + return { + signal: controller.signal, + dispose: () => clearTimeout(timer) + }; +} + +export function getClientIp(req) { + const forwarded = req.headers["x-forwarded-for"]; + if (typeof forwarded === "string" && forwarded.trim()) { + return forwarded.split(",")[0].trim(); + } + + const realIp = req.headers["x-real-ip"]; + if (typeof realIp === "string" && realIp.trim()) { + return realIp.trim(); + } + + return "unknown"; +} diff --git a/api/analyze.js b/api/analyze.js new file mode 100644 index 000000000..b6f88538b --- /dev/null +++ b/api/analyze.js @@ -0,0 +1,213 @@ +import { enforceRateLimit } from "./_lib/rate-limit.js"; +import { GitHubError, parseProfileInput, runAnalysisPipeline } from "./_lib/github.js"; + +export const config = { + runtime: "nodejs" +}; + +const ANALYSIS_CACHE_TTL_MS = Number(process.env.ANALYSIS_CACHE_TTL_MS || 300_000); + +const analysisCache = new Map(); +const inflight = new Map(); + +export default async function handler(req, res) { + setBaseHeaders(res); + + if (req.method !== "GET") { + return sendJson(res, 405, { + ok: false, + error: { + type: "MethodNotAllowed", + message: "Only GET is supported." + } + }); + } + + const rate = await enforceRateLimit(req); + res.setHeader("X-RateLimit-Limit", String(rate.maxRequests)); + res.setHeader("X-RateLimit-Remaining", String(Math.max(0, rate.maxRequests - rate.count))); + res.setHeader("X-RateLimit-Reset-At", rate.resetAt); + + if (!rate.allowed) { + res.setHeader("Retry-After", String(Math.max(1, Math.ceil((new Date(rate.resetAt).getTime() - Date.now()) / 1000)))); + return sendJson(res, 429, { + ok: false, + error: { + type: "RateLimited", + message: "Too many requests. Please retry later.", + resetAt: rate.resetAt + } + }); + } + + const rawInput = normalizeQueryValue(req.query?.username); + const parsed = parseProfileInput(rawInput); + + if (!parsed.ok) { + return sendJson(res, 400, { + ok: false, + error: { + type: "Validation", + message: parsed.error + } + }); + } + + const username = parsed.username; + const cacheKey = username.toLowerCase(); + + const cached = getAnalysisCache(cacheKey); + if (cached) { + res.setHeader("X-Cache", "HIT"); + res.setHeader("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=600"); + return sendJson(res, 200, { ok: true, data: cached }); + } + + const running = inflight.get(cacheKey); + if (running) { + try { + const data = await running; + res.setHeader("X-Cache", "INFLIGHT"); + res.setHeader("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=600"); + return sendJson(res, 200, { ok: true, data }); + } catch (error) { + return handleError(error, res); + } + } + + const token = process.env.GITHUB_TOKEN || ""; + + const runPromise = runAnalysisPipeline(username, token) + .then((analysis) => { + setAnalysisCache(cacheKey, analysis); + return analysis; + }) + .finally(() => { + inflight.delete(cacheKey); + }); + + inflight.set(cacheKey, runPromise); + + try { + const data = await runPromise; + res.setHeader("X-Cache", "MISS"); + res.setHeader("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=600"); + return sendJson(res, 200, { ok: true, data }); + } catch (error) { + return handleError(error, res); + } +} + +function handleError(error, res) { + if (error instanceof GitHubError) { + if (error.type === "NotFound") { + return sendJson(res, 404, { + ok: false, + error: { + type: "NotFound", + message: "GitHub profile not found." + } + }); + } + + if (error.type === "RateLimited") { + const resetAt = error.details?.resetAt || null; + if (resetAt) { + res.setHeader("X-RateLimit-Reset-At", resetAt); + } + + return sendJson(res, 429, { + ok: false, + error: { + type: "RateLimited", + message: "GitHub upstream API rate limit reached. Retry shortly.", + resetAt + } + }); + } + + if (error.type === "Unauthorized") { + return sendJson(res, 503, { + ok: false, + error: { + type: "Unauthorized", + message: "Server GitHub token is invalid or missing in deployment configuration." + } + }); + } + + if (error.type === "Network") { + return sendJson(res, 502, { + ok: false, + error: { + type: "Network", + message: "Network error while fetching GitHub data." + } + }); + } + + return sendJson(res, 502, { + ok: false, + error: { + type: "Upstream", + message: error.message || "GitHub API request failed." + } + }); + } + + return sendJson(res, 500, { + ok: false, + error: { + type: "Internal", + message: "Unexpected server error while analyzing profile." + } + }); +} + +function setBaseHeaders(res) { + res.setHeader("Content-Type", "application/json; charset=utf-8"); + res.setHeader("X-Content-Type-Options", "nosniff"); + res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); +} + +function sendJson(res, status, body) { + res.status(status).json(body); +} + +function normalizeQueryValue(value) { + if (Array.isArray(value)) { + return value[0] || ""; + } + if (typeof value === "string") { + return value; + } + return ""; +} + +function getAnalysisCache(key) { + const hit = analysisCache.get(key); + if (!hit) { + return null; + } + + if (Date.now() > hit.expiresAt) { + analysisCache.delete(key); + return null; + } + + return hit.value; +} + +function setAnalysisCache(key, value) { + analysisCache.set(key, { + value, + expiresAt: Date.now() + ANALYSIS_CACHE_TTL_MS + }); + + if (analysisCache.size > 300) { + const oldestKey = analysisCache.keys().next().value; + if (oldestKey) { + analysisCache.delete(oldestKey); + } + } +} diff --git a/index.html b/index.html index 3834c591a..1f1e5fff2 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,15 @@ name="description" content="DevDetective analyzes GitHub portfolios with recruiter-style scoring, strengths, red flags, and actionable suggestions." /> + + + + + + + + + DevDetective | GitHub Portfolio Analyzer @@ -18,7 +27,7 @@ - + + diff --git a/manifest.json b/manifest.json index 768bdc1f8..71557ed93 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "name": "DevDetective GitHub Portfolio Analyzer", "short_name": "DevDetective", "description": "Recruiter-style GitHub portfolio analyzer with weighted scoring, insights, and actionable suggestions.", - "start_url": "./index.html", + "start_url": "/", "display": "standalone", "background_color": "#f3f7ff", "theme_color": "#1f6feb", diff --git a/package.json b/package.json new file mode 100644 index 000000000..592a5f428 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "dev-detective-vercel", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vercel dev", + "start": "python3 -m http.server 5500" + } +} diff --git a/script.js b/script.js deleted file mode 100644 index 2cb6959fd..000000000 --- a/script.js +++ /dev/null @@ -1,2562 +0,0 @@ -const GITHUB_API_ROOT = "https://api.github.com"; -const GITHUB_GRAPHQL_ENDPOINT = "https://api.github.com/graphql"; - -const MAX_REPO_PAGES = 3; -const MAX_DEEP_REPOS = 30; -const CONCURRENCY_LIMIT = 5; -const CACHE_TTL_MS = 10 * 60 * 1000; -const HISTORY_LIMIT = 8; - -const STORAGE_KEYS = Object.freeze({ - history: "devdetective_history", - theme: "devdetective_theme", - token: "devdetective_token" -}); - -const CACHE_PREFIX = "devdetective_cache_v2_"; - -const WEIGHTS = Object.freeze({ - documentationQuality: 18, - codeActivityConsistency: 17, - projectPopularity: 15, - repositoryCompleteness: 14, - languageDiversity: 10, - recentActivity: 14, - impactSignals: 12 -}); - -const SUBSCORE_ID_MAP = Object.freeze({ - documentationQuality: "subDocumentationQuality", - codeActivityConsistency: "subCodeActivityConsistency", - projectPopularity: "subProjectPopularity", - repositoryCompleteness: "subRepositoryCompleteness", - languageDiversity: "subLanguageDiversity", - recentActivity: "subRecentActivity", - impactSignals: "subImpactSignals" -}); - -const SCORING_EXPLAIN_ID_MAP = Object.freeze({ - documentationQuality: "explainDocumentationQuality", - codeActivityConsistency: "explainCodeActivityConsistency", - projectPopularity: "explainProjectPopularity", - repositoryCompleteness: "explainRepositoryCompleteness", - languageDiversity: "explainLanguageDiversity", - recentActivity: "explainRecentActivity", - impactSignals: "explainImpactSignals" -}); - -const state = { - currentUsername: "", - token: "", - analysisResult: null, - abortController: null, - charts: { - language: null, - importance: null, - subscoreRadar: null, - activity: null - }, - history: readJsonStorage(STORAGE_KEYS.history, []) -}; - -const ui = { - profileInput: document.getElementById("profileInput"), - tokenInput: document.getElementById("tokenInput"), - analyzeBtn: document.getElementById("analyzeBtn"), - themeToggleBtn: document.getElementById("themeToggleBtn"), - downloadReportBtn: document.getElementById("downloadReportBtn"), - - loadingState: document.getElementById("loadingState"), - loadingText: document.getElementById("loadingText"), - errorBanner: document.getElementById("errorBanner"), - rateLimitBanner: document.getElementById("rateLimitBanner"), - - avatarImg: document.getElementById("avatarImg"), - profileName: document.getElementById("profileName"), - profileHandle: document.getElementById("profileHandle"), - profileLink: document.getElementById("profileLink"), - profileBio: document.getElementById("profileBio"), - - statRepos: document.getElementById("statRepos"), - statFollowers: document.getElementById("statFollowers"), - statFollowing: document.getElementById("statFollowing"), - statLastPush: document.getElementById("statLastPush"), - statPrCount: document.getElementById("statPrCount"), - statIssueCount: document.getElementById("statIssueCount"), - - pinnedSourceBadge: document.getElementById("pinnedSourceBadge"), - pinnedReposList: document.getElementById("pinnedReposList"), - historyList: document.getElementById("historyList"), - - overallScoreRing: document.getElementById("overallScoreRing"), - overallScore: document.getElementById("overallScore"), - scoreGrade: document.getElementById("scoreGrade"), - scoreSummary: document.getElementById("scoreSummary"), - hireabilityScore: document.getElementById("hireabilityScore"), - hireabilityHint: document.getElementById("hireabilityHint"), - readinessLevel: document.getElementById("readinessLevel"), - readinessHint: document.getElementById("readinessHint"), - readinessBar: document.getElementById("readinessBar"), - - strengthsList: document.getElementById("strengthsList"), - redFlagsList: document.getElementById("redFlagsList"), - suggestionsList: document.getElementById("suggestionsList"), - hiddenRisksList: document.getElementById("hiddenRisksList"), - aiRecruiterVerdict: document.getElementById("aiRecruiterVerdict"), - aiRecruiterSummary: document.getElementById("aiRecruiterSummary"), - aiRecruiterSignals: document.getElementById("aiRecruiterSignals"), - careerPathTitle: document.getElementById("careerPathTitle"), - careerPathSummary: document.getElementById("careerPathSummary"), - careerConfidence: document.getElementById("careerConfidence"), - careerSkillsList: document.getElementById("careerSkillsList"), - roadmapList: document.getElementById("roadmapList"), - - repoRankingTable: document.getElementById("repoRankingTable"), - - languageChart: document.getElementById("languageChart"), - importanceChart: document.getElementById("importanceChart"), - subscoreRadarChart: document.getElementById("subscoreRadarChart"), - activityChart: document.getElementById("activityChart") -}; - -class GitHubError extends Error { - constructor(type, message, details = {}) { - super(message); - this.name = "GitHubError"; - this.type = type; - this.details = details; - } -} - -init(); - -function init() { - bindEvents(); - - state.token = localStorage.getItem(STORAGE_KEYS.token) || ""; - ui.tokenInput.value = state.token; - - const savedTheme = localStorage.getItem(STORAGE_KEYS.theme) || "light"; - applyTheme(savedTheme); - - renderHistory(); - renderPinnedRepos({ source: "fallback", items: [] }); - - renderInsightList(ui.strengthsList, ["No profile analyzed yet."], "neutral"); - renderInsightList(ui.redFlagsList, ["No profile analyzed yet."], "neutral"); - renderInsightList(ui.suggestionsList, ["No profile analyzed yet."], "neutral"); - renderInsightList(ui.hiddenRisksList, ["No hidden risk analysis yet."], "neutral"); - renderInsightList(ui.aiRecruiterSignals, ["Run an analysis to simulate recruiter feedback."], "neutral"); - renderRoadmap(["Roadmap will appear after analysis."]); - renderCareerPath({ - title: "Run an analysis to generate role fit", - confidence: 0, - summary: "Career path recommendations are generated from your public GitHub portfolio signals.", - nextSkills: ["Add repositories and analyze profile to unlock recommendations."] - }); - ui.aiRecruiterVerdict.textContent = "Pending"; - ui.aiRecruiterVerdict.className = "chip chip-neutral"; - ui.aiRecruiterSummary.textContent = "Run an analysis to get recruiter-style interview feedback simulation."; - ui.hireabilityScore.textContent = "--"; - ui.hireabilityHint.textContent = "Weighted recruiter-fit index"; - ui.readinessLevel.textContent = "--"; - ui.readinessHint.textContent = "Analyze a profile to classify readiness"; - ui.readinessBar.style.width = "0%"; - - if (state.history[0]) { - ui.profileInput.value = state.history[0]; - } -} - -function bindEvents() { - ui.analyzeBtn.addEventListener("click", handleAnalyze); - - ui.profileInput.addEventListener("keydown", (event) => { - if (event.key === "Enter") { - handleAnalyze(); - } - }); - - ui.themeToggleBtn.addEventListener("click", () => { - const currentTheme = document.documentElement.getAttribute("data-theme") || "light"; - const nextTheme = currentTheme === "dark" ? "light" : "dark"; - applyTheme(nextTheme); - - if (state.analysisResult) { - renderCharts(state.analysisResult); - } - }); - - ui.downloadReportBtn.addEventListener("click", downloadMarkdownReport); - - const persistToken = () => { - state.token = ui.tokenInput.value.trim(); - if (state.token) { - localStorage.setItem(STORAGE_KEYS.token, state.token); - } else { - localStorage.removeItem(STORAGE_KEYS.token); - } - }; - - ui.tokenInput.addEventListener("change", persistToken); - ui.tokenInput.addEventListener("blur", persistToken); -} - -async function handleAnalyze() { - clearBanners(); - - const parsed = parseProfileInput(ui.profileInput.value); - if (!parsed.ok) { - showError(parsed.error); - return; - } - - const username = parsed.username; - state.currentUsername = username; - ui.profileInput.value = username; - - state.token = ui.tokenInput.value.trim(); - if (state.token) { - localStorage.setItem(STORAGE_KEYS.token, state.token); - } else { - localStorage.removeItem(STORAGE_KEYS.token); - } - - if (state.abortController) { - state.abortController.abort(); - } - - const controller = new AbortController(); - state.abortController = controller; - - setLoading(true, "Preparing analysis..."); - - try { - let analysis = getCachedAnalysis(username, Boolean(state.token)); - - if (analysis) { - setLoading(true, "Loaded cached analysis from the last 10 minutes..."); - } else { - analysis = await runAnalysisPipeline(username, state.token, controller.signal); - setCachedAnalysis(username, Boolean(state.token), analysis); - } - - if (controller.signal.aborted) { - return; - } - - state.analysisResult = analysis; - ui.downloadReportBtn.disabled = false; - - saveHistory(username); - renderHistory(); - renderAnalysis(analysis); - } catch (error) { - if (error.name === "AbortError") { - return; - } - - if (error instanceof GitHubError && error.type === "Unauthorized" && state.token) { - showError("Token appears invalid. Retrying once without token."); - try { - const fallbackAnalysis = await runAnalysisPipeline(username, "", controller.signal); - if (controller.signal.aborted) { - return; - } - - state.analysisResult = fallbackAnalysis; - ui.downloadReportBtn.disabled = false; - - saveHistory(username); - renderHistory(); - renderAnalysis(fallbackAnalysis); - } catch (fallbackError) { - if (fallbackError.name !== "AbortError") { - handleAnalysisError(fallbackError); - } - } - } else { - handleAnalysisError(error); - } - } finally { - if (state.abortController === controller) { - state.abortController = null; - } - setLoading(false); - } -} - -function handleAnalysisError(error) { - if (!(error instanceof GitHubError)) { - showError("Unexpected error while analyzing the profile."); - return; - } - - if (error.type === "NotFound") { - showError("Profile not found. Enter a valid GitHub username or GitHub profile URL."); - return; - } - - if (error.type === "RateLimited") { - showError("GitHub API rate limit reached."); - showRateLimit(error.details.resetAt || null); - return; - } - - if (error.type === "Unauthorized") { - showError("GitHub token is invalid or expired. Update or clear the token and retry."); - return; - } - - if (error.type === "Network") { - showError("Network issue while contacting GitHub. Please retry."); - return; - } - - showError(error.message || "GitHub API request failed."); -} - -function parseProfileInput(rawInput) { - const value = (rawInput || "").trim(); - - if (!value) { - return { ok: false, error: "Enter a GitHub username or profile URL to analyze." }; - } - - let candidate = value; - - if (/github\.com\//i.test(candidate) && !/^https?:\/\//i.test(candidate)) { - candidate = `https://${candidate}`; - } - - if (/^https?:\/\//i.test(candidate)) { - try { - const url = new URL(candidate); - const host = url.hostname.toLowerCase(); - if (host !== "github.com" && host !== "www.github.com") { - return { ok: false, error: "URL must be a valid github.com profile URL." }; - } - - const segments = url.pathname.split("/").filter(Boolean); - if (!segments.length) { - return { ok: false, error: "GitHub URL is missing a username." }; - } - - candidate = segments[0]; - } catch { - return { ok: false, error: "Invalid URL format. Use a username or a github.com profile URL." }; - } - } - - candidate = candidate.replace(/^@+/, "").trim(); - - if (candidate.endsWith(".git")) { - candidate = candidate.slice(0, -4); - } - - const isValid = /^[A-Za-z0-9-]{1,39}$/.test(candidate) && !candidate.startsWith("-") && !candidate.endsWith("-"); - - if (!isValid) { - return { ok: false, error: "Invalid GitHub username format." }; - } - - return { ok: true, username: candidate.toLowerCase() }; -} - -async function runAnalysisPipeline(username, token, signal) { - setLoading(true, "Fetching profile..."); - const profileResponse = await githubRequest(`/users/${encodeURIComponent(username)}`, { token, signal }); - const profile = profileResponse.data; - - setLoading(true, "Fetching public repositories..."); - const allRepos = await fetchAllRepositories(profile.login, token, signal); - const scorableRepos = allRepos.filter((repo) => !repo.fork); - - setLoading(true, "Collecting repository language and README data..."); - const deepEnrichment = await enrichTopRepositories(profile.login, scorableRepos, token, signal); - const deepMap = new Map(deepEnrichment.items.map((item) => [item.id, item])); - - const enrichedScorableRepos = scorableRepos.map((repo) => { - const extra = deepMap.get(repo.id); - return { - ...repo, - _languageBytes: extra ? extra.languageBytes : null, - _languageChecked: extra ? extra.languageChecked : false, - _hasReadme: extra ? extra.hasReadme : null, - _readmeChecked: extra ? extra.readmeChecked : false - }; - }); - - setLoading(true, "Fetching issue and PR contribution counts..."); - const contributions = await fetchContributionSignals(profile.login, token, signal); - - setLoading(true, "Checking pinned repositories..."); - const pinnedFromGraphql = token ? await fetchPinnedRepositories(profile.login, token, signal) : null; - - setLoading(true, "Calculating recruiter score and insights..."); - return buildAnalysisResult({ - profile, - scorableRepos: enrichedScorableRepos, - contributions, - partial: deepEnrichment.partial, - pinnedFromGraphql - }); -} - -async function fetchAllRepositories(username, token, signal) { - const repos = []; - - for (let page = 1; page <= MAX_REPO_PAGES; page += 1) { - const path = `/users/${encodeURIComponent(username)}/repos?type=owner&sort=updated&per_page=100&page=${page}`; - const response = await githubRequest(path, { token, signal }); - const data = Array.isArray(response.data) ? response.data : []; - - repos.push(...data); - - if (data.length < 100) { - break; - } - } - - return repos; -} - -async function enrichTopRepositories(owner, scorableRepos, token, signal) { - const candidates = [...scorableRepos] - .sort((a, b) => preRankImportanceScore(b) - preRankImportanceScore(a)) - .slice(0, MAX_DEEP_REPOS); - - const partial = { - deepRepoCount: candidates.length, - languageChecked: 0, - readmeChecked: 0, - languageFailures: 0, - readmeFailures: 0 - }; - - const items = await mapWithConcurrency(candidates, CONCURRENCY_LIMIT, async (repo) => { - return fetchRepoDeepMeta(owner, repo, token, signal, partial); - }); - - return { items, partial }; -} - -async function fetchRepoDeepMeta(owner, repo, token, signal, partial) { - const details = { - id: repo.id, - languageBytes: null, - languageChecked: false, - hasReadme: null, - readmeChecked: false - }; - - try { - const languagesResponse = await githubRequest( - `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo.name)}/languages`, - { token, signal } - ); - details.languageBytes = languagesResponse.data && typeof languagesResponse.data === "object" ? languagesResponse.data : {}; - details.languageChecked = true; - partial.languageChecked += 1; - } catch (error) { - if (error.name === "AbortError") { - throw error; - } - partial.languageFailures += 1; - } - - try { - await githubRequest( - `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo.name)}/readme`, - { token, signal } - ); - details.hasReadme = true; - details.readmeChecked = true; - partial.readmeChecked += 1; - } catch (error) { - if (error.name === "AbortError") { - throw error; - } - - if (error instanceof GitHubError && error.type === "NotFound") { - details.hasReadme = false; - details.readmeChecked = true; - partial.readmeChecked += 1; - } else { - partial.readmeFailures += 1; - } - } - - return details; -} - -async function fetchContributionSignals(username, token, signal) { - const [prCount, issueCount] = await Promise.all([ - fetchSearchCount(`author:${username} type:pr`, token, signal), - fetchSearchCount(`author:${username} type:issue`, token, signal) - ]); - - return { prCount, issueCount }; -} - -async function fetchSearchCount(query, token, signal) { - const path = `/search/issues?q=${encodeURIComponent(query)}&per_page=1`; - const response = await githubRequest(path, { token, signal }); - return Number(response.data && response.data.total_count) || 0; -} - -async function fetchPinnedRepositories(username, token, signal) { - const query = ` - query ($login: String!) { - user(login: $login) { - pinnedItems(first: 6, types: REPOSITORY) { - nodes { - ... on Repository { - name - url - stargazerCount - } - } - } - } - } - `; - - try { - const data = await githubGraphQL(query, { login: username }, token, signal); - const nodes = data && data.user && data.user.pinnedItems ? data.user.pinnedItems.nodes : []; - - if (!Array.isArray(nodes)) { - return null; - } - - return nodes - .filter((node) => node && node.name && node.url) - .map((node) => ({ - name: node.name, - url: node.url, - stars: Number(node.stargazerCount) || 0 - })); - } catch (error) { - if (error.name === "AbortError") { - throw error; - } - return null; - } -} - -function buildAnalysisResult({ profile, scorableRepos, contributions, partial, pinnedFromGraphql }) { - const now = Date.now(); - const scorableCount = scorableRepos.length; - - const totalStars = scorableRepos.reduce((sum, repo) => sum + (Number(repo.stargazers_count) || 0), 0); - const totalForks = scorableRepos.reduce((sum, repo) => sum + (Number(repo.forks_count) || 0), 0); - const totalWatchers = scorableRepos.reduce((sum, repo) => sum + (Number(repo.watchers_count) || 0), 0); - - const descriptionCoverage = safeRatio( - scorableRepos.filter((repo) => Boolean((repo.description || "").trim())).length, - scorableCount - ); - - const readmeScannedRepos = scorableRepos.filter((repo) => repo._readmeChecked); - const readmeCoverage = safeRatio( - readmeScannedRepos.filter((repo) => repo._hasReadme === true).length, - readmeScannedRepos.length - ); - - const nonEmptyRepoRatio = safeRatio( - scorableRepos.filter((repo) => (Number(repo.size) || 0) > 0).length, - scorableCount - ); - - const homepageRatio = safeRatio( - scorableRepos.filter((repo) => Boolean((repo.homepage || "").trim())).length, - scorableCount - ); - - const topicsRatio = safeRatio( - scorableRepos.filter((repo) => Array.isArray(repo.topics) && repo.topics.length > 0).length, - scorableCount - ); - - const pushedDays = scorableRepos - .map((repo) => daysSinceDate(repo.pushed_at, now)) - .filter((days) => Number.isFinite(days)); - - const reposUpdated30d = pushedDays.filter((days) => days <= 30).length; - const reposUpdated90d = pushedDays.filter((days) => days <= 90).length; - const reposInactive180d = pushedDays.filter((days) => days > 180).length; - - const activityBuckets = { - updated30d: pushedDays.filter((days) => days <= 30).length, - updated31to90d: pushedDays.filter((days) => days > 30 && days <= 90).length, - updated91to180d: pushedDays.filter((days) => days > 90 && days <= 180).length, - updated181plus: pushedDays.filter((days) => days > 180).length - }; - - const lastPushRepo = [...scorableRepos] - .filter((repo) => repo.pushed_at) - .sort((a, b) => new Date(b.pushed_at).getTime() - new Date(a.pushed_at).getTime())[0] || null; - - const daysSinceLastPush = lastPushRepo ? daysSinceDate(lastPushRepo.pushed_at, now) : 9999; - - const activity = computeActivityInLastSixMonths(scorableRepos); - const activeMonthsLast6Ratio = activity.ratio; - - const starsPerRepo = scorableCount ? totalStars / scorableCount : 0; - const forksPerRepo = scorableCount ? totalForks / scorableCount : 0; - const watchersPerRepo = scorableCount ? totalWatchers / scorableCount : 0; - - const languageTotals = aggregateLanguageTotals(scorableRepos); - const languageEntries = Object.entries(languageTotals).sort((a, b) => b[1] - a[1]); - const uniqueLanguages = languageEntries.length; - const normalizedShannonEntropy = computeNormalizedEntropy(languageTotals); - const totalLanguageBytes = languageEntries.reduce((sum, [, bytes]) => sum + (Number(bytes) || 0), 0); - const dominantLanguage = languageEntries[0] ? languageEntries[0][0] : "Unknown"; - const dominantLanguageShare = - totalLanguageBytes > 0 && languageEntries[0] ? cap01((Number(languageEntries[0][1]) || 0) / totalLanguageBytes) : 0; - const topLanguages = languageEntries.slice(0, 5).map(([language]) => language); - - const topRepoByStars = [...scorableRepos].sort( - (a, b) => (Number(b.stargazers_count) || 0) - (Number(a.stargazers_count) || 0) - )[0] || null; - - const topRepoStars = topRepoByStars ? Number(topRepoByStars.stargazers_count) || 0 : 0; - - const reposUpdated90dRatio = safeRatio(reposUpdated90d, scorableCount); - const reposUpdated30dRatio = safeRatio(reposUpdated30d, scorableCount); - - const recencyBucket = getRecencyBucket(daysSinceLastPush); - - const subscores = { - documentationQuality: scoreFromRatio(0.75 * readmeCoverage + 0.25 * descriptionCoverage), - codeActivityConsistency: scoreFromRatio(0.6 * activeMonthsLast6Ratio + 0.4 * reposUpdated90dRatio), - projectPopularity: scoreFromRatio( - 0.6 * cap01(starsPerRepo / 50) + - 0.25 * cap01(forksPerRepo / 20) + - 0.15 * cap01(watchersPerRepo / 20) - ), - repositoryCompleteness: scoreFromRatio( - 0.5 * nonEmptyRepoRatio + - 0.3 * homepageRatio + - 0.2 * topicsRatio - ), - languageDiversity: scoreFromRatio( - 0.7 * cap01(uniqueLanguages / 8) + - 0.3 * normalizedShannonEntropy - ), - recentActivity: scoreFromRatio(0.7 * recencyBucket + 0.3 * reposUpdated30dRatio), - impactSignals: scoreFromRatio( - 0.35 * cap01(topRepoStars / 300) + - 0.25 * cap01((Number(profile.followers) || 0) / 500) + - 0.25 * cap01(contributions.prCount / 200) + - 0.15 * cap01(contributions.issueCount / 100) - ) - }; - - const overallScore = clampToRange( - Math.round( - (subscores.documentationQuality * WEIGHTS.documentationQuality + - subscores.codeActivityConsistency * WEIGHTS.codeActivityConsistency + - subscores.projectPopularity * WEIGHTS.projectPopularity + - subscores.repositoryCompleteness * WEIGHTS.repositoryCompleteness + - subscores.languageDiversity * WEIGHTS.languageDiversity + - subscores.recentActivity * WEIGHTS.recentActivity + - subscores.impactSignals * WEIGHTS.impactSignals) / 100 - ), - 0, - 100 - ); - - const rankedRepos = buildRankedRepositories(scorableRepos); - - const pinnedRepos = pinnedFromGraphql && pinnedFromGraphql.length - ? { - source: "graphql", - items: pinnedFromGraphql - } - : { - source: "fallback", - items: rankedRepos.slice(0, 6).map((repo) => ({ - name: repo.name, - url: repo.url, - stars: repo.stars - })) - }; - - const metrics = { - scorableRepoCount: scorableCount, - totalStars, - totalForks, - totalWatchers, - readmeCoverage, - reposUpdated30d, - reposUpdated90d, - daysSinceLastPush, - authoredPRCount: contributions.prCount, - authoredIssueCount: contributions.issueCount, - uniqueLanguages, - topLanguages, - dominantLanguage, - dominantLanguageShare, - - descriptionCoverage, - descriptionlessRatio: cap01(1 - descriptionCoverage), - activeMonthsLast6: activity.activeMonths, - activeMonthsLast6Ratio, - reposInactive180d, - reposInactive180dRatio: safeRatio(reposInactive180d, scorableCount), - emptyRepoRatio: cap01(1 - nonEmptyRepoRatio), - nonEmptyRepoRatio, - homepageRatio, - topicsRatio, - starsPerRepo, - forksPerRepo, - watchersPerRepo, - reposUpdated30dRatio, - reposUpdated90dRatio, - recencyBucket, - topRepoStars, - normalizedShannonEntropy, - readmeSampleSize: readmeScannedRepos.length, - lastPushDate: lastPushRepo ? lastPushRepo.pushed_at : null, - partialReadmeFailures: partial.readmeFailures, - partialLanguageFailures: partial.languageFailures, - deepRepoCount: partial.deepRepoCount, - activityBuckets - }; - - const strengths = buildStrengths(subscores, metrics, rankedRepos); - const redFlags = buildRedFlags(subscores, metrics); - const suggestions = buildSuggestions(subscores, metrics, rankedRepos, pinnedRepos); - const hiddenRisks = buildHiddenRisks(subscores, metrics, rankedRepos); - const hireabilityScore = calculateHireabilityScore(subscores, overallScore, hiddenRisks); - const readiness = classifyReadiness(hireabilityScore, overallScore); - const careerPath = buildCareerPathRecommendation(metrics, rankedRepos, subscores); - const improvementRoadmap = buildImprovementRoadmap(subscores, metrics, rankedRepos, careerPath, hiddenRisks); - const recruiterSimulation = buildRecruiterSimulation({ - overallScore, - hireabilityScore, - readiness, - subscores, - metrics, - strengths, - redFlags, - hiddenRisks, - rankedRepos - }); - - const grade = scoreToGrade(overallScore); - const scoreSummary = buildScoreSummary(overallScore, grade, subscores, metrics, hireabilityScore, readiness.label); - - return { - profile: { - login: profile.login, - name: profile.name || profile.login, - htmlUrl: profile.html_url, - followers: Number(profile.followers) || 0, - following: Number(profile.following) || 0, - publicRepos: Number(profile.public_repos) || 0, - avatarUrl: profile.avatar_url, - bio: profile.bio || "No bio provided.", - createdAt: profile.created_at, - updatedAt: profile.updated_at - }, - generatedAt: new Date().toISOString(), - weights: WEIGHTS, - subscores, - overallScore, - hireabilityScore, - readiness, - readinessLevel: readiness.label, - metrics, - strengths, - redFlags, - suggestions, - hiddenRisks, - recruiterSimulation, - careerPath, - improvementRoadmap, - pinnedRepos, - rankedRepos: rankedRepos.map((repo) => ({ - name: repo.name, - url: repo.url, - importance: repo.importance, - stars: repo.stars, - forks: repo.forks, - watchers: repo.watchers, - pushedAt: repo.pushedAt, - hasReadme: repo.hasReadme, - language: repo.language, - homepage: repo.homepage, - topicsCount: repo.topicsCount, - hasDescription: repo.hasDescription, - isEmpty: repo.isEmpty, - readmeKnown: repo.readmeKnown - })), - languageTotals, - grade, - scoreSummary - }; -} - -function aggregateLanguageTotals(repos) { - const totals = {}; - - repos.forEach((repo) => { - let hasDetailedBreakdown = false; - - if (repo._languageBytes && typeof repo._languageBytes === "object") { - const entries = Object.entries(repo._languageBytes); - if (entries.length) { - entries.forEach(([language, bytes]) => { - totals[language] = (totals[language] || 0) + (Number(bytes) || 0); - }); - hasDetailedBreakdown = true; - } - } - - if (!hasDetailedBreakdown && repo.language) { - totals[repo.language] = (totals[repo.language] || 0) + 1000; - } - }); - - return totals; -} - -function computeNormalizedEntropy(totals) { - const values = Object.values(totals).filter((value) => value > 0); - const total = values.reduce((sum, value) => sum + value, 0); - - if (total <= 0 || values.length <= 1) { - return 0; - } - - const entropy = values.reduce((sum, value) => { - const p = value / total; - return sum - p * Math.log2(p); - }, 0); - - const maxEntropy = Math.log2(values.length); - return maxEntropy > 0 ? entropy / maxEntropy : 0; -} - -function buildRankedRepositories(repos) { - if (!repos.length) { - return []; - } - - const ranked = repos.map((repo) => { - const ageDays = daysSinceDate(repo.pushed_at); - - const recencyBoost = ageDays <= 30 ? 15 : ageDays <= 90 ? 8 : ageDays <= 180 ? 4 : 0; - const readmeBoost = repo._hasReadme === true ? 8 : 0; - const homepageBoost = (repo.homepage || "").trim() ? 4 : 0; - const topicsBoost = Array.isArray(repo.topics) && repo.topics.length > 0 ? 3 : 0; - const descriptionBoost = (repo.description || "").trim() ? 3 : 0; - const sizeBoost = (Number(repo.size) || 0) > 0 ? 2 : 0; - - const rawImportance = - (Number(repo.stargazers_count) || 0) * 4 + - (Number(repo.forks_count) || 0) * 3 + - (Number(repo.watchers_count) || 0) * 2 + - recencyBoost + - readmeBoost + - homepageBoost + - topicsBoost + - descriptionBoost + - sizeBoost; - - return { - name: repo.name, - url: repo.html_url, - rawImportance, - stars: Number(repo.stargazers_count) || 0, - forks: Number(repo.forks_count) || 0, - watchers: Number(repo.watchers_count) || 0, - pushedAt: repo.pushed_at, - hasReadme: repo._hasReadme === true, - readmeKnown: Boolean(repo._readmeChecked), - language: repo.language || "Unknown", - homepage: (repo.homepage || "").trim(), - topicsCount: Array.isArray(repo.topics) ? repo.topics.length : 0, - hasDescription: Boolean((repo.description || "").trim()), - isEmpty: (Number(repo.size) || 0) === 0 - }; - }); - - const maxRaw = Math.max(...ranked.map((repo) => repo.rawImportance), 1); - - ranked.forEach((repo) => { - repo.importance = clampToRange(Math.round((repo.rawImportance / maxRaw) * 100), 0, 100); - }); - - return ranked.sort((a, b) => b.importance - a.importance || b.stars - a.stars); -} - -function buildStrengths(subscores, metrics, rankedRepos) { - const strengths = []; - - if (subscores.codeActivityConsistency >= 70) { - strengths.push( - `Strong activity consistency with ${metrics.activeMonthsLast6}/6 active months and ${metrics.reposUpdated90d} repositories updated in the last 90 days.` - ); - } - - if (subscores.projectPopularity >= 70) { - const top = rankedRepos[0]; - strengths.push( - `Good popularity signals: ${metrics.totalStars} total stars and ${top ? `${top.name} as a leading project` : "multiple visible projects"}.` - ); - } - - if (subscores.languageDiversity >= 70) { - strengths.push(`Diverse technical stack with ${metrics.uniqueLanguages} detected languages.`); - } - - if (subscores.recentActivity >= 70) { - strengths.push(`Recent contribution momentum: latest push was ${metrics.daysSinceLastPush} day(s) ago.`); - } - - if (subscores.documentationQuality >= 70) { - strengths.push( - `Documentation quality is strong with ${(metrics.readmeCoverage * 100).toFixed(0)}% README coverage in sampled repositories.` - ); - } - - if (subscores.impactSignals >= 70) { - strengths.push( - `Impact signals are healthy with ${metrics.authoredPRCount} authored PRs and ${metrics.authoredIssueCount} authored issues.` - ); - } - - if (!strengths.length) { - strengths.push( - `Public portfolio is visible (${metrics.scorableRepoCount} non-fork repositories) but still needs stronger recruiter-facing signals.` - ); - } - - return strengths.slice(0, 6); -} - -function buildRedFlags(subscores, metrics) { - const redFlags = []; - - if (metrics.readmeCoverage < 0.5) { - redFlags.push( - `Low README coverage (${(metrics.readmeCoverage * 100).toFixed(0)}%) in sampled repositories makes project intent harder to evaluate.` - ); - } - - if (metrics.reposInactive180dRatio > 0.5) { - redFlags.push( - `${metrics.reposInactive180d} of ${metrics.scorableRepoCount} repositories have been inactive for more than 180 days.` - ); - } - - if (metrics.emptyRepoRatio > 0.3) { - redFlags.push( - `${(metrics.emptyRepoRatio * 100).toFixed(0)}% of repositories look empty or near-empty based on repository size.` - ); - } - - if (metrics.descriptionlessRatio > 0.4) { - redFlags.push( - `${(metrics.descriptionlessRatio * 100).toFixed(0)}% of repositories have missing descriptions, which weakens recruiter readability.` - ); - } - - if (metrics.daysSinceLastPush > 90) { - redFlags.push(`No recent pushes in the last 90 days (latest push was ${metrics.daysSinceLastPush} day(s) ago).`); - } - - if (subscores.impactSignals < 40) { - redFlags.push( - `Impact signals are weak (${subscores.impactSignals}/100) due to low follower, PR, issue, or top-repo traction.` - ); - } - - if (!redFlags.length) { - redFlags.push("No major red flags detected from the available public signals."); - } - - return redFlags.slice(0, 6); -} - -function buildSuggestions(subscores, metrics, rankedRepos, pinnedRepos) { - const suggestions = []; - - const missingReadmeRepos = rankedRepos - .filter((repo) => repo.readmeKnown && !repo.hasReadme) - .slice(0, 3) - .map((repo) => repo.name); - - const staleRepos = rankedRepos - .filter((repo) => daysSinceDate(repo.pushedAt) > 180) - .slice(0, 3) - .map((repo) => repo.name); - - const noHomepageRepos = rankedRepos - .filter((repo) => !repo.homepage) - .slice(0, 3) - .map((repo) => repo.name); - - const emptyRepos = rankedRepos - .filter((repo) => repo.isEmpty) - .slice(0, 3) - .map((repo) => repo.name); - - const missingDescriptionRepos = rankedRepos - .filter((repo) => !repo.hasDescription) - .slice(0, 3) - .map((repo) => repo.name); - - if (missingReadmeRepos.length) { - suggestions.push({ - priority: 120 - subscores.documentationQuality, - text: `Add README files to ${joinRepoNames(missingReadmeRepos)} with problem statement, setup, usage, and outcomes.` - }); - } - - if (staleRepos.length) { - suggestions.push({ - priority: 120 - subscores.recentActivity, - text: `Update or archive stale repositories (${joinRepoNames(staleRepos)}) so recruiters see a maintained portfolio.` - }); - } - - if (noHomepageRepos.length) { - suggestions.push({ - priority: 110 - subscores.repositoryCompleteness, - text: `Add live demo or homepage links for ${joinRepoNames(noHomepageRepos)} to improve project completeness.` - }); - } - - if (emptyRepos.length) { - suggestions.push({ - priority: 108 - subscores.repositoryCompleteness, - text: `Complete or archive near-empty repositories (${joinRepoNames(emptyRepos)}) to reduce noise in your public profile.` - }); - } - - if (missingDescriptionRepos.length || metrics.descriptionlessRatio > 0.4) { - const target = missingDescriptionRepos.length ? joinRepoNames(missingDescriptionRepos) : "your weakest repos"; - suggestions.push({ - priority: 109 - subscores.documentationQuality, - text: `Improve project descriptions for ${target} with concise problem, stack, and outcomes so recruiters can scan faster.` - }); - } - - if (subscores.codeActivityConsistency < 70) { - suggestions.push({ - priority: 105 - subscores.codeActivityConsistency, - text: `Improve commit consistency: target at least 1 meaningful commit per week for 8 weeks and aim for 4/6 active months.` - }); - } - - if (subscores.impactSignals < 70) { - suggestions.push({ - priority: 105 - subscores.impactSignals, - text: `Increase impact signals by targeting 2 authored PRs and 2 authored issues per month on relevant repositories.` - }); - } - - if (pinnedRepos.source === "fallback") { - const suggestedPins = rankedRepos.slice(0, 3).map((repo) => repo.name); - suggestions.push({ - priority: 102, - text: `Pin your strongest repositories (${joinRepoNames(suggestedPins)}) so recruiters immediately see your best work.` - }); - } - - if (metrics.topicsRatio < 0.5) { - suggestions.push({ - priority: 95, - text: "Add GitHub topics/tags to your key repositories to improve discovery and communicate stack relevance quickly." - }); - } - - if (metrics.uniqueLanguages < 3) { - suggestions.push({ - priority: 88, - text: "Showcase at least one additional production-quality project in a different language or framework to broaden stack signals." - }); - } - - const sorted = suggestions - .sort((a, b) => b.priority - a.priority) - .map((entry) => entry.text) - .filter((text, index, arr) => arr.indexOf(text) === index); - - const defaults = [ - `Raise README coverage from ${(metrics.readmeCoverage * 100).toFixed(0)}% to at least 80% in your top repositories.`, - `Set a monthly maintenance pass to close stale issues and refresh pinned projects with recent commits.`, - `Improve repository completeness by ensuring every flagship repo has README, topics, and a demo/homepage link.`, - `Create a monthly portfolio changelog in one pinned repository to highlight recent improvements and impact.`, - `Publish measurable project outcomes (users, performance, business value) in your top README files.` - ]; - - while (sorted.length < 5 && defaults.length) { - sorted.push(defaults.shift()); - } - - return sorted.slice(0, 7); -} - -function buildHiddenRisks(subscores, metrics, rankedRepos) { - const risks = []; - - if (metrics.dominantLanguageShare > 0.78 && metrics.uniqueLanguages >= 2) { - risks.push( - `Stack concentration risk: ${metrics.dominantLanguage} accounts for ${formatPercent(metrics.dominantLanguageShare)} of detected language volume.` - ); - } - - const topWithoutHomepage = rankedRepos - .slice(0, 5) - .filter((repo) => !repo.homepage) - .map((repo) => repo.name); - - if (topWithoutHomepage.length >= 3) { - risks.push( - `Conversion risk: ${topWithoutHomepage.length} of your top repositories lack demo/homepage links (${joinRepoNames(topWithoutHomepage.slice(0, 3))}).` - ); - } - - const starConcentration = metrics.totalStars > 0 ? metrics.topRepoStars / metrics.totalStars : 0; - if (starConcentration > 0.85 && metrics.totalStars >= 20 && rankedRepos.length >= 4) { - risks.push( - `Brand concentration risk: one repository drives ${formatPercent(starConcentration)} of total stars, so portfolio impact is overly dependent on a single project.` - ); - } - - if (metrics.authoredPRCount < 5 && metrics.scorableRepoCount >= 10) { - risks.push( - `Collaboration signal risk: only ${metrics.authoredPRCount} authored PRs across a portfolio of ${metrics.scorableRepoCount} repositories.` - ); - } - - if (metrics.reposUpdated90dRatio > 0.45 && metrics.reposUpdated30dRatio < 0.15) { - risks.push( - "Momentum decay risk: older recent activity exists, but updates in the last 30 days are sparse." - ); - } - - if (subscores.repositoryCompleteness < 50 && metrics.topicsRatio < 0.35) { - risks.push( - "Discoverability risk: weak metadata coverage (topics and project links) can reduce recruiter confidence during quick profile scans." - ); - } - - if (!risks.length) { - risks.push("No hidden structural risks detected beyond the visible red flags."); - } - - return risks.slice(0, 6); -} - -function calculateHireabilityScore(subscores, overallScore, hiddenRisks) { - const riskCount = hiddenRisks.filter((item) => !/^No hidden/i.test(item)).length; - const penalty = Math.min(riskCount * 4, 16); - - const raw = - overallScore * 0.45 + - subscores.impactSignals * 0.2 + - subscores.recentActivity * 0.15 + - subscores.documentationQuality * 0.1 + - subscores.repositoryCompleteness * 0.1; - - return clampToRange(Math.round(raw - penalty), 0, 100); -} - -function classifyReadiness(hireabilityScore, overallScore) { - const blended = clampToRange(Math.round((hireabilityScore + overallScore) / 2), 0, 100); - - if (blended >= 85) { - return { - label: "Recruiter-Ready", - severity: "good", - percent: blended, - summary: "Portfolio can usually pass recruiter screens without major concerns." - }; - } - - if (blended >= 70) { - return { - label: "Interview-Ready", - severity: "good", - percent: blended, - summary: "Strong enough for interview pipelines with minor polish opportunities." - }; - } - - if (blended >= 55) { - return { - label: "Emerging", - severity: "warn", - percent: blended, - summary: "Promising portfolio that needs stronger consistency and presentation signals." - }; - } - - return { - label: "Foundation Stage", - severity: "risk", - percent: blended, - summary: "Core work is visible, but recruiter confidence is currently limited." - }; -} - -function buildRecruiterSimulation({ - overallScore, - hireabilityScore, - readiness, - subscores, - metrics, - strengths, - redFlags, - hiddenRisks, - rankedRepos -}) { - let verdict = "Not Ready for Interview Loop"; - let level = "risk"; - - if (hireabilityScore >= 82) { - verdict = "Strong Consider"; - level = "good"; - } else if (hireabilityScore >= 68) { - verdict = "Proceed to Technical Screen"; - level = "warn"; - } else if (hireabilityScore >= 52) { - verdict = "Potential with Portfolio Polish"; - level = "warn"; - } - - const leadingRepo = rankedRepos[0]; - const summary = `${verdict}: overall ${overallScore}/100 and hireability ${hireabilityScore}/100. Current readiness is ${readiness.label}. ${ - leadingRepo ? `Top signal comes from ${leadingRepo.name} (${leadingRepo.importance}/100 importance).` : "No standout repository identified yet." - }`; - - const signals = []; - if (strengths[0]) { - signals.push(`Positive signal: ${strengths[0]}`); - } - if (metrics.totalStars > 0) { - signals.push(`Market traction: ${metrics.totalStars} total stars across ${metrics.scorableRepoCount} scored repositories.`); - } else { - signals.push("Market traction is minimal; add demos and visibility to increase external validation."); - } - const firstRedFlag = redFlags.find((item) => !/No major red flags/i.test(item)); - if (firstRedFlag) { - signals.push(`Primary concern: ${firstRedFlag}`); - } - const firstHiddenRisk = hiddenRisks.find((item) => !/^No hidden/i.test(item)); - if (firstHiddenRisk) { - signals.push(`Hidden concern: ${firstHiddenRisk}`); - } - if (subscores.impactSignals < 60) { - signals.push("Interview risk: impact signals are below benchmark for competitive product roles."); - } - - return { - verdict, - level, - summary, - signals: signals.slice(0, 6) - }; -} - -function buildCareerPathRecommendation(metrics, rankedRepos, subscores) { - const languages = (metrics.topLanguages || []).map((lang) => lang.toLowerCase()); - const hasAnyLanguage = (list) => list.some((lang) => languages.includes(lang)); - - let title = "Generalist Software Engineer"; - let summary = "Your repositories indicate broad engineering capability across multiple project types."; - let nextSkills = [ - "Create 2 case-study READMEs that highlight architecture decisions and measurable outcomes.", - "Add live demos to your top projects to improve recruiter conversion.", - "Contribute at least 2 PRs/month to repositories related to your target role." - ]; - - if (hasAnyLanguage(["javascript", "typescript"])) { - title = "Full-Stack JavaScript Engineer"; - summary = "Your language mix and repository profile align best with product-focused full-stack roles."; - nextSkills = [ - "Ship one end-to-end project with production deployment, auth, and monitoring.", - "Document system architecture and tradeoffs for your top JavaScript/TypeScript repositories.", - "Add test coverage and CI status badges to your top 3 repositories." - ]; - } else if (hasAnyLanguage(["python"])) { - title = "Data / AI Engineer"; - summary = "Python-heavy activity indicates strong alignment with data and AI engineering tracks."; - nextSkills = [ - "Publish one reproducible ML/data project with dataset, metrics, and inference/demo endpoint.", - "Add evaluation methodology and model limitations to README docs.", - "Showcase pipeline automation and observability in at least one repository." - ]; - } else if (hasAnyLanguage(["java", "kotlin", "scala"])) { - title = "Backend Platform Engineer"; - summary = "JVM-oriented repositories and contribution signals fit backend and platform engineering roles."; - nextSkills = [ - "Demonstrate API design quality with versioned contracts and load/performance notes.", - "Add reliability signals: retries, circuit breakers, and structured logging.", - "Publish a backend project with deployment and scalability benchmarks." - ]; - } else if (hasAnyLanguage(["go", "rust", "c", "c++"])) { - title = "Systems / Infrastructure Engineer"; - summary = "Your dominant languages suggest strongest fit for systems and infrastructure engineering paths."; - nextSkills = [ - "Build one performance-focused project with clear latency/throughput benchmarks.", - "Document low-level design choices and profiling evidence in README.", - "Add automation scripts for build/test/release workflows." - ]; - } else if (hasAnyLanguage(["swift", "objective-c", "dart"])) { - title = "Mobile Application Engineer"; - summary = "Language signals indicate strongest fit for modern mobile development roles."; - nextSkills = [ - "Publish a shipped-quality mobile app with store-ready documentation and screenshots.", - "Add crash/error monitoring strategy and release notes cadence.", - "Showcase offline support and performance considerations." - ]; - } - - const confidence = clampToRange( - Math.round( - 52 + - metrics.dominantLanguageShare * 18 + - Math.min(metrics.uniqueLanguages, 5) * 4 + - (subscores.codeActivityConsistency >= 70 ? 6 : 0) + - (subscores.impactSignals >= 60 ? 6 : 0) - ), - 35, - 95 - ); - - const weakestSubscore = Object.entries(subscores).sort((a, b) => a[1] - b[1])[0][0]; - const weakestAdviceMap = { - documentationQuality: "Strengthen documentation quality to make your projects legible to non-engineers.", - codeActivityConsistency: "Establish a visible weekly commit cadence to reduce perceived delivery risk.", - projectPopularity: "Increase visibility through demos, developer posts, and open-source collaboration.", - repositoryCompleteness: "Add project metadata (description, topics, demos) to boost portfolio clarity.", - languageDiversity: "Add one adjacent-stack project to signal broader technical range.", - recentActivity: "Prioritize recent updates in top repositories to maintain recruiter confidence.", - impactSignals: "Increase authored PR and issue activity in relevant external repositories." - }; - - nextSkills.push(weakestAdviceMap[weakestSubscore]); - - if (rankedRepos[0] && rankedRepos[0].importance >= 80) { - nextSkills.push(`Position \`${rankedRepos[0].name}\` as flagship project with a complete case-study README.`); - } - - return { - title, - confidence, - summary, - nextSkills: dedupe(nextSkills).slice(0, 6) - }; -} - -function buildImprovementRoadmap(subscores, metrics, rankedRepos, careerPath, hiddenRisks) { - const roadmap = []; - const missingReadmeRepos = rankedRepos - .filter((repo) => repo.readmeKnown && !repo.hasReadme) - .slice(0, 2) - .map((repo) => repo.name); - const noHomepageRepos = rankedRepos - .filter((repo) => !repo.homepage) - .slice(0, 2) - .map((repo) => repo.name); - const staleRepos = rankedRepos - .filter((repo) => daysSinceDate(repo.pushedAt) > 180) - .slice(0, 2) - .map((repo) => repo.name); - - roadmap.push( - "Week 1: Set portfolio baseline by updating profile bio, pinning top repositories, and documenting measurable outcomes." - ); - - const deficitOrder = Object.entries(subscores) - .sort((a, b) => a[1] - b[1]) - .map(([key]) => key); - - deficitOrder.forEach((key) => { - if (key === "documentationQuality" && missingReadmeRepos.length) { - roadmap.push( - `Week 1-2: Add structured README files to ${joinRepoNames(missingReadmeRepos)} with problem, architecture, setup, and results.` - ); - } else if (key === "recentActivity" || key === "codeActivityConsistency") { - roadmap.push( - "Week 2-5: Maintain weekly commits (minimum 1 meaningful update/week) across at least 4 core repositories." - ); - } else if (key === "repositoryCompleteness" && noHomepageRepos.length) { - roadmap.push( - `Week 2-3: Add demo/homepage links and GitHub topics for ${joinRepoNames(noHomepageRepos)}.` - ); - } else if (key === "impactSignals") { - roadmap.push( - "Week 3-6: Target 8 authored PRs and 6 authored issues in repositories aligned to your target role." - ); - } else if (key === "projectPopularity") { - roadmap.push( - "Week 4: Publish concise demo posts and architecture threads to improve repository discoverability and star velocity." - ); - } else if (key === "languageDiversity" && metrics.uniqueLanguages < 4) { - roadmap.push( - "Week 5-7: Build one production-quality project in an adjacent stack to expand technical breadth signals." - ); - } - }); - - if (staleRepos.length) { - roadmap.push(`Week 3: Refresh or archive stale repositories (${joinRepoNames(staleRepos)}) to reduce portfolio noise.`); - } - - const firstHiddenRisk = hiddenRisks.find((item) => !/^No hidden/i.test(item)); - if (firstHiddenRisk) { - roadmap.push(`Week 4: Resolve hidden risk identified by analysis: ${firstHiddenRisk}`); - } - - roadmap.push( - `Week 8: Repackage top 3 projects for ${careerPath.title} positioning with recruiter-focused case studies and outcomes.` - ); - - const defaults = [ - "Set a monthly portfolio review reminder to keep all flagship repositories active and complete.", - "Add short demo videos/GIFs to your top repositories for faster recruiter evaluation." - ]; - - const uniqueRoadmap = dedupe(roadmap); - - while (uniqueRoadmap.length < 5 && defaults.length) { - uniqueRoadmap.push(defaults.shift()); - } - - return uniqueRoadmap.slice(0, 7); -} - -function buildScoreSummary(overallScore, grade, subscores, metrics, hireabilityScore, readinessLabel) { - const strongest = getNamedSubscore(Object.entries(subscores).sort((a, b) => b[1] - a[1])[0][0]); - const weakest = getNamedSubscore(Object.entries(subscores).sort((a, b) => a[1] - b[1])[0][0]); - - let summary = `Score ${overallScore}/100 (${grade}), hireability ${hireabilityScore}/100 (${readinessLabel}). Strongest area: ${strongest}. Biggest gap: ${weakest}.`; - - if (metrics.partialLanguageFailures || metrics.partialReadmeFailures) { - summary += ` Partial data warning: ${metrics.partialLanguageFailures + metrics.partialReadmeFailures} deep checks failed due to API limits or transient errors.`; - } - - return summary; -} - -function renderAnalysis(result) { - renderProfile(result); - renderScore(result); - renderSubscores(result.subscores); - renderScoringTransparency(result); - renderPinnedRepos(result.pinnedRepos); - renderRecruiterSimulation(result.recruiterSimulation); - renderInsightList(ui.strengthsList, result.strengths, "good"); - renderInsightList(ui.redFlagsList, result.redFlags, "risk"); - renderInsightList(ui.suggestionsList, result.suggestions, "warn"); - renderInsightList(ui.hiddenRisksList, result.hiddenRisks, "risk"); - renderCareerPath(result.careerPath); - renderRoadmap(result.improvementRoadmap); - renderRepoRanking(result.rankedRepos); - renderCharts(result); -} - -function renderProfile(result) { - const profile = result.profile; - const metrics = result.metrics; - - ui.avatarImg.src = profile.avatarUrl || "Images/logo.png"; - ui.avatarImg.alt = `${profile.login} avatar`; - - ui.profileName.textContent = profile.name; - ui.profileHandle.textContent = `@${profile.login}`; - - ui.profileLink.href = profile.htmlUrl; - ui.profileLink.textContent = profile.htmlUrl; - - ui.profileBio.textContent = profile.bio || "No bio provided."; - - ui.statRepos.textContent = String(profile.publicRepos); - ui.statFollowers.textContent = formatCompactNumber(profile.followers); - ui.statFollowing.textContent = formatCompactNumber(profile.following); - ui.statLastPush.textContent = metrics.lastPushDate ? formatDate(metrics.lastPushDate) : "No push data"; - ui.statPrCount.textContent = formatCompactNumber(metrics.authoredPRCount); - ui.statIssueCount.textContent = formatCompactNumber(metrics.authoredIssueCount); -} - -function renderScore(result) { - const severity = getSeverity(result.overallScore); - - ui.overallScore.textContent = String(result.overallScore); - ui.overallScoreRing.style.setProperty("--score-value", String(result.overallScore)); - ui.overallScoreRing.setAttribute("data-level", severity); - ui.scoreGrade.textContent = `Grade ${result.grade}`; - ui.scoreSummary.textContent = result.scoreSummary; - - const hireabilitySeverity = getSeverity(result.hireabilityScore); - ui.hireabilityScore.textContent = `${result.hireabilityScore}/100`; - ui.hireabilityScore.style.color = severityColor(hireabilitySeverity); - ui.hireabilityHint.textContent = "Calibrated from score, impact, recency, and hidden-risk penalties."; - - ui.readinessLevel.textContent = result.readiness.label; - ui.readinessLevel.style.color = severityColor(result.readiness.severity); - ui.readinessHint.textContent = result.readiness.summary; - ui.readinessBar.style.width = `${result.readiness.percent}%`; -} - -function renderSubscores(subscores) { - Object.entries(SUBSCORE_ID_MAP).forEach(([key, elementId]) => { - const element = document.getElementById(elementId); - const value = subscores[key]; - const severity = getSeverity(value); - - element.textContent = `${value}/100`; - element.className = `chip ${severityToChipClass(severity)}`; - }); -} - -function renderScoringTransparency(result) { - const { subscores, metrics } = result; - const details = { - documentationQuality: `README ${formatPercent(metrics.readmeCoverage)}, Desc ${formatPercent(metrics.descriptionCoverage)}`, - codeActivityConsistency: `${metrics.activeMonthsLast6}/6 months, ${formatPercent(metrics.reposUpdated90dRatio)} updated`, - projectPopularity: `Stars ${metrics.starsPerRepo.toFixed(1)}/repo, Forks ${metrics.forksPerRepo.toFixed(1)}/repo`, - repositoryCompleteness: `Non-empty ${formatPercent(metrics.nonEmptyRepoRatio)}, Topics ${formatPercent(metrics.topicsRatio)}`, - languageDiversity: `${metrics.uniqueLanguages} langs, Entropy ${metrics.normalizedShannonEntropy.toFixed(2)}`, - recentActivity: `${metrics.daysSinceLastPush}d last push, ${formatPercent(metrics.reposUpdated30dRatio)} updated`, - impactSignals: `Top repo ⭐ ${metrics.topRepoStars}, PRs ${metrics.authoredPRCount}, Issues ${metrics.authoredIssueCount}` - }; - - Object.entries(SCORING_EXPLAIN_ID_MAP).forEach(([key, elementId]) => { - const element = document.getElementById(elementId); - if (!element) { - return; - } - - const score = subscores[key]; - const severity = getSeverity(score); - element.textContent = `${score}/100 • ${details[key]}`; - element.className = `chip ${severityToChipClass(severity)}`; - }); -} - -function renderRecruiterSimulation(simulation) { - if (!simulation) { - ui.aiRecruiterVerdict.textContent = "Pending"; - ui.aiRecruiterVerdict.className = "chip chip-neutral"; - ui.aiRecruiterSummary.textContent = "Run an analysis to get recruiter-style interview feedback simulation."; - renderInsightList(ui.aiRecruiterSignals, ["No recruiter simulation available."], "neutral"); - return; - } - - ui.aiRecruiterVerdict.textContent = simulation.verdict; - ui.aiRecruiterVerdict.className = `chip ${severityToChipClass(simulation.level || "warn")}`; - ui.aiRecruiterSummary.textContent = simulation.summary; - renderInsightList(ui.aiRecruiterSignals, simulation.signals, simulation.level || "warn"); -} - -function renderCareerPath(careerPath) { - if (!careerPath) { - ui.careerPathTitle.textContent = "No career path recommendation"; - ui.careerPathSummary.textContent = "Run an analysis to unlock role fit recommendations."; - ui.careerConfidence.textContent = "--"; - ui.careerConfidence.className = "chip chip-neutral"; - renderInsightList(ui.careerSkillsList, ["No recommendations yet."], "neutral"); - return; - } - - ui.careerPathTitle.textContent = careerPath.title; - ui.careerPathSummary.textContent = careerPath.summary; - ui.careerConfidence.textContent = `${careerPath.confidence}% confidence`; - ui.careerConfidence.className = `chip ${severityToChipClass(getSeverity(careerPath.confidence))}`; - renderInsightList(ui.careerSkillsList, careerPath.nextSkills, "warn"); -} - -function renderRoadmap(roadmapItems) { - clearChildren(ui.roadmapList); - - if (!Array.isArray(roadmapItems) || !roadmapItems.length) { - const li = document.createElement("li"); - li.textContent = "No roadmap available."; - ui.roadmapList.appendChild(li); - return; - } - - roadmapItems.forEach((item) => { - const li = document.createElement("li"); - li.textContent = item; - ui.roadmapList.appendChild(li); - }); -} - -function renderPinnedRepos(pinnedRepos) { - clearChildren(ui.pinnedReposList); - - ui.pinnedSourceBadge.textContent = pinnedRepos.source; - ui.pinnedSourceBadge.className = `chip ${pinnedRepos.source === "graphql" ? "chip-good" : "chip-warn"}`; - - if (!pinnedRepos.items.length) { - appendEmptyState(ui.pinnedReposList, "No pinned repositories available."); - return; - } - - pinnedRepos.items.forEach((repo) => { - const li = document.createElement("li"); - li.className = "repo-item"; - - const left = document.createElement("div"); - const link = document.createElement("a"); - link.href = repo.url; - link.target = "_blank"; - link.rel = "noopener noreferrer"; - link.textContent = repo.name; - link.className = "repo-link"; - - left.appendChild(link); - - const meta = document.createElement("div"); - meta.className = "repo-item-meta"; - meta.textContent = `Stars: ${formatCompactNumber(repo.stars)}`; - - li.appendChild(left); - li.appendChild(meta); - - ui.pinnedReposList.appendChild(li); - }); -} - -function renderInsightList(container, items, tone) { - clearChildren(container); - - if (!items || !items.length) { - appendEmptyState(container, "No insights available."); - return; - } - - items.forEach((item) => { - const li = document.createElement("li"); - li.textContent = item; - - if (tone === "good") { - li.classList.add("chip-good"); - } else if (tone === "warn") { - li.classList.add("chip-warn"); - } else if (tone === "risk") { - li.classList.add("chip-risk"); - } - - container.appendChild(li); - }); -} - -function renderRepoRanking(rankedRepos) { - clearChildren(ui.repoRankingTable); - - if (!rankedRepos.length) { - const row = document.createElement("tr"); - const cell = document.createElement("td"); - cell.colSpan = 5; - cell.textContent = "No repositories available for ranking."; - cell.className = "empty-state"; - row.appendChild(cell); - ui.repoRankingTable.appendChild(row); - return; - } - - rankedRepos.slice(0, 15).forEach((repo) => { - const row = document.createElement("tr"); - - const repoCell = document.createElement("td"); - const link = document.createElement("a"); - link.href = repo.url; - link.target = "_blank"; - link.rel = "noopener noreferrer"; - link.textContent = repo.name; - link.className = "repo-link"; - - const langMeta = document.createElement("div"); - langMeta.className = "repo-item-meta"; - langMeta.textContent = repo.language; - - repoCell.appendChild(link); - repoCell.appendChild(langMeta); - - const importanceCell = document.createElement("td"); - const wrap = document.createElement("div"); - wrap.className = "importance-wrap"; - - const meter = document.createElement("div"); - meter.className = "importance-meter"; - const fill = document.createElement("span"); - fill.style.width = `${repo.importance}%`; - meter.appendChild(fill); - - const importanceText = document.createElement("span"); - importanceText.textContent = String(repo.importance); - - wrap.appendChild(meter); - wrap.appendChild(importanceText); - importanceCell.appendChild(wrap); - - const statsCell = document.createElement("td"); - statsCell.textContent = `${repo.stars} / ${repo.forks} / ${repo.watchers}`; - - const pushedCell = document.createElement("td"); - pushedCell.textContent = repo.pushedAt ? formatDate(repo.pushedAt) : "Unknown"; - - const readmeCell = document.createElement("td"); - const readmeChip = document.createElement("span"); - - if (repo.readmeKnown && repo.hasReadme) { - readmeChip.textContent = "Yes"; - readmeChip.className = "chip chip-good"; - } else if (repo.readmeKnown && !repo.hasReadme) { - readmeChip.textContent = "No"; - readmeChip.className = "chip chip-risk"; - } else { - readmeChip.textContent = "Unknown"; - readmeChip.className = "chip chip-neutral"; - } - - readmeCell.appendChild(readmeChip); - - row.appendChild(repoCell); - row.appendChild(importanceCell); - row.appendChild(statsCell); - row.appendChild(pushedCell); - row.appendChild(readmeCell); - - ui.repoRankingTable.appendChild(row); - }); -} - -function renderCharts(result) { - if ( - typeof Chart === "undefined" || - !ui.languageChart || - !ui.importanceChart || - !ui.subscoreRadarChart || - !ui.activityChart - ) { - return; - } - - const chartColors = [ - getCssVar("--chart-1"), - getCssVar("--chart-2"), - getCssVar("--chart-3"), - getCssVar("--chart-4"), - getCssVar("--chart-5"), - getCssVar("--chart-6") - ]; - - const axisColor = getCssVar("--muted"); - const borderColor = getCssVar("--border"); - - const languageEntries = Object.entries(result.languageTotals) - .sort((a, b) => b[1] - a[1]) - .slice(0, 8); - - const languageLabels = languageEntries.map(([label]) => label); - const languageValues = languageEntries.map(([, value]) => value); - - const hasLanguageData = languageLabels.length > 0; - - const languageData = { - labels: hasLanguageData ? languageLabels : ["No data"], - datasets: [{ - data: hasLanguageData ? languageValues : [1], - backgroundColor: hasLanguageData ? chartColors : [borderColor], - borderWidth: 1, - borderColor - }] - }; - - if (state.charts.language) { - state.charts.language.destroy(); - } - - state.charts.language = new Chart(ui.languageChart, { - type: "doughnut", - data: languageData, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { color: axisColor } - } - } - } - }); - - const importanceRepos = result.rankedRepos.slice(0, 10); - - if (state.charts.importance) { - state.charts.importance.destroy(); - } - - state.charts.importance = new Chart(ui.importanceChart, { - type: "bar", - data: { - labels: importanceRepos.map((repo) => repo.name), - datasets: [{ - label: "Importance", - data: importanceRepos.map((repo) => repo.importance), - backgroundColor: chartColors[0], - borderColor: chartColors[1], - borderWidth: 1 - }] - }, - options: { - maintainAspectRatio: false, - scales: { - x: { - ticks: { color: axisColor }, - grid: { color: borderColor } - }, - y: { - beginAtZero: true, - max: 100, - ticks: { color: axisColor }, - grid: { color: borderColor } - } - }, - plugins: { - legend: { - labels: { color: axisColor } - } - } - } - }); - - const radarLabels = Object.keys(result.subscores).map((key) => getNamedSubscore(key)); - const radarValues = Object.values(result.subscores); - - if (state.charts.subscoreRadar) { - state.charts.subscoreRadar.destroy(); - } - - state.charts.subscoreRadar = new Chart(ui.subscoreRadarChart, { - type: "radar", - data: { - labels: radarLabels, - datasets: [{ - label: "Portfolio Dimensions", - data: radarValues, - backgroundColor: `${chartColors[0]}40`, - borderColor: chartColors[0], - borderWidth: 2, - pointBackgroundColor: chartColors[1] - }] - }, - options: { - maintainAspectRatio: false, - scales: { - r: { - min: 0, - max: 100, - ticks: { color: axisColor, backdropColor: "transparent" }, - angleLines: { color: borderColor }, - grid: { color: borderColor }, - pointLabels: { color: axisColor } - } - }, - plugins: { - legend: { - labels: { color: axisColor } - } - } - } - }); - - const bucketLabels = ["0-30d", "31-90d", "91-180d", "181d+"]; - const bucketValues = [ - result.metrics.activityBuckets.updated30d, - result.metrics.activityBuckets.updated31to90d, - result.metrics.activityBuckets.updated91to180d, - result.metrics.activityBuckets.updated181plus - ]; - - if (state.charts.activity) { - state.charts.activity.destroy(); - } - - state.charts.activity = new Chart(ui.activityChart, { - type: "bar", - data: { - labels: bucketLabels, - datasets: [{ - label: "Repo Count", - data: bucketValues, - backgroundColor: [chartColors[1], chartColors[2], chartColors[3], chartColors[4]], - borderWidth: 1, - borderColor - }] - }, - options: { - maintainAspectRatio: false, - scales: { - x: { - ticks: { color: axisColor }, - grid: { color: borderColor } - }, - y: { - beginAtZero: true, - ticks: { color: axisColor, precision: 0 }, - grid: { color: borderColor } - } - }, - plugins: { - legend: { - labels: { color: axisColor } - } - } - } - }); -} - -function downloadMarkdownReport() { - if (!state.analysisResult) { - showError("Run an analysis before downloading a report."); - return; - } - - const markdown = buildMarkdownReport(state.analysisResult); - const blob = new Blob([markdown], { type: "text/markdown;charset=utf-8" }); - const url = URL.createObjectURL(blob); - - const anchor = document.createElement("a"); - anchor.href = url; - anchor.download = `${state.analysisResult.profile.login}-portfolio-report.md`; - anchor.click(); - - URL.revokeObjectURL(url); -} - -function buildMarkdownReport(result) { - const lines = []; - - lines.push("# GitHub Portfolio Analysis Report"); - lines.push(""); - lines.push(`- Generated: ${new Date(result.generatedAt).toLocaleString()}`); - lines.push(`- Profile: [${result.profile.name} (@${result.profile.login})](${result.profile.htmlUrl})`); - lines.push(`- Overall Score: **${result.overallScore}/100 (${result.grade})**`); - lines.push(`- Hireability Score: **${result.hireabilityScore}/100**`); - lines.push(`- Portfolio Readiness: **${result.readiness.label}**`); - lines.push(""); - lines.push("## Score Summary"); - lines.push(result.scoreSummary); - lines.push(""); - - lines.push("## Subscores"); - lines.push("| Category | Score | Weight |"); - lines.push("| --- | ---: | ---: |"); - - Object.entries(result.subscores).forEach(([key, score]) => { - lines.push(`| ${escapeMarkdown(getNamedSubscore(key))} | ${score} | ${result.weights[key]}% |`); - }); - - lines.push(""); - lines.push("## Scoring Inputs (Transparency)"); - lines.push(`- Documentation Quality Inputs: README coverage ${formatPercent(result.metrics.readmeCoverage)}, description coverage ${formatPercent(result.metrics.descriptionCoverage)}`); - lines.push(`- Activity Inputs: active months ${result.metrics.activeMonthsLast6}/6, repos updated in 90 days ${formatPercent(result.metrics.reposUpdated90dRatio)}`); - lines.push(`- Popularity Inputs: stars/repo ${result.metrics.starsPerRepo.toFixed(2)}, forks/repo ${result.metrics.forksPerRepo.toFixed(2)}, watchers/repo ${result.metrics.watchersPerRepo.toFixed(2)}`); - lines.push(`- Completeness Inputs: non-empty ${formatPercent(result.metrics.nonEmptyRepoRatio)}, homepage ${formatPercent(result.metrics.homepageRatio)}, topics ${formatPercent(result.metrics.topicsRatio)}`); - lines.push(`- Diversity Inputs: unique languages ${result.metrics.uniqueLanguages}, entropy ${result.metrics.normalizedShannonEntropy.toFixed(2)}`); - lines.push(`- Recency Inputs: days since last push ${result.metrics.daysSinceLastPush}, repos updated in 30 days ${formatPercent(result.metrics.reposUpdated30dRatio)}`); - lines.push(`- Impact Inputs: top repo stars ${result.metrics.topRepoStars}, followers ${result.profile.followers}, PRs ${result.metrics.authoredPRCount}, issues ${result.metrics.authoredIssueCount}`); - lines.push(""); - lines.push("## Strengths"); - result.strengths.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); - - lines.push(""); - lines.push("## Red Flags"); - result.redFlags.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); - - lines.push(""); - lines.push("## Actionable Suggestions"); - result.suggestions.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); - - lines.push(""); - lines.push("## AI Recruiter Simulation"); - lines.push(`- Verdict: **${escapeMarkdown(result.recruiterSimulation.verdict)}**`); - lines.push(`- Summary: ${escapeMarkdown(result.recruiterSimulation.summary)}`); - lines.push("- Signals:"); - result.recruiterSimulation.signals.forEach((item) => lines.push(` - ${escapeMarkdown(item)}`)); - - lines.push(""); - lines.push("## Hidden Risks"); - result.hiddenRisks.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); - - lines.push(""); - lines.push("## Career Path Recommendation"); - lines.push(`- Suggested Path: **${escapeMarkdown(result.careerPath.title)}**`); - lines.push(`- Confidence: ${result.careerPath.confidence}%`); - lines.push(`- Rationale: ${escapeMarkdown(result.careerPath.summary)}`); - lines.push("- Next Skill Targets:"); - result.careerPath.nextSkills.forEach((item) => lines.push(` - ${escapeMarkdown(item)}`)); - - lines.push(""); - lines.push("## Personalized Improvement Roadmap"); - result.improvementRoadmap.forEach((item, index) => lines.push(`${index + 1}. ${escapeMarkdown(item)}`)); - - lines.push(""); - lines.push("## Top Ranked Repositories"); - lines.push("| Repo | Importance | Stars | Forks | Watchers | Last Push | README |"); - lines.push("| --- | ---: | ---: | ---: | ---: | --- | --- |"); - - result.rankedRepos.slice(0, 10).forEach((repo) => { - lines.push( - `| [${escapeMarkdown(repo.name)}](${repo.url}) | ${repo.importance} | ${repo.stars} | ${repo.forks} | ${repo.watchers} | ${formatDate(repo.pushedAt)} | ${repo.readmeKnown ? (repo.hasReadme ? "Yes" : "No") : "Unknown"} |` - ); - }); - - lines.push(""); - lines.push("## Pinned Repositories"); - lines.push(`- Source: ${result.pinnedRepos.source}`); - - result.pinnedRepos.items.forEach((repo) => { - lines.push(`- [${escapeMarkdown(repo.name)}](${repo.url}) - ${repo.stars} stars`); - }); - - lines.push(""); - lines.push("## Key Metrics"); - lines.push(`- Scorable repositories: ${result.metrics.scorableRepoCount}`); - lines.push(`- Total stars/forks/watchers: ${result.metrics.totalStars}/${result.metrics.totalForks}/${result.metrics.totalWatchers}`); - lines.push(`- README coverage (sampled): ${(result.metrics.readmeCoverage * 100).toFixed(1)}%`); - lines.push(`- Repositories updated in 30d/90d: ${result.metrics.reposUpdated30d}/${result.metrics.reposUpdated90d}`); - lines.push(`- Days since last push: ${result.metrics.daysSinceLastPush}`); - lines.push(`- Authored PRs/issues: ${result.metrics.authoredPRCount}/${result.metrics.authoredIssueCount}`); - lines.push(`- Unique languages: ${result.metrics.uniqueLanguages}`); - lines.push(`- Dominant language share: ${formatPercent(result.metrics.dominantLanguageShare)} (${result.metrics.dominantLanguage})`); - lines.push(`- Activity buckets (0-30/31-90/91-180/181+ days): ${result.metrics.activityBuckets.updated30d}/${result.metrics.activityBuckets.updated31to90d}/${result.metrics.activityBuckets.updated91to180d}/${result.metrics.activityBuckets.updated181plus}`); - - return lines.join("\n"); -} - -function renderHistory() { - clearChildren(ui.historyList); - - if (!state.history.length) { - const empty = document.createElement("p"); - empty.className = "empty-state"; - empty.textContent = "No recent searches yet."; - ui.historyList.appendChild(empty); - return; - } - - state.history.forEach((username) => { - const button = document.createElement("button"); - button.type = "button"; - button.className = "history-chip"; - button.textContent = username; - button.addEventListener("click", () => { - ui.profileInput.value = username; - handleAnalyze(); - }); - - ui.historyList.appendChild(button); - }); -} - -function saveHistory(username) { - const clean = (username || "").trim().toLowerCase(); - if (!clean) { - return; - } - - state.history = [clean, ...state.history.filter((item) => item !== clean)].slice(0, HISTORY_LIMIT); - localStorage.setItem(STORAGE_KEYS.history, JSON.stringify(state.history)); -} - -function setLoading(isLoading, message = "") { - ui.analyzeBtn.disabled = isLoading; - - if (isLoading) { - ui.loadingState.classList.remove("hidden"); - ui.loadingText.textContent = message || "Analyzing profile..."; - } else { - ui.loadingState.classList.add("hidden"); - ui.loadingText.textContent = "Analyzing profile..."; - } -} - -function clearBanners() { - ui.errorBanner.classList.add("hidden"); - ui.errorBanner.textContent = ""; - - ui.rateLimitBanner.classList.add("hidden"); - ui.rateLimitBanner.textContent = ""; -} - -function showError(message) { - ui.errorBanner.textContent = message; - ui.errorBanner.classList.remove("hidden"); -} - -function showRateLimit(resetAt) { - const when = resetAt ? new Date(resetAt).toLocaleString() : "the API reset window"; - ui.rateLimitBanner.textContent = `Rate limited by GitHub API. Retry after ${when}.`; - ui.rateLimitBanner.classList.remove("hidden"); -} - -function applyTheme(theme) { - const normalized = theme === "dark" ? "dark" : "light"; - document.documentElement.setAttribute("data-theme", normalized); - localStorage.setItem(STORAGE_KEYS.theme, normalized); - ui.themeToggleBtn.textContent = normalized === "dark" ? "Switch to Light" : "Switch to Dark"; -} - -function getCachedAnalysis(username, tokenMode) { - const key = `${CACHE_PREFIX}${username.toLowerCase()}`; - const payload = readJsonStorage(key, null); - - if (!payload || typeof payload !== "object") { - return null; - } - - const isFresh = Number(payload.savedAt) && Date.now() - Number(payload.savedAt) <= CACHE_TTL_MS; - const sameMode = Boolean(payload.tokenMode) === Boolean(tokenMode); - - const hasExpectedShape = - payload.analysis && - typeof payload.analysis === "object" && - typeof payload.analysis.overallScore === "number" && - typeof payload.analysis.hireabilityScore === "number" && - payload.analysis.readiness && - payload.analysis.recruiterSimulation && - Array.isArray(payload.analysis.improvementRoadmap); - - if (!isFresh || !sameMode || !hasExpectedShape) { - localStorage.removeItem(key); - return null; - } - - return payload.analysis; -} - -function setCachedAnalysis(username, tokenMode, analysis) { - const key = `${CACHE_PREFIX}${username.toLowerCase()}`; - localStorage.setItem( - key, - JSON.stringify({ - savedAt: Date.now(), - tokenMode: Boolean(tokenMode), - analysis - }) - ); -} - -async function githubRequest(path, options = {}) { - const { - token = "", - method = "GET", - signal, - retry = 1, - body, - accept = "application/vnd.github+json" - } = options; - - const url = path.startsWith("http") ? path : `${GITHUB_API_ROOT}${path}`; - - let attempt = 0; - while (attempt <= retry) { - attempt += 1; - - try { - const response = await fetch(url, { - method, - signal, - headers: { - Accept: accept, - "X-GitHub-Api-Version": "2022-11-28", - ...(token ? { Authorization: `Bearer ${token}` } : {}) - }, - ...(body ? { body: JSON.stringify(body) } : {}) - }); - - const rateInfo = extractRateInfo(response.headers); - const contentType = response.headers.get("content-type") || ""; - - if (!response.ok) { - let errorPayload = null; - try { - errorPayload = contentType.includes("application/json") - ? await response.json() - : { message: await response.text() }; - } catch { - errorPayload = { message: `GitHub API error (${response.status})` }; - } - - const message = - (errorPayload && typeof errorPayload.message === "string" && errorPayload.message.trim()) || - `GitHub API error (${response.status})`; - - if (response.status === 404) { - throw new GitHubError("NotFound", message, { status: response.status, rateInfo }); - } - - if (response.status === 401) { - throw new GitHubError("Unauthorized", message, { status: response.status, rateInfo }); - } - - const rateLimited = - response.status === 429 || - (response.status === 403 && (rateInfo.remaining === 0 || /rate limit/i.test(message))); - - if (rateLimited) { - throw new GitHubError("RateLimited", message, { - status: response.status, - resetAt: rateInfo.resetAt, - rateInfo - }); - } - - throw new GitHubError("Api", message, { status: response.status, rateInfo }); - } - - if (response.status === 204) { - return { data: null, headers: response.headers, rateInfo }; - } - - let data; - if (contentType.includes("application/json")) { - data = await response.json(); - } else { - data = await response.text(); - } - - return { data, headers: response.headers, rateInfo }; - } catch (error) { - if (error.name === "AbortError") { - throw error; - } - - if (error instanceof GitHubError) { - throw error; - } - - if (attempt > retry) { - throw new GitHubError("Network", "Failed to reach GitHub API.", { cause: error }); - } - } - } - - throw new GitHubError("Network", "Failed to reach GitHub API."); -} - -async function githubGraphQL(query, variables, token, signal) { - if (!token) { - throw new GitHubError("Unauthorized", "GitHub token is required for GraphQL requests."); - } - - let response; - try { - response = await fetch(GITHUB_GRAPHQL_ENDPOINT, { - method: "POST", - signal, - headers: { - Accept: "application/vnd.github+json", - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - "X-GitHub-Api-Version": "2022-11-28" - }, - body: JSON.stringify({ query, variables }) - }); - } catch (error) { - if (error.name === "AbortError") { - throw error; - } - throw new GitHubError("Network", "Failed to reach GitHub GraphQL API.", { cause: error }); - } - - const rateInfo = extractRateInfo(response.headers); - const payload = await response.json(); - - if (!response.ok) { - if (response.status === 401) { - throw new GitHubError("Unauthorized", "GitHub token is invalid for GraphQL API.", { - status: response.status, - rateInfo - }); - } - - const message = (payload && payload.message) || `GitHub GraphQL error (${response.status})`; - throw new GitHubError("Api", message, { status: response.status, rateInfo }); - } - - if (payload.errors && payload.errors.length) { - throw new GitHubError("Api", payload.errors[0].message || "GraphQL query failed.", { - errors: payload.errors, - rateInfo - }); - } - - return payload.data; -} - -function extractRateInfo(headers) { - const remaining = Number(headers.get("x-ratelimit-remaining")); - const resetEpoch = Number(headers.get("x-ratelimit-reset")); - - return { - remaining: Number.isFinite(remaining) ? remaining : null, - resetAt: Number.isFinite(resetEpoch) && resetEpoch > 0 ? new Date(resetEpoch * 1000).toISOString() : null - }; -} - -async function mapWithConcurrency(items, concurrency, worker) { - if (!items.length) { - return []; - } - - const results = new Array(items.length); - let currentIndex = 0; - - const runner = async () => { - while (currentIndex < items.length) { - const index = currentIndex; - currentIndex += 1; - results[index] = await worker(items[index], index); - } - }; - - const workers = []; - const count = Math.min(concurrency, items.length); - for (let i = 0; i < count; i += 1) { - workers.push(runner()); - } - - await Promise.all(workers); - return results; -} - -function preRankImportanceScore(repo) { - const days = daysSinceDate(repo.pushed_at); - const freshness = days <= 30 ? 10 : days <= 90 ? 6 : days <= 180 ? 3 : 0; - - return ( - (Number(repo.stargazers_count) || 0) * 3 + - (Number(repo.forks_count) || 0) * 2 + - (Number(repo.watchers_count) || 0) + - freshness - ); -} - -function computeActivityInLastSixMonths(repos) { - const now = new Date(); - const monthKeys = []; - - for (let i = 0; i < 6; i += 1) { - const date = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - i, 1)); - monthKeys.push(`${date.getUTCFullYear()}-${date.getUTCMonth() + 1}`); - } - - const activeSet = new Set(); - - repos.forEach((repo) => { - if (!repo.pushed_at) { - return; - } - - const pushed = new Date(repo.pushed_at); - const key = `${pushed.getUTCFullYear()}-${pushed.getUTCMonth() + 1}`; - if (monthKeys.includes(key)) { - activeSet.add(key); - } - }); - - const activeMonths = activeSet.size; - return { - activeMonths, - ratio: activeMonths / 6 - }; -} - -function getRecencyBucket(daysSinceLastPush) { - if (daysSinceLastPush <= 7) { - return 1; - } - if (daysSinceLastPush <= 30) { - return 0.8; - } - if (daysSinceLastPush <= 90) { - return 0.6; - } - if (daysSinceLastPush <= 180) { - return 0.3; - } - return 0.1; -} - -function getSeverity(score) { - if (score >= 70) { - return "good"; - } - if (score >= 40) { - return "warn"; - } - return "risk"; -} - -function severityToChipClass(severity) { - if (severity === "good") { - return "chip-good"; - } - if (severity === "warn") { - return "chip-warn"; - } - return "chip-risk"; -} - -function severityColor(severity) { - if (severity === "good") { - return getCssVar("--good"); - } - if (severity === "warn") { - return getCssVar("--warn"); - } - return getCssVar("--risk"); -} - -function scoreToGrade(score) { - if (score >= 90) { - return "A+"; - } - if (score >= 80) { - return "A"; - } - if (score >= 70) { - return "B"; - } - if (score >= 60) { - return "C"; - } - if (score >= 45) { - return "D"; - } - return "E"; -} - -function getNamedSubscore(key) { - const labels = { - documentationQuality: "Documentation Quality", - codeActivityConsistency: "Code Activity / Consistency", - projectPopularity: "Project Popularity", - repositoryCompleteness: "Repository Completeness", - languageDiversity: "Language Diversity", - recentActivity: "Recent Activity", - impactSignals: "Impact Signals" - }; - - return labels[key] || key; -} - -function safeRatio(numerator, denominator) { - if (!denominator) { - return 0; - } - return numerator / denominator; -} - -function scoreFromRatio(ratio) { - return clampToRange(Math.round(cap01(ratio) * 100), 0, 100); -} - -function cap01(value) { - if (!Number.isFinite(value)) { - return 0; - } - return Math.max(0, Math.min(1, value)); -} - -function clampToRange(value, min, max) { - return Math.min(max, Math.max(min, value)); -} - -function formatDate(value) { - if (!value) { - return "Unknown"; - } - - const date = new Date(value); - if (Number.isNaN(date.getTime())) { - return "Unknown"; - } - - return date.toLocaleDateString(undefined, { - year: "numeric", - month: "short", - day: "numeric" - }); -} - -function daysSinceDate(value, nowMs = Date.now()) { - if (!value) { - return Number.POSITIVE_INFINITY; - } - - const date = new Date(value); - if (Number.isNaN(date.getTime())) { - return Number.POSITIVE_INFINITY; - } - - return Math.floor((nowMs - date.getTime()) / 86400000); -} - -function formatCompactNumber(value) { - const num = Number(value) || 0; - return new Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 1 }).format(num); -} - -function formatPercent(value) { - return `${Math.round(cap01(value) * 100)}%`; -} - -function joinRepoNames(names) { - if (!names.length) { - return "target repositories"; - } - return names.map((name) => `\`${name}\``).join(", "); -} - -function dedupe(items) { - return items.filter((item, index) => items.indexOf(item) === index); -} - -function readJsonStorage(key, fallback) { - try { - const raw = localStorage.getItem(key); - if (!raw) { - return fallback; - } - return JSON.parse(raw); - } catch { - return fallback; - } -} - -function clearChildren(node) { - while (node.firstChild) { - node.removeChild(node.firstChild); - } -} - -function appendEmptyState(container, text) { - const li = document.createElement("li"); - li.className = "empty-state"; - li.textContent = text; - container.appendChild(li); -} - -function getCssVar(name) { - return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); -} - -function escapeMarkdown(text) { - return String(text).replace(/[|]/g, "\\|"); -} diff --git a/src/config/constants.js b/src/config/constants.js new file mode 100644 index 000000000..0314aebdf --- /dev/null +++ b/src/config/constants.js @@ -0,0 +1,54 @@ +export const CACHE_TTL_MS = 10 * 60 * 1000; +export const HISTORY_LIMIT = 8; +export const ANALYZE_ENDPOINT = "/api/analyze"; + +export const STORAGE_KEYS = Object.freeze({ + history: "devdetective_history", + theme: "devdetective_theme" +}); + +export const CACHE_PREFIX = "devdetective_cache_v4_"; + +export const WEIGHTS = Object.freeze({ + documentationQuality: 18, + codeActivityConsistency: 17, + projectPopularity: 15, + repositoryCompleteness: 14, + languageDiversity: 10, + recentActivity: 14, + impactSignals: 12 +}); + +export const SUBSCORE_ID_MAP = Object.freeze({ + documentationQuality: "subDocumentationQuality", + codeActivityConsistency: "subCodeActivityConsistency", + projectPopularity: "subProjectPopularity", + repositoryCompleteness: "subRepositoryCompleteness", + languageDiversity: "subLanguageDiversity", + recentActivity: "subRecentActivity", + impactSignals: "subImpactSignals" +}); + +export const SCORING_EXPLAIN_ID_MAP = Object.freeze({ + documentationQuality: "explainDocumentationQuality", + codeActivityConsistency: "explainCodeActivityConsistency", + projectPopularity: "explainProjectPopularity", + repositoryCompleteness: "explainRepositoryCompleteness", + languageDiversity: "explainLanguageDiversity", + recentActivity: "explainRecentActivity", + impactSignals: "explainImpactSignals" +}); + +export function getNamedSubscore(key) { + const labels = { + documentationQuality: "Documentation Quality", + codeActivityConsistency: "Code Activity / Consistency", + projectPopularity: "Project Popularity", + repositoryCompleteness: "Repository Completeness", + languageDiversity: "Language Diversity", + recentActivity: "Recent Activity", + impactSignals: "Impact Signals" + }; + + return labels[key] || key; +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 000000000..dfb27ff2c --- /dev/null +++ b/src/main.js @@ -0,0 +1,381 @@ +import { + ANALYZE_ENDPOINT, + CACHE_PREFIX, + CACHE_TTL_MS, + HISTORY_LIMIT, + STORAGE_KEYS +} from "./config/constants.js"; +import { getUiElements } from "./ui/elements.js"; +import { renderInitialState, renderAnalysis, renderHistory } from "./ui/render.js"; +import { renderCharts } from "./ui/charts.js"; +import { buildMarkdownReport } from "./report/markdown.js"; +import { readJsonStorage } from "./utils/core.js"; + +const ui = getUiElements(); + +const state = { + currentUsername: "", + analysisResult: null, + abortController: null, + charts: { + language: null, + importance: null, + subscoreRadar: null, + activity: null + }, + history: readJsonStorage(STORAGE_KEYS.history, []) +}; + +class ApiError extends Error { + constructor(type, message, details = {}) { + super(message); + this.name = "ApiError"; + this.type = type; + this.details = details; + } +} + +init(); + +function init() { + bindEvents(); + + const savedTheme = localStorage.getItem(STORAGE_KEYS.theme) || "light"; + applyTheme(savedTheme); + + renderInitialState(ui); + renderHistory(state.history, ui, handleHistorySelect); + + if (state.history[0]) { + ui.profileInput.value = state.history[0]; + } +} + +function bindEvents() { + ui.analyzeBtn.addEventListener("click", handleAnalyze); + + ui.profileInput.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + handleAnalyze(); + } + }); + + ui.themeToggleBtn.addEventListener("click", () => { + const currentTheme = document.documentElement.getAttribute("data-theme") || "light"; + const nextTheme = currentTheme === "dark" ? "light" : "dark"; + applyTheme(nextTheme); + + if (state.analysisResult) { + renderCharts(state.analysisResult, state, ui); + } + }); + + ui.downloadReportBtn.addEventListener("click", downloadMarkdownReport); +} + +async function handleAnalyze() { + clearBanners(); + + const parsed = parseProfileInput(ui.profileInput.value); + if (!parsed.ok) { + showError(parsed.error); + return; + } + + const username = parsed.username; + state.currentUsername = username; + ui.profileInput.value = username; + + if (state.abortController) { + state.abortController.abort(); + } + + const controller = new AbortController(); + state.abortController = controller; + + setLoading(true, "Preparing analysis..."); + + try { + let analysis = getCachedAnalysis(username); + + if (analysis) { + setLoading(true, "Loaded cached analysis from the last 10 minutes..."); + } else { + setLoading(true, "Analyzing GitHub profile via secure API..."); + analysis = await fetchAnalysisFromApi(username, controller.signal); + setCachedAnalysis(username, analysis); + } + + if (controller.signal.aborted) { + return; + } + + state.analysisResult = analysis; + ui.downloadReportBtn.disabled = false; + + saveHistory(username); + renderHistory(state.history, ui, handleHistorySelect); + + renderAnalysis(analysis, ui); + renderCharts(analysis, state, ui); + } catch (error) { + if (error.name === "AbortError") { + return; + } + handleAnalysisError(error); + } finally { + if (state.abortController === controller) { + state.abortController = null; + } + setLoading(false); + } +} + +async function fetchAnalysisFromApi(username, signal) { + const url = `${ANALYZE_ENDPOINT}?username=${encodeURIComponent(username)}`; + + let response; + try { + response = await fetch(url, { + method: "GET", + signal, + headers: { + Accept: "application/json" + } + }); + } catch (error) { + if (error.name === "AbortError") { + throw error; + } + throw new ApiError("Network", "Failed to reach analysis API.", { cause: error }); + } + + let payload = null; + try { + payload = await response.json(); + } catch { + payload = null; + } + + if (!response.ok || !payload || payload.ok !== true) { + const type = payload && payload.error && payload.error.type ? payload.error.type : mapStatusToType(response.status); + const message = + payload && payload.error && payload.error.message + ? payload.error.message + : `Analysis API failed with status ${response.status}.`; + + throw new ApiError(type, message, { + status: response.status, + resetAt: payload && payload.error ? payload.error.resetAt : response.headers.get("x-ratelimit-reset-at") + }); + } + + return payload.data; +} + +function mapStatusToType(status) { + if (status === 404) { + return "NotFound"; + } + if (status === 429) { + return "RateLimited"; + } + if (status === 400) { + return "Validation"; + } + if (status === 401 || status === 403) { + return "Unauthorized"; + } + return "Api"; +} + +function handleHistorySelect(username) { + ui.profileInput.value = username; + handleAnalyze(); +} + +function handleAnalysisError(error) { + if (!(error instanceof ApiError)) { + showError("Unexpected error while analyzing the profile."); + return; + } + + if (error.type === "NotFound") { + showError("Profile not found. Enter a valid GitHub username or GitHub profile URL."); + return; + } + + if (error.type === "RateLimited") { + showError("Rate limit reached. Please retry in a minute."); + showRateLimit(error.details.resetAt || null); + return; + } + + if (error.type === "Validation") { + showError(error.message || "Invalid username or URL input."); + return; + } + + if (error.type === "Unauthorized") { + showError("Server GitHub token is missing or invalid. Configure deployment environment variables."); + return; + } + + if (error.type === "Network") { + showError("Network issue while contacting analysis API. Please retry."); + return; + } + + showError(error.message || "Analysis request failed."); +} + +function downloadMarkdownReport() { + if (!state.analysisResult) { + showError("Run an analysis before downloading a report."); + return; + } + + const markdown = buildMarkdownReport(state.analysisResult); + const blob = new Blob([markdown], { type: "text/markdown;charset=utf-8" }); + const url = URL.createObjectURL(blob); + + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = `${state.analysisResult.profile.login}-portfolio-report.md`; + anchor.click(); + + URL.revokeObjectURL(url); +} + +function saveHistory(username) { + const clean = (username || "").trim().toLowerCase(); + if (!clean) { + return; + } + + state.history = [clean, ...state.history.filter((item) => item !== clean)].slice(0, HISTORY_LIMIT); + localStorage.setItem(STORAGE_KEYS.history, JSON.stringify(state.history)); +} + +function setLoading(isLoading, message = "") { + ui.analyzeBtn.disabled = isLoading; + + if (isLoading) { + ui.loadingState.classList.remove("hidden"); + ui.loadingText.textContent = message || "Analyzing profile..."; + } else { + ui.loadingState.classList.add("hidden"); + ui.loadingText.textContent = "Analyzing profile..."; + } +} + +function clearBanners() { + ui.errorBanner.classList.add("hidden"); + ui.errorBanner.textContent = ""; + + ui.rateLimitBanner.classList.add("hidden"); + ui.rateLimitBanner.textContent = ""; +} + +function showError(message) { + ui.errorBanner.textContent = message; + ui.errorBanner.classList.remove("hidden"); +} + +function showRateLimit(resetAt) { + const when = resetAt ? new Date(resetAt).toLocaleString() : "the API reset window"; + ui.rateLimitBanner.textContent = `Rate limited. Retry after ${when}.`; + ui.rateLimitBanner.classList.remove("hidden"); +} + +function applyTheme(theme) { + const normalized = theme === "dark" ? "dark" : "light"; + document.documentElement.setAttribute("data-theme", normalized); + localStorage.setItem(STORAGE_KEYS.theme, normalized); + ui.themeToggleBtn.textContent = normalized === "dark" ? "Switch to Light" : "Switch to Dark"; +} + +function getCachedAnalysis(username) { + const key = `${CACHE_PREFIX}${username.toLowerCase()}`; + const payload = readJsonStorage(key, null); + + if (!payload || typeof payload !== "object") { + return null; + } + + const isFresh = Number(payload.savedAt) && Date.now() - Number(payload.savedAt) <= CACHE_TTL_MS; + + const hasExpectedShape = + payload.analysis && + typeof payload.analysis === "object" && + typeof payload.analysis.overallScore === "number" && + typeof payload.analysis.hireabilityScore === "number" && + payload.analysis.readiness && + payload.analysis.recruiterSimulation && + Array.isArray(payload.analysis.improvementRoadmap); + + if (!isFresh || !hasExpectedShape) { + localStorage.removeItem(key); + return null; + } + + return payload.analysis; +} + +function setCachedAnalysis(username, analysis) { + const key = `${CACHE_PREFIX}${username.toLowerCase()}`; + localStorage.setItem( + key, + JSON.stringify({ + savedAt: Date.now(), + analysis + }) + ); +} + +function parseProfileInput(rawInput) { + const value = (rawInput || "").trim(); + + if (!value) { + return { ok: false, error: "Enter a GitHub username or profile URL to analyze." }; + } + + let candidate = value; + + if (/github\.com\//i.test(candidate) && !/^https?:\/\//i.test(candidate)) { + candidate = `https://${candidate}`; + } + + if (/^https?:\/\//i.test(candidate)) { + try { + const url = new URL(candidate); + const host = url.hostname.toLowerCase(); + if (host !== "github.com" && host !== "www.github.com") { + return { ok: false, error: "URL must be a valid github.com profile URL." }; + } + + const segments = url.pathname.split("/").filter(Boolean); + if (!segments.length) { + return { ok: false, error: "GitHub URL is missing a username." }; + } + + candidate = segments[0]; + } catch { + return { ok: false, error: "Invalid URL format. Use a username or a github.com profile URL." }; + } + } + + candidate = candidate.replace(/^@+/, "").trim(); + + if (candidate.endsWith(".git")) { + candidate = candidate.slice(0, -4); + } + + const isValid = /^[A-Za-z0-9-]{1,39}$/.test(candidate) && !candidate.startsWith("-") && !candidate.endsWith("-"); + + if (!isValid) { + return { ok: false, error: "Invalid GitHub username format." }; + } + + return { ok: true, username: candidate.toLowerCase() }; +} diff --git a/src/report/markdown.js b/src/report/markdown.js new file mode 100644 index 000000000..4e97ca39b --- /dev/null +++ b/src/report/markdown.js @@ -0,0 +1,104 @@ +import { formatDate, formatPercent, escapeMarkdown } from "../utils/core.js"; +import { getNamedSubscore } from "../config/constants.js"; + +export function buildMarkdownReport(result) { + const lines = []; + + lines.push("# GitHub Portfolio Analysis Report"); + lines.push(""); + lines.push(`- Generated: ${new Date(result.generatedAt).toLocaleString()}`); + lines.push(`- Profile: [${result.profile.name} (@${result.profile.login})](${result.profile.htmlUrl})`); + lines.push(`- Overall Score: **${result.overallScore}/100 (${result.grade})**`); + lines.push(`- Hireability Score: **${result.hireabilityScore}/100**`); + lines.push(`- Portfolio Readiness: **${result.readiness.label}**`); + lines.push(""); + + lines.push("## Score Summary"); + lines.push(result.scoreSummary); + lines.push(""); + + lines.push("## Subscores"); + lines.push("| Category | Score | Weight |"); + lines.push("| --- | ---: | ---: |"); + + Object.entries(result.subscores).forEach(([key, score]) => { + lines.push(`| ${escapeMarkdown(getNamedSubscore(key))} | ${score} | ${result.weights[key]}% |`); + }); + + lines.push(""); + lines.push("## Scoring Inputs (Transparency)"); + lines.push(`- Documentation Quality Inputs: README coverage ${formatPercent(result.metrics.readmeCoverage)}, description coverage ${formatPercent(result.metrics.descriptionCoverage)}`); + lines.push(`- Activity Inputs: active months ${result.metrics.activeMonthsLast6}/6, repos updated in 90 days ${formatPercent(result.metrics.reposUpdated90dRatio)}`); + lines.push(`- Popularity Inputs: stars/repo ${result.metrics.starsPerRepo.toFixed(2)}, forks/repo ${result.metrics.forksPerRepo.toFixed(2)}, watchers/repo ${result.metrics.watchersPerRepo.toFixed(2)}`); + lines.push(`- Completeness Inputs: non-empty ${formatPercent(result.metrics.nonEmptyRepoRatio)}, homepage ${formatPercent(result.metrics.homepageRatio)}, topics ${formatPercent(result.metrics.topicsRatio)}`); + lines.push(`- Diversity Inputs: unique languages ${result.metrics.uniqueLanguages}, entropy ${result.metrics.normalizedShannonEntropy.toFixed(2)}`); + lines.push(`- Recency Inputs: days since last push ${result.metrics.daysSinceLastPush}, repos updated in 30 days ${formatPercent(result.metrics.reposUpdated30dRatio)}`); + lines.push(`- Impact Inputs: top repo stars ${result.metrics.topRepoStars}, followers ${result.profile.followers}, PRs ${result.metrics.authoredPRCount}, issues ${result.metrics.authoredIssueCount}`); + + lines.push(""); + lines.push("## Strengths"); + result.strengths.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Red Flags"); + result.redFlags.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Actionable Suggestions"); + result.suggestions.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## AI Recruiter Simulation"); + lines.push(`- Verdict: **${escapeMarkdown(result.recruiterSimulation.verdict)}**`); + lines.push(`- Summary: ${escapeMarkdown(result.recruiterSimulation.summary)}`); + lines.push("- Signals:"); + result.recruiterSimulation.signals.forEach((item) => lines.push(` - ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Hidden Risks"); + result.hiddenRisks.forEach((item) => lines.push(`- ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Career Path Recommendation"); + lines.push(`- Suggested Path: **${escapeMarkdown(result.careerPath.title)}**`); + lines.push(`- Confidence: ${result.careerPath.confidence}%`); + lines.push(`- Rationale: ${escapeMarkdown(result.careerPath.summary)}`); + lines.push("- Next Skill Targets:"); + result.careerPath.nextSkills.forEach((item) => lines.push(` - ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Personalized Improvement Roadmap"); + result.improvementRoadmap.forEach((item, index) => lines.push(`${index + 1}. ${escapeMarkdown(item)}`)); + + lines.push(""); + lines.push("## Top Ranked Repositories"); + lines.push("| Repo | Importance | Stars | Forks | Watchers | Last Push | README |"); + lines.push("| --- | ---: | ---: | ---: | ---: | --- | --- |"); + + result.rankedRepos.slice(0, 10).forEach((repo) => { + lines.push( + `| [${escapeMarkdown(repo.name)}](${repo.url}) | ${repo.importance} | ${repo.stars} | ${repo.forks} | ${repo.watchers} | ${formatDate(repo.pushedAt)} | ${repo.readmeKnown ? (repo.hasReadme ? "Yes" : "No") : "Unknown"} |` + ); + }); + + lines.push(""); + lines.push("## Pinned Repositories"); + lines.push(`- Source: ${result.pinnedRepos.source}`); + result.pinnedRepos.items.forEach((repo) => { + lines.push(`- [${escapeMarkdown(repo.name)}](${repo.url}) - ${repo.stars} stars`); + }); + + lines.push(""); + lines.push("## Key Metrics"); + lines.push(`- Scorable repositories: ${result.metrics.scorableRepoCount}`); + lines.push(`- Total stars/forks/watchers: ${result.metrics.totalStars}/${result.metrics.totalForks}/${result.metrics.totalWatchers}`); + lines.push(`- README coverage (sampled): ${(result.metrics.readmeCoverage * 100).toFixed(1)}%`); + lines.push(`- Repositories updated in 30d/90d: ${result.metrics.reposUpdated30d}/${result.metrics.reposUpdated90d}`); + lines.push(`- Days since last push: ${result.metrics.daysSinceLastPush}`); + lines.push(`- Authored PRs/issues: ${result.metrics.authoredPRCount}/${result.metrics.authoredIssueCount}`); + lines.push(`- Unique languages: ${result.metrics.uniqueLanguages}`); + lines.push(`- Dominant language share: ${formatPercent(result.metrics.dominantLanguageShare)} (${result.metrics.dominantLanguage})`); + lines.push(`- Activity buckets (0-30/31-90/91-180/181+ days): ${result.metrics.activityBuckets.updated30d}/${result.metrics.activityBuckets.updated31to90d}/${result.metrics.activityBuckets.updated91to180d}/${result.metrics.activityBuckets.updated181plus}`); + + return lines.join("\n"); +} diff --git a/src/ui/charts.js b/src/ui/charts.js new file mode 100644 index 000000000..1e24ed912 --- /dev/null +++ b/src/ui/charts.js @@ -0,0 +1,179 @@ +import { getCssVar } from "../utils/core.js"; +import { getNamedSubscore } from "../config/constants.js"; + +export function renderCharts(result, state, ui) { + if ( + typeof Chart === "undefined" || + !ui.languageChart || + !ui.importanceChart || + !ui.subscoreRadarChart || + !ui.activityChart + ) { + return; + } + + const chartColors = [ + getCssVar("--chart-1"), + getCssVar("--chart-2"), + getCssVar("--chart-3"), + getCssVar("--chart-4"), + getCssVar("--chart-5"), + getCssVar("--chart-6") + ]; + + const axisColor = getCssVar("--muted"); + const borderColor = getCssVar("--border"); + + const languageEntries = Object.entries(result.languageTotals) + .sort((a, b) => b[1] - a[1]) + .slice(0, 8); + + const languageLabels = languageEntries.map(([label]) => label); + const languageValues = languageEntries.map(([, value]) => value); + const hasLanguageData = languageLabels.length > 0; + + const languageData = { + labels: hasLanguageData ? languageLabels : ["No data"], + datasets: [{ + data: hasLanguageData ? languageValues : [1], + backgroundColor: hasLanguageData ? chartColors : [borderColor], + borderWidth: 1, + borderColor + }] + }; + + destroyChart(state.charts.language); + state.charts.language = new Chart(ui.languageChart, { + type: "doughnut", + data: languageData, + options: { + maintainAspectRatio: false, + plugins: { + legend: { + labels: { color: axisColor } + } + } + } + }); + + const importanceRepos = result.rankedRepos.slice(0, 10); + destroyChart(state.charts.importance); + state.charts.importance = new Chart(ui.importanceChart, { + type: "bar", + data: { + labels: importanceRepos.map((repo) => repo.name), + datasets: [{ + label: "Importance", + data: importanceRepos.map((repo) => repo.importance), + backgroundColor: chartColors[0], + borderColor: chartColors[1], + borderWidth: 1 + }] + }, + options: { + maintainAspectRatio: false, + scales: { + x: { + ticks: { color: axisColor }, + grid: { color: borderColor } + }, + y: { + beginAtZero: true, + max: 100, + ticks: { color: axisColor }, + grid: { color: borderColor } + } + }, + plugins: { + legend: { + labels: { color: axisColor } + } + } + } + }); + + const radarLabels = Object.keys(result.subscores).map((key) => getNamedSubscore(key)); + const radarValues = Object.values(result.subscores); + + destroyChart(state.charts.subscoreRadar); + state.charts.subscoreRadar = new Chart(ui.subscoreRadarChart, { + type: "radar", + data: { + labels: radarLabels, + datasets: [{ + label: "Portfolio Dimensions", + data: radarValues, + backgroundColor: `${chartColors[0]}40`, + borderColor: chartColors[0], + borderWidth: 2, + pointBackgroundColor: chartColors[1] + }] + }, + options: { + maintainAspectRatio: false, + scales: { + r: { + min: 0, + max: 100, + ticks: { color: axisColor, backdropColor: "transparent" }, + angleLines: { color: borderColor }, + grid: { color: borderColor }, + pointLabels: { color: axisColor } + } + }, + plugins: { + legend: { + labels: { color: axisColor } + } + } + } + }); + + const bucketLabels = ["0-30d", "31-90d", "91-180d", "181d+"]; + const bucketValues = [ + result.metrics.activityBuckets.updated30d, + result.metrics.activityBuckets.updated31to90d, + result.metrics.activityBuckets.updated91to180d, + result.metrics.activityBuckets.updated181plus + ]; + + destroyChart(state.charts.activity); + state.charts.activity = new Chart(ui.activityChart, { + type: "bar", + data: { + labels: bucketLabels, + datasets: [{ + label: "Repo Count", + data: bucketValues, + backgroundColor: [chartColors[1], chartColors[2], chartColors[3], chartColors[4]], + borderWidth: 1, + borderColor + }] + }, + options: { + maintainAspectRatio: false, + scales: { + x: { + ticks: { color: axisColor }, + grid: { color: borderColor } + }, + y: { + beginAtZero: true, + ticks: { color: axisColor, precision: 0 }, + grid: { color: borderColor } + } + }, + plugins: { + legend: { + labels: { color: axisColor } + } + } + } + }); +} + +function destroyChart(chart) { + if (chart) { + chart.destroy(); + } +} diff --git a/src/ui/elements.js b/src/ui/elements.js new file mode 100644 index 000000000..2930be0c6 --- /dev/null +++ b/src/ui/elements.js @@ -0,0 +1,62 @@ +export function getUiElements() { + return { + profileInput: document.getElementById("profileInput"), + analyzeBtn: document.getElementById("analyzeBtn"), + themeToggleBtn: document.getElementById("themeToggleBtn"), + downloadReportBtn: document.getElementById("downloadReportBtn"), + + loadingState: document.getElementById("loadingState"), + loadingText: document.getElementById("loadingText"), + errorBanner: document.getElementById("errorBanner"), + rateLimitBanner: document.getElementById("rateLimitBanner"), + + avatarImg: document.getElementById("avatarImg"), + profileName: document.getElementById("profileName"), + profileHandle: document.getElementById("profileHandle"), + profileLink: document.getElementById("profileLink"), + profileBio: document.getElementById("profileBio"), + + statRepos: document.getElementById("statRepos"), + statFollowers: document.getElementById("statFollowers"), + statFollowing: document.getElementById("statFollowing"), + statLastPush: document.getElementById("statLastPush"), + statPrCount: document.getElementById("statPrCount"), + statIssueCount: document.getElementById("statIssueCount"), + + pinnedSourceBadge: document.getElementById("pinnedSourceBadge"), + pinnedReposList: document.getElementById("pinnedReposList"), + historyList: document.getElementById("historyList"), + + overallScoreRing: document.getElementById("overallScoreRing"), + overallScore: document.getElementById("overallScore"), + scoreGrade: document.getElementById("scoreGrade"), + scoreSummary: document.getElementById("scoreSummary"), + hireabilityScore: document.getElementById("hireabilityScore"), + hireabilityHint: document.getElementById("hireabilityHint"), + readinessLevel: document.getElementById("readinessLevel"), + readinessHint: document.getElementById("readinessHint"), + readinessBar: document.getElementById("readinessBar"), + + strengthsList: document.getElementById("strengthsList"), + redFlagsList: document.getElementById("redFlagsList"), + suggestionsList: document.getElementById("suggestionsList"), + hiddenRisksList: document.getElementById("hiddenRisksList"), + + aiRecruiterVerdict: document.getElementById("aiRecruiterVerdict"), + aiRecruiterSummary: document.getElementById("aiRecruiterSummary"), + aiRecruiterSignals: document.getElementById("aiRecruiterSignals"), + + careerPathTitle: document.getElementById("careerPathTitle"), + careerPathSummary: document.getElementById("careerPathSummary"), + careerConfidence: document.getElementById("careerConfidence"), + careerSkillsList: document.getElementById("careerSkillsList"), + roadmapList: document.getElementById("roadmapList"), + + repoRankingTable: document.getElementById("repoRankingTable"), + + languageChart: document.getElementById("languageChart"), + importanceChart: document.getElementById("importanceChart"), + subscoreRadarChart: document.getElementById("subscoreRadarChart"), + activityChart: document.getElementById("activityChart") + }; +} diff --git a/src/ui/render.js b/src/ui/render.js new file mode 100644 index 000000000..09cb02e7f --- /dev/null +++ b/src/ui/render.js @@ -0,0 +1,349 @@ +import { SUBSCORE_ID_MAP, SCORING_EXPLAIN_ID_MAP } from "../config/constants.js"; +import { + clearChildren, + appendEmptyState, + formatCompactNumber, + formatDate, + formatPercent, + getSeverity, + severityToChipClass, + severityColor +} from "../utils/core.js"; + +export function renderInitialState(ui) { + renderPinnedRepos({ source: "fallback", items: [] }, ui); + + renderInsightList(ui.strengthsList, ["No profile analyzed yet."], "neutral"); + renderInsightList(ui.redFlagsList, ["No profile analyzed yet."], "neutral"); + renderInsightList(ui.suggestionsList, ["No profile analyzed yet."], "neutral"); + renderInsightList(ui.hiddenRisksList, ["No hidden risk analysis yet."], "neutral"); + renderInsightList(ui.aiRecruiterSignals, ["Run an analysis to simulate recruiter feedback."], "neutral"); + + renderRoadmap(["Roadmap will appear after analysis."], ui); + renderCareerPath( + { + title: "Run an analysis to generate role fit", + confidence: 0, + summary: "Career path recommendations are generated from your public GitHub portfolio signals.", + nextSkills: ["Add repositories and analyze profile to unlock recommendations."] + }, + ui + ); + + ui.aiRecruiterVerdict.textContent = "Pending"; + ui.aiRecruiterVerdict.className = "chip chip-neutral"; + ui.aiRecruiterSummary.textContent = "Run an analysis to get recruiter-style interview feedback simulation."; + + ui.hireabilityScore.textContent = "--"; + ui.hireabilityHint.textContent = "Weighted recruiter-fit index"; + ui.readinessLevel.textContent = "--"; + ui.readinessHint.textContent = "Analyze a profile to classify readiness"; + ui.readinessBar.style.width = "0%"; +} + +export function renderAnalysis(result, ui) { + renderProfile(result, ui); + renderScore(result, ui); + renderSubscores(result.subscores); + renderScoringTransparency(result); + renderPinnedRepos(result.pinnedRepos, ui); + renderRecruiterSimulation(result.recruiterSimulation, ui); + renderInsightList(ui.strengthsList, result.strengths, "good"); + renderInsightList(ui.redFlagsList, result.redFlags, "risk"); + renderInsightList(ui.suggestionsList, result.suggestions, "warn"); + renderInsightList(ui.hiddenRisksList, result.hiddenRisks, "risk"); + renderCareerPath(result.careerPath, ui); + renderRoadmap(result.improvementRoadmap, ui); + renderRepoRanking(result.rankedRepos, ui); +} + +export function renderHistory(history, ui, onSelect) { + clearChildren(ui.historyList); + + if (!history.length) { + const empty = document.createElement("p"); + empty.className = "empty-state"; + empty.textContent = "No recent searches yet."; + ui.historyList.appendChild(empty); + return; + } + + history.forEach((username) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = "history-chip"; + button.textContent = username; + button.addEventListener("click", () => onSelect(username)); + + ui.historyList.appendChild(button); + }); +} + +function renderProfile(result, ui) { + const profile = result.profile; + const metrics = result.metrics; + + ui.avatarImg.src = profile.avatarUrl || "Images/logo.png"; + ui.avatarImg.alt = `${profile.login} avatar`; + + ui.profileName.textContent = profile.name; + ui.profileHandle.textContent = `@${profile.login}`; + + ui.profileLink.href = profile.htmlUrl; + ui.profileLink.textContent = profile.htmlUrl; + + ui.profileBio.textContent = profile.bio || "No bio provided."; + + ui.statRepos.textContent = String(profile.publicRepos); + ui.statFollowers.textContent = formatCompactNumber(profile.followers); + ui.statFollowing.textContent = formatCompactNumber(profile.following); + ui.statLastPush.textContent = metrics.lastPushDate ? formatDate(metrics.lastPushDate) : "No push data"; + ui.statPrCount.textContent = formatCompactNumber(metrics.authoredPRCount); + ui.statIssueCount.textContent = formatCompactNumber(metrics.authoredIssueCount); +} + +function renderScore(result, ui) { + const severity = getSeverity(result.overallScore); + + ui.overallScore.textContent = String(result.overallScore); + ui.overallScoreRing.style.setProperty("--score-value", String(result.overallScore)); + ui.overallScoreRing.setAttribute("data-level", severity); + ui.scoreGrade.textContent = `Grade ${result.grade}`; + ui.scoreSummary.textContent = result.scoreSummary; + + const hireabilitySeverity = getSeverity(result.hireabilityScore); + ui.hireabilityScore.textContent = `${result.hireabilityScore}/100`; + ui.hireabilityScore.style.color = severityColor(hireabilitySeverity); + ui.hireabilityHint.textContent = "Calibrated from score, impact, recency, and hidden-risk penalties."; + + ui.readinessLevel.textContent = result.readiness.label; + ui.readinessLevel.style.color = severityColor(result.readiness.severity); + ui.readinessHint.textContent = result.readiness.summary; + ui.readinessBar.style.width = `${result.readiness.percent}%`; +} + +function renderSubscores(subscores) { + Object.entries(SUBSCORE_ID_MAP).forEach(([key, elementId]) => { + const element = document.getElementById(elementId); + const value = subscores[key]; + const severity = getSeverity(value); + + element.textContent = `${value}/100`; + element.className = `chip ${severityToChipClass(severity)}`; + }); +} + +function renderScoringTransparency(result) { + const { subscores, metrics } = result; + const details = { + documentationQuality: `README ${formatPercent(metrics.readmeCoverage)}, Desc ${formatPercent(metrics.descriptionCoverage)}`, + codeActivityConsistency: `${metrics.activeMonthsLast6}/6 months, ${formatPercent(metrics.reposUpdated90dRatio)} updated`, + projectPopularity: `Stars ${metrics.starsPerRepo.toFixed(1)}/repo, Forks ${metrics.forksPerRepo.toFixed(1)}/repo`, + repositoryCompleteness: `Non-empty ${formatPercent(metrics.nonEmptyRepoRatio)}, Topics ${formatPercent(metrics.topicsRatio)}`, + languageDiversity: `${metrics.uniqueLanguages} langs, Entropy ${metrics.normalizedShannonEntropy.toFixed(2)}`, + recentActivity: `${metrics.daysSinceLastPush}d last push, ${formatPercent(metrics.reposUpdated30dRatio)} updated`, + impactSignals: `Top repo ⭐ ${metrics.topRepoStars}, PRs ${metrics.authoredPRCount}, Issues ${metrics.authoredIssueCount}` + }; + + Object.entries(SCORING_EXPLAIN_ID_MAP).forEach(([key, elementId]) => { + const element = document.getElementById(elementId); + if (!element) { + return; + } + + const score = subscores[key]; + const severity = getSeverity(score); + element.textContent = `${score}/100 • ${details[key]}`; + element.className = `chip ${severityToChipClass(severity)}`; + }); +} + +function renderRecruiterSimulation(simulation, ui) { + if (!simulation) { + ui.aiRecruiterVerdict.textContent = "Pending"; + ui.aiRecruiterVerdict.className = "chip chip-neutral"; + ui.aiRecruiterSummary.textContent = "Run an analysis to get recruiter-style interview feedback simulation."; + renderInsightList(ui.aiRecruiterSignals, ["No recruiter simulation available."], "neutral"); + return; + } + + ui.aiRecruiterVerdict.textContent = simulation.verdict; + ui.aiRecruiterVerdict.className = `chip ${severityToChipClass(simulation.level || "warn")}`; + ui.aiRecruiterSummary.textContent = simulation.summary; + renderInsightList(ui.aiRecruiterSignals, simulation.signals, simulation.level || "warn"); +} + +function renderCareerPath(careerPath, ui) { + if (!careerPath) { + ui.careerPathTitle.textContent = "No career path recommendation"; + ui.careerPathSummary.textContent = "Run an analysis to unlock role fit recommendations."; + ui.careerConfidence.textContent = "--"; + ui.careerConfidence.className = "chip chip-neutral"; + renderInsightList(ui.careerSkillsList, ["No recommendations yet."], "neutral"); + return; + } + + ui.careerPathTitle.textContent = careerPath.title; + ui.careerPathSummary.textContent = careerPath.summary; + ui.careerConfidence.textContent = `${careerPath.confidence}% confidence`; + ui.careerConfidence.className = `chip ${severityToChipClass(getSeverity(careerPath.confidence))}`; + renderInsightList(ui.careerSkillsList, careerPath.nextSkills, "warn"); +} + +function renderRoadmap(roadmapItems, ui) { + clearChildren(ui.roadmapList); + + if (!Array.isArray(roadmapItems) || !roadmapItems.length) { + const li = document.createElement("li"); + li.textContent = "No roadmap available."; + ui.roadmapList.appendChild(li); + return; + } + + roadmapItems.forEach((item) => { + const li = document.createElement("li"); + li.textContent = item; + ui.roadmapList.appendChild(li); + }); +} + +function renderPinnedRepos(pinnedRepos, ui) { + clearChildren(ui.pinnedReposList); + + ui.pinnedSourceBadge.textContent = pinnedRepos.source; + ui.pinnedSourceBadge.className = `chip ${pinnedRepos.source === "graphql" ? "chip-good" : "chip-warn"}`; + + if (!pinnedRepos.items.length) { + appendEmptyState(ui.pinnedReposList, "No pinned repositories available."); + return; + } + + pinnedRepos.items.forEach((repo) => { + const li = document.createElement("li"); + li.className = "repo-item"; + + const left = document.createElement("div"); + const link = document.createElement("a"); + link.href = repo.url; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + link.textContent = repo.name; + link.className = "repo-link"; + + left.appendChild(link); + + const meta = document.createElement("div"); + meta.className = "repo-item-meta"; + meta.textContent = `Stars: ${formatCompactNumber(repo.stars)}`; + + li.appendChild(left); + li.appendChild(meta); + + ui.pinnedReposList.appendChild(li); + }); +} + +export function renderInsightList(container, items, tone) { + clearChildren(container); + + if (!items || !items.length) { + appendEmptyState(container, "No insights available."); + return; + } + + items.forEach((item) => { + const li = document.createElement("li"); + li.textContent = item; + + if (tone === "good") { + li.classList.add("chip-good"); + } else if (tone === "warn") { + li.classList.add("chip-warn"); + } else if (tone === "risk") { + li.classList.add("chip-risk"); + } + + container.appendChild(li); + }); +} + +function renderRepoRanking(rankedRepos, ui) { + clearChildren(ui.repoRankingTable); + + if (!rankedRepos.length) { + const row = document.createElement("tr"); + const cell = document.createElement("td"); + cell.colSpan = 5; + cell.textContent = "No repositories available for ranking."; + cell.className = "empty-state"; + row.appendChild(cell); + ui.repoRankingTable.appendChild(row); + return; + } + + rankedRepos.slice(0, 15).forEach((repo) => { + const row = document.createElement("tr"); + + const repoCell = document.createElement("td"); + const link = document.createElement("a"); + link.href = repo.url; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + link.textContent = repo.name; + link.className = "repo-link"; + + const langMeta = document.createElement("div"); + langMeta.className = "repo-item-meta"; + langMeta.textContent = repo.language; + + repoCell.appendChild(link); + repoCell.appendChild(langMeta); + + const importanceCell = document.createElement("td"); + const wrap = document.createElement("div"); + wrap.className = "importance-wrap"; + + const meter = document.createElement("div"); + meter.className = "importance-meter"; + const fill = document.createElement("span"); + fill.style.width = `${repo.importance}%`; + meter.appendChild(fill); + + const importanceText = document.createElement("span"); + importanceText.textContent = String(repo.importance); + + wrap.appendChild(meter); + wrap.appendChild(importanceText); + importanceCell.appendChild(wrap); + + const statsCell = document.createElement("td"); + statsCell.textContent = `${repo.stars} / ${repo.forks} / ${repo.watchers}`; + + const pushedCell = document.createElement("td"); + pushedCell.textContent = repo.pushedAt ? formatDate(repo.pushedAt) : "Unknown"; + + const readmeCell = document.createElement("td"); + const readmeChip = document.createElement("span"); + + if (repo.readmeKnown && repo.hasReadme) { + readmeChip.textContent = "Yes"; + readmeChip.className = "chip chip-good"; + } else if (repo.readmeKnown && !repo.hasReadme) { + readmeChip.textContent = "No"; + readmeChip.className = "chip chip-risk"; + } else { + readmeChip.textContent = "Unknown"; + readmeChip.className = "chip chip-neutral"; + } + + readmeCell.appendChild(readmeChip); + + row.appendChild(repoCell); + row.appendChild(importanceCell); + row.appendChild(statsCell); + row.appendChild(pushedCell); + row.appendChild(readmeCell); + + ui.repoRankingTable.appendChild(row); + }); +} diff --git a/src/utils/core.js b/src/utils/core.js new file mode 100644 index 000000000..44ea19410 --- /dev/null +++ b/src/utils/core.js @@ -0,0 +1,160 @@ +export function cap01(value) { + if (!Number.isFinite(value)) { + return 0; + } + return Math.max(0, Math.min(1, value)); +} + +export function clampToRange(value, min, max) { + return Math.min(max, Math.max(min, value)); +} + +export function safeRatio(numerator, denominator) { + if (!denominator) { + return 0; + } + return numerator / denominator; +} + +export function scoreFromRatio(ratio) { + return clampToRange(Math.round(cap01(ratio) * 100), 0, 100); +} + +export function formatDate(value) { + if (!value) { + return "Unknown"; + } + + const date = new Date(value); + if (Number.isNaN(date.getTime())) { + return "Unknown"; + } + + return date.toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric" + }); +} + +export function daysSinceDate(value, nowMs = Date.now()) { + if (!value) { + return Number.POSITIVE_INFINITY; + } + + const date = new Date(value); + if (Number.isNaN(date.getTime())) { + return Number.POSITIVE_INFINITY; + } + + return Math.floor((nowMs - date.getTime()) / 86400000); +} + +export function formatCompactNumber(value) { + const num = Number(value) || 0; + return new Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 1 }).format(num); +} + +export function formatPercent(value) { + return `${Math.round(cap01(value) * 100)}%`; +} + +export function joinRepoNames(names) { + if (!names.length) { + return "target repositories"; + } + return names.map((name) => `\`${name}\``).join(", "); +} + +export function dedupe(items) { + return items.filter((item, index) => items.indexOf(item) === index); +} + +export function readJsonStorage(key, fallback) { + try { + const raw = localStorage.getItem(key); + if (!raw) { + return fallback; + } + return JSON.parse(raw); + } catch { + return fallback; + } +} + +export function clearChildren(node) { + while (node.firstChild) { + node.removeChild(node.firstChild); + } +} + +export function appendEmptyState(container, text) { + const li = document.createElement("li"); + li.className = "empty-state"; + li.textContent = text; + container.appendChild(li); +} + +export function getCssVar(name) { + return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); +} + +export function escapeMarkdown(text) { + return String(text).replace(/[|]/g, "\\|"); +} + +export function getSeverity(score) { + if (score >= 70) { + return "good"; + } + if (score >= 40) { + return "warn"; + } + return "risk"; +} + +export function severityToChipClass(severity) { + if (severity === "good") { + return "chip-good"; + } + if (severity === "warn") { + return "chip-warn"; + } + return "chip-risk"; +} + +export function severityColor(severity) { + if (severity === "good") { + return getCssVar("--good"); + } + if (severity === "warn") { + return getCssVar("--warn"); + } + return getCssVar("--risk"); +} + +export async function mapWithConcurrency(items, concurrency, worker) { + if (!items.length) { + return []; + } + + const results = new Array(items.length); + let currentIndex = 0; + + const runner = async () => { + while (currentIndex < items.length) { + const index = currentIndex; + currentIndex += 1; + results[index] = await worker(items[index], index); + } + }; + + const workers = []; + const count = Math.min(concurrency, items.length); + for (let i = 0; i < count; i += 1) { + workers.push(runner()); + } + + await Promise.all(workers); + return results; +} diff --git a/style.css b/style.css index 4c3599fda..ab6bd0c69 100644 --- a/style.css +++ b/style.css @@ -1,18 +1,22 @@ :root { - --bg: #f3f7ff; - --bg-gradient-a: #f8fbff; - --bg-gradient-b: #e8f0ff; - --surface: #ffffff; - --surface-soft: #f5f8ff; - --text: #1e2a39; - --muted: #5f6f83; - --border: #dce6f8; - --accent: #1f6feb; - --accent-strong: #144fb6; + --bg: #f1f6ff; + --bg-gradient-a: #f9fcff; + --bg-gradient-b: #e4efff; + --surface: #ffffffcc; + --surface-solid: #ffffff; + --surface-soft: #f3f7ff; + --text: #19253a; + --muted: #5a6f8f; + --border: #d3e0f6; + --accent: #0f67f5; + --accent-strong: #0e4dc8; + --accent-soft: #81bcff; + --accent-glow: rgba(15, 103, 245, 0.24); --good: #177245; --warn: #a06000; --risk: #a3213d; - --shadow: 0 20px 45px rgba(21, 38, 74, 0.1); + --shadow: 0 24px 56px rgba(17, 40, 84, 0.12); + --hero-shadow: 0 32px 64px rgba(20, 60, 131, 0.18); --chart-1: #1f6feb; --chart-2: #0f9d58; --chart-3: #f2994a; @@ -22,20 +26,24 @@ } :root[data-theme="dark"] { - --bg: #0f1624; - --bg-gradient-a: #121b2c; - --bg-gradient-b: #0b111d; - --surface: #182235; - --surface-soft: #1e2b42; - --text: #ebf1ff; - --muted: #98aac4; - --border: #2a3a58; + --bg: #091321; + --bg-gradient-a: #0e1d33; + --bg-gradient-b: #060d17; + --surface: #15253bcc; + --surface-solid: #15253b; + --surface-soft: #1a304c; + --text: #edf3ff; + --muted: #93aacc; + --border: #28425f; --accent: #58a6ff; --accent-strong: #3f8ce8; + --accent-soft: #8ec4ff; + --accent-glow: rgba(88, 166, 255, 0.26); --good: #4fd694; --warn: #f0ad4e; --risk: #ff7b91; - --shadow: 0 24px 50px rgba(0, 0, 0, 0.4); + --shadow: 0 28px 64px rgba(0, 0, 0, 0.42); + --hero-shadow: 0 34px 72px rgba(0, 0, 0, 0.5); --chart-1: #58a6ff; --chart-2: #4fd694; --chart-3: #f0ad4e; @@ -54,7 +62,8 @@ body { font-family: "Manrope", "Segoe UI", sans-serif; color: var(--text); background: - radial-gradient(circle at 5% 0%, rgba(31, 111, 235, 0.12), transparent 35%), + radial-gradient(circle at 10% -5%, var(--accent-glow), transparent 34%), + radial-gradient(circle at 92% 10%, rgba(22, 190, 255, 0.11), transparent 28%), linear-gradient(180deg, var(--bg-gradient-a) 0%, var(--bg-gradient-b) 100%); } @@ -81,12 +90,17 @@ a { } .app-shell { - width: min(1200px, 100% - 2rem); - margin: 1.2rem auto 2.4rem; + width: min(1280px, 100% - 2rem); + margin: 1.2rem auto 2.8rem; } .topbar { margin-bottom: 1rem; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; } .brand { @@ -116,10 +130,131 @@ a { font-size: 0.9rem; } +.topbar-pills { + display: flex; + gap: 0.45rem; + flex-wrap: wrap; +} + +.top-pill { + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border)); + background: color-mix(in srgb, var(--accent) 10%, var(--surface-soft)); + color: var(--accent-strong); + font-size: 0.7rem; + padding: 0.22rem 0.58rem; +} + +.hero-shell { + display: grid; + grid-template-columns: minmax(360px, 1.1fr) minmax(260px, 0.75fr); + gap: 0.9rem; + margin-bottom: 0.9rem; +} + +.hero-copy, +.hero-mascot { + position: relative; + overflow: hidden; +} + +.hero-copy::before { + content: ""; + position: absolute; + width: 260px; + height: 260px; + right: -120px; + top: -130px; + border-radius: 50%; + background: radial-gradient(circle, color-mix(in srgb, var(--accent) 20%, transparent), transparent 70%); +} + +.eyebrow { + margin: 0; + text-transform: uppercase; + letter-spacing: 0.12em; + font-size: 0.68rem; + color: var(--accent-strong); +} + +.hero-copy h2 { + margin: 0.42rem 0 0; + font-size: clamp(1.35rem, 3vw, 2.2rem); + line-height: 1.18; + max-width: 18ch; +} + +.hero-subtitle { + margin: 0.62rem 0 0; + color: var(--muted); + line-height: 1.5; + max-width: 58ch; + font-size: 0.9rem; +} + +.hero-points { + display: flex; + gap: 0.45rem; + margin-top: 0.85rem; + flex-wrap: wrap; +} + +.hero-chip { + font-size: 0.72rem; + border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--border)); + color: var(--accent-strong); + background: color-mix(in srgb, var(--accent) 10%, var(--surface-soft)); + border-radius: 999px; + padding: 0.28rem 0.62rem; +} + +.hero-mascot { + background: + radial-gradient(circle at 85% 10%, color-mix(in srgb, var(--accent-soft) 38%, transparent), transparent 42%), + linear-gradient(165deg, color-mix(in srgb, var(--surface-soft) 78%, transparent), var(--surface)); + box-shadow: var(--hero-shadow); + display: grid; + place-items: center; + align-content: center; + gap: 0.35rem; +} + +.mascot-wrap { + width: min(240px, 100%); + position: relative; +} + +.mascot-art { + width: 100%; + height: auto; + display: block; + position: relative; + z-index: 1; +} + +.mascot-glow { + position: absolute; + inset: auto 12% -8% 12%; + height: 34px; + border-radius: 50%; + background: color-mix(in srgb, var(--accent) 28%, transparent); + filter: blur(10px); +} + +.mascot-line { + margin: 0; + font-size: 0.76rem; + color: var(--muted); + text-align: center; + max-width: 28ch; +} + .card { background: var(--surface); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); border: 1px solid var(--border); - border-radius: 16px; + border-radius: 18px; box-shadow: var(--shadow); padding: 1rem; animation: card-in 220ms ease-out; @@ -128,8 +263,8 @@ a { .card:hover { transform: translateY(-2px); - border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); - box-shadow: 0 28px 52px rgba(16, 33, 70, 0.14); + border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); + box-shadow: 0 30px 58px rgba(16, 38, 83, 0.18); } @keyframes card-in { @@ -145,11 +280,14 @@ a { .controls-card { margin-bottom: 0.85rem; + background: + radial-gradient(circle at 85% 0%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 38%), + var(--surface); } .controls-grid { display: grid; - grid-template-columns: 1.2fr 1fr auto; + grid-template-columns: 1fr auto; gap: 0.8rem; align-items: end; } @@ -163,6 +301,50 @@ a { .field label { font-size: 0.78rem; color: var(--muted); + display: inline-flex; + align-items: center; + gap: 0.36rem; +} + +.tooltip { + width: 17px; + height: 17px; + border-radius: 50%; + display: inline-grid; + place-items: center; + font-size: 0.62rem; + border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); + color: var(--accent-strong); + background: color-mix(in srgb, var(--accent) 10%, var(--surface-soft)); + cursor: help; + position: relative; + user-select: none; +} + +.tooltip::after { + content: attr(data-tooltip); + position: absolute; + left: 50%; + bottom: calc(100% + 8px); + transform: translateX(-50%); + min-width: 210px; + max-width: 240px; + border-radius: 10px; + border: 1px solid var(--border); + background: var(--surface-solid); + color: var(--text); + padding: 0.4rem 0.5rem; + font-size: 0.68rem; + line-height: 1.35; + box-shadow: var(--shadow); + opacity: 0; + pointer-events: none; + transition: opacity 140ms ease; + z-index: 3; +} + +.tooltip:hover::after { + opacity: 1; } input { @@ -171,8 +353,9 @@ input { border-radius: 10px; background: var(--surface-soft); color: var(--text); - padding: 0.62rem 0.72rem; + padding: 0.68rem 0.75rem; font-size: 0.88rem; + transition: border-color 150ms ease, box-shadow 150ms ease, transform 120ms ease; } input:focus { @@ -203,12 +386,13 @@ input:focus { font-size: 0.78rem; padding: 0.62rem 0.82rem; cursor: pointer; - transition: transform 140ms ease, border-color 140ms ease; + transition: transform 140ms ease, border-color 140ms ease, box-shadow 140ms ease; } .btn:hover:not(:disabled) { transform: translateY(-1px); border-color: var(--accent); + box-shadow: 0 10px 22px color-mix(in srgb, var(--accent) 22%, transparent); } .btn:disabled { @@ -222,6 +406,10 @@ input:focus { color: #ffffff; } +.btn-primary:hover:not(:disabled) { + box-shadow: 0 14px 28px color-mix(in srgb, var(--accent) 44%, transparent); +} + .status-stack { display: grid; gap: 0.5rem; @@ -256,6 +444,76 @@ input:focus { border-color: color-mix(in srgb, var(--warn) 32%, var(--border)); } +.status-success { + color: var(--good); + background: color-mix(in srgb, var(--good) 10%, transparent); + border-color: color-mix(in srgb, var(--good) 34%, var(--border)); +} + +.skeleton-panel { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.8rem; + margin-bottom: 0.9rem; +} + +.skeleton-card { + border: 1px solid var(--border); + border-radius: 16px; + background: var(--surface); + padding: 0.85rem; + box-shadow: var(--shadow); + display: grid; + gap: 0.6rem; +} + +.skeleton-line, +.skeleton-pill { + border-radius: 8px; + position: relative; + overflow: hidden; + background: color-mix(in srgb, var(--surface-soft) 65%, var(--border)); +} + +.skeleton-line::after, +.skeleton-pill::after { + content: ""; + position: absolute; + inset: 0; + transform: translateX(-100%); + background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent-soft) 34%, transparent), transparent); + animation: shimmer 1.2s infinite; +} + +.skeleton-line { + height: 12px; +} + +.skeleton-line-lg { + height: 18px; + width: 72%; +} + +.skeleton-line-sm { + width: 42%; +} + +.skeleton-grid-mini { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.5rem; +} + +.skeleton-pill { + height: 28px; +} + +@keyframes shimmer { + 100% { + transform: translateX(100%); + } +} + .spinner { width: 14px; height: 14px; diff --git a/sw.js b/sw.js index 0cc6a5c14..4e429d7b8 100644 --- a/sw.js +++ b/sw.js @@ -1,10 +1,16 @@ -const CACHE_NAME = "devdetective-v3"; +const CACHE_NAME = "devdetective-v5"; const STATIC_ASSETS = [ "./", "./index.html", "./style.css", - "./script.js", + "./src/main.js", + "./src/config/constants.js", + "./src/ui/elements.js", + "./src/ui/render.js", + "./src/ui/charts.js", + "./src/report/markdown.js", + "./src/utils/core.js", "./manifest.json", "./Images/logo.png", "./Images/favicon-32x32.png", @@ -43,7 +49,7 @@ self.addEventListener("fetch", (event) => { const url = new URL(request.url); - if (url.origin === "https://api.github.com") { + if (url.pathname.startsWith("/api/") || url.origin === "https://api.github.com") { event.respondWith(fetch(request)); return; } diff --git a/vercel.json b/vercel.json new file mode 100644 index 000000000..61cf13c21 --- /dev/null +++ b/vercel.json @@ -0,0 +1,71 @@ +{ + "functions": { + "api/analyze.js": { + "maxDuration": 15 + } + }, + "headers": [ + { + "source": "/Images/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=31536000, immutable" + } + ] + }, + { + "source": "/style.css", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=3600" + } + ] + }, + { + "source": "/src/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=3600" + } + ] + }, + { + "source": "/manifest.json", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=86400" + } + ] + }, + { + "source": "/sw.js", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=0, must-revalidate" + } + ] + }, + { + "source": "/(.*)", + "headers": [ + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "key": "Referrer-Policy", + "value": "strict-origin-when-cross-origin" + } + ] + } + ] +}