@@ -7,6 +7,15 @@ const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
77const ORG_NAME = 'universal-tool-calling-protocol' ;
88const OUTPUT_FILE = path . join ( __dirname , '../src/data/contributors.json' ) ;
99
10+ // Configuration for efficient API batching
11+ const BATCH_CONFIG = {
12+ COMMIT_DETAILS_BATCH_SIZE : 20 , // Process commits in batches of 20
13+ CONCURRENT_REQUESTS : 5 , // Max concurrent API requests
14+ RETRY_ATTEMPTS : 3 , // Number of retry attempts for failed requests
15+ RATE_LIMIT_DELAY : 100 , // Base delay between requests (ms)
16+ COMMIT_ANALYSIS_LIMIT : 1000 , // Max commits to analyze (0 = no limit)
17+ } ;
18+
1019const githubApi = async ( endpoint ) => {
1120 const headers = {
1221 'Accept' : 'application/vnd.github.v3+json' ,
@@ -50,6 +59,82 @@ const fetchContributorsForRepo = async (repoName) => {
5059 }
5160} ;
5261
62+ const fetchCommitDetails = async ( repoName , commitSha ) => {
63+ try {
64+ const commit = await githubApi ( `/repos/${ ORG_NAME } /${ repoName } /commits/${ commitSha } ` ) ;
65+ return {
66+ additions : commit . stats ?. additions || 0 ,
67+ deletions : commit . stats ?. deletions || 0 ,
68+ total : commit . stats ?. total || 0
69+ } ;
70+ } catch ( error ) {
71+ console . warn ( `Could not fetch commit details for ${ commitSha } :` , error . message ) ;
72+ return { additions : 0 , deletions : 0 , total : 0 } ;
73+ }
74+ } ;
75+
76+ // Helper function for batched API calls with retry logic
77+ const batchedApiCall = async ( items , apiCallFn , batchSize = BATCH_CONFIG . COMMIT_DETAILS_BATCH_SIZE ) => {
78+ const results = [ ] ;
79+ const batches = [ ] ;
80+
81+ // Split items into batches
82+ for ( let i = 0 ; i < items . length ; i += batchSize ) {
83+ batches . push ( items . slice ( i , i + batchSize ) ) ;
84+ }
85+
86+ console . log ( ` 📦 Processing ${ items . length } items in ${ batches . length } batches...` ) ;
87+
88+ for ( let batchIndex = 0 ; batchIndex < batches . length ; batchIndex ++ ) {
89+ const batch = batches [ batchIndex ] ;
90+ console . log ( ` ⚡ Processing batch ${ batchIndex + 1 } /${ batches . length } (${ batch . length } items)...` ) ;
91+
92+ // Process items in current batch concurrently
93+ const batchPromises = batch . map ( async ( item ) => {
94+ let retryCount = 0 ;
95+ while ( retryCount < BATCH_CONFIG . RETRY_ATTEMPTS ) {
96+ try {
97+ const result = await apiCallFn ( item ) ;
98+ return result ;
99+ } catch ( error ) {
100+ retryCount ++ ;
101+ if ( retryCount === BATCH_CONFIG . RETRY_ATTEMPTS ) {
102+ console . warn ( ` ⚠️ Failed after ${ BATCH_CONFIG . RETRY_ATTEMPTS } attempts:` , error . message ) ;
103+ return null ;
104+ }
105+ // Exponential backoff for retries
106+ await new Promise ( resolve => setTimeout ( resolve , BATCH_CONFIG . RATE_LIMIT_DELAY * Math . pow ( 2 , retryCount ) ) ) ;
107+ }
108+ }
109+ } ) ;
110+
111+ const batchResults = await Promise . all ( batchPromises ) ;
112+ results . push ( ...batchResults . filter ( Boolean ) ) ; // Filter out null results from failures
113+
114+ // Rate limiting between batches
115+ if ( batchIndex < batches . length - 1 ) {
116+ await new Promise ( resolve => setTimeout ( resolve , BATCH_CONFIG . RATE_LIMIT_DELAY ) ) ;
117+ }
118+ }
119+
120+ return results ;
121+ } ;
122+
123+ // Helper function to create progress reporting
124+ const createProgressReporter = ( total , operation ) => {
125+ let processed = 0 ;
126+ return {
127+ update : ( ) => {
128+ processed ++ ;
129+ const percentage = Math . round ( ( processed / total ) * 100 ) ;
130+ console . log ( ` 📊 ${ operation } : ${ processed } /${ total } (${ percentage } %)` ) ;
131+ } ,
132+ finish : ( ) => {
133+ console . log ( ` ✅ ${ operation } : Completed all ${ total } items` ) ;
134+ }
135+ } ;
136+ } ;
137+
53138const fetchUserPRsAndActivity = async ( username , repoName ) => {
54139 try {
55140 // Get PRs for this user in this repo
@@ -60,6 +145,61 @@ const fetchUserPRsAndActivity = async (username, repoName) => {
60145 sixMonthsAgo . setMonth ( sixMonthsAgo . getMonth ( ) - 6 ) ;
61146 const commits = await githubApi ( `/repos/${ ORG_NAME } /${ repoName } /commits?author=${ username } &since=${ sixMonthsAgo . toISOString ( ) } &per_page=100` ) ;
62147
148+ // Get ALL commits by this user for line change statistics
149+ let allCommits = [ ] ;
150+ let page = 1 ;
151+ let hasMore = true ;
152+
153+ console . log ( ` 📊 Fetching all commits for line statistics...` ) ;
154+ while ( hasMore && allCommits . length < ( BATCH_CONFIG . COMMIT_ANALYSIS_LIMIT || Infinity ) ) { // Limit to prevent excessive API calls
155+ try {
156+ const commitPage = await githubApi ( `/repos/${ ORG_NAME } /${ repoName } /commits?author=${ username } &per_page=100&page=${ page } ` ) ;
157+
158+ if ( commitPage . length === 0 ) {
159+ hasMore = false ;
160+ } else {
161+ allCommits = allCommits . concat ( commitPage ) ;
162+ page ++ ;
163+
164+ // Rate limiting for commit fetching
165+ await new Promise ( resolve => setTimeout ( resolve , 100 ) ) ;
166+ }
167+ } catch ( error ) {
168+ console . warn ( ` Could not fetch commits page ${ page } for ${ username } :` , error . message ) ;
169+ hasMore = false ;
170+ }
171+ }
172+
173+ // Analyze ALL commits with efficient batching (improved from 100 commit limit)
174+ let totalAdditions = 0 ;
175+ let totalDeletions = 0 ;
176+ let totalChanges = 0 ;
177+
178+ // Apply configurable limit if set (0 = no limit)
179+ const commitsToAnalyze = BATCH_CONFIG . COMMIT_ANALYSIS_LIMIT > 0
180+ ? allCommits . slice ( 0 , BATCH_CONFIG . COMMIT_ANALYSIS_LIMIT )
181+ : allCommits ;
182+
183+ console . log ( ` 📈 Analyzing ${ commitsToAnalyze . length } commits for line changes (improved batching)...` ) ;
184+
185+ if ( commitsToAnalyze . length > 0 ) {
186+ // Use efficient batching instead of sequential processing
187+ const commitDetails = await batchedApiCall (
188+ commitsToAnalyze ,
189+ async ( commit ) => await fetchCommitDetails ( repoName , commit . sha ) ,
190+ BATCH_CONFIG . COMMIT_DETAILS_BATCH_SIZE
191+ ) ;
192+
193+ // Aggregate the results
194+ for ( const details of commitDetails ) {
195+ totalAdditions += details . additions ;
196+ totalDeletions += details . deletions ;
197+ totalChanges += details . total ;
198+ }
199+
200+ console . log ( ` ✅ Successfully analyzed ${ commitDetails . length } /${ commitsToAnalyze . length } commits` ) ;
201+ }
202+
63203 // Get PR reviews by this user
64204 const reviews = await githubApi ( `/repos/${ ORG_NAME } /${ repoName } /pulls/comments?per_page=100` ) ;
65205 const userReviews = reviews . filter ( review => review . user . login === username ) ;
@@ -68,17 +208,30 @@ const fetchUserPRsAndActivity = async (username, repoName) => {
68208 prs : prs . length ,
69209 mergedPrs : prs . filter ( pr => pr . merged_at ) . length ,
70210 recentCommits : commits . length ,
211+ totalCommits : allCommits . length ,
71212 reviews : userReviews . length ,
72- lastActivity : commits . length > 0 ? commits [ 0 ] . commit . author . date : null
213+ lastActivity : commits . length > 0 ? commits [ 0 ] . commit . author . date : null ,
214+ // Enhanced line change statistics (now analyzes ALL commits up to limit)
215+ totalAdditions,
216+ totalDeletions,
217+ totalChanges,
218+ commitsAnalyzed : commitsToAnalyze . length , // Total commits we attempted to analyze
219+ commitsSuccessfullyAnalyzed : totalChanges > 0 ? commitsToAnalyze . filter ( commit => commit ) . length : 0 // Successful analyses
73220 } ;
74221 } catch ( error ) {
75222 console . warn ( `Could not fetch detailed activity for ${ username } in ${ repoName } :` , error . message ) ;
76223 return {
77224 prs : 0 ,
78225 mergedPrs : 0 ,
79226 recentCommits : 0 ,
227+ totalCommits : 0 ,
80228 reviews : 0 ,
81- lastActivity : null
229+ lastActivity : null ,
230+ totalAdditions : 0 ,
231+ totalDeletions : 0 ,
232+ totalChanges : 0 ,
233+ commitsAnalyzed : 0 ,
234+ commitsSuccessfullyAnalyzed : 0
82235 } ;
83236 }
84237} ;
@@ -107,7 +260,13 @@ const aggregateContributors = (contributorsByRepo) => {
107260 existing . totalPrs += contributor . activityData . prs ;
108261 existing . totalMergedPrs += contributor . activityData . mergedPrs ;
109262 existing . totalRecentCommits += contributor . activityData . recentCommits ;
263+ existing . totalCommits += contributor . activityData . totalCommits ;
110264 existing . totalReviews += contributor . activityData . reviews ;
265+ // Aggregate line change statistics
266+ existing . totalAdditions += contributor . activityData . totalAdditions ;
267+ existing . totalDeletions += contributor . activityData . totalDeletions ;
268+ existing . totalChanges += contributor . activityData . totalChanges ;
269+ existing . commitsAnalyzed += contributor . activityData . commitsAnalyzed ;
111270 // Keep the most recent activity date
112271 if ( contributor . activityData . lastActivity ) {
113272 const activityDate = new Date ( contributor . activityData . lastActivity ) ;
@@ -127,8 +286,14 @@ const aggregateContributors = (contributorsByRepo) => {
127286 totalPrs : contributor . activityData ?. prs || 0 ,
128287 totalMergedPrs : contributor . activityData ?. mergedPrs || 0 ,
129288 totalRecentCommits : contributor . activityData ?. recentCommits || 0 ,
289+ totalCommits : contributor . activityData ?. totalCommits || 0 ,
130290 totalReviews : contributor . activityData ?. reviews || 0 ,
131- lastActivity : contributor . activityData ?. lastActivity || null
291+ lastActivity : contributor . activityData ?. lastActivity || null ,
292+ // New line change statistics
293+ totalAdditions : contributor . activityData ?. totalAdditions || 0 ,
294+ totalDeletions : contributor . activityData ?. totalDeletions || 0 ,
295+ totalChanges : contributor . activityData ?. totalChanges || 0 ,
296+ commitsAnalyzed : contributor . activityData ?. commitsAnalyzed || 0
132297 } ) ;
133298 }
134299 } ) ;
@@ -194,10 +359,16 @@ const enhanceWithUserData = async (contributors) => {
194359 total_prs : contributor . totalPrs ,
195360 total_merged_prs : contributor . totalMergedPrs ,
196361 total_recent_commits : contributor . totalRecentCommits ,
362+ total_commits : contributor . totalCommits ,
197363 total_reviews : contributor . totalReviews ,
198364 last_activity : contributor . lastActivity ,
199365 pr_success_rate : contributor . totalPrs > 0 ?
200- Math . round ( ( contributor . totalMergedPrs / contributor . totalPrs ) * 100 ) : 0
366+ Math . round ( ( contributor . totalMergedPrs / contributor . totalPrs ) * 100 ) : 0 ,
367+ // New line change statistics
368+ total_additions : contributor . totalAdditions ,
369+ total_deletions : contributor . totalDeletions ,
370+ total_changes : contributor . totalChanges ,
371+ commits_analyzed : contributor . commitsAnalyzed
201372 } ) ;
202373
203374 // Rate limiting: delay between requests
@@ -269,15 +440,19 @@ const calculateAllScores = async (contributorsByRepo, repositories) => {
269440
270441const main = async ( ) => {
271442 try {
272- console . log ( '🚀 Starting contributor data fetch with simplified scoring...' ) ;
443+ console . log ( '🚀 Starting contributor data fetch with enhanced batching and full commit analysis...' ) ;
444+ console . log ( `📊 Configuration: Analyzing up to ${ BATCH_CONFIG . COMMIT_ANALYSIS_LIMIT } commits per contributor (0 = no limit)` ) ;
445+ console . log ( `⚡ Batching: ${ BATCH_CONFIG . COMMIT_DETAILS_BATCH_SIZE } commits per batch with ${ BATCH_CONFIG . RETRY_ATTEMPTS } retry attempts` ) ;
446+ console . log ( '✨ Improvements: Removed 100-commit limit, added efficient batching, retry logic, and better error handling\n' ) ;
273447
274448 /*
275449 * Process Flow:
276450 * 1. Fetch all repositories from the organization
277451 * 2. Fetch all contributors from all repositories
278452 * 3. Calculate scores for all contributors (simplified recent activity scoring)
279- * 4. Enhance with detailed user information from GitHub
280- * 5. Generate and save output file
453+ * 4. Fetch detailed line change statistics for each contributor
454+ * 5. Enhance with detailed user information from GitHub
455+ * 6. Generate and save output file with comprehensive metrics
281456 */
282457
283458 // Step 1: Fetch all repositories
@@ -308,6 +483,10 @@ const main = async () => {
308483 const totalImpactScore = enhancedContributors . reduce ( ( sum , c ) => sum + c . impact_score , 0 ) ;
309484 const totalContributions = enhancedContributors . reduce ( ( sum , c ) => sum + c . contributions , 0 ) ;
310485 const totalRecentActivity = enhancedContributors . reduce ( ( sum , c ) => sum + c . total_recent_commits , 0 ) ;
486+ const totalAdditions = enhancedContributors . reduce ( ( sum , c ) => sum + c . total_additions , 0 ) ;
487+ const totalDeletions = enhancedContributors . reduce ( ( sum , c ) => sum + c . total_deletions , 0 ) ;
488+ const totalChanges = enhancedContributors . reduce ( ( sum , c ) => sum + c . total_changes , 0 ) ;
489+ const totalCommitsAnalyzed = enhancedContributors . reduce ( ( sum , c ) => sum + c . commits_analyzed , 0 ) ;
311490
312491 // Write to file
313492 fs . writeFileSync ( OUTPUT_FILE , JSON . stringify ( {
@@ -317,6 +496,11 @@ const main = async () => {
317496 total_impact_score : totalImpactScore ,
318497 total_recent_activity : totalRecentActivity ,
319498 scoring_method : 'simplified_recent_activity' ,
499+ // New aggregated line change statistics
500+ total_additions : totalAdditions ,
501+ total_deletions : totalDeletions ,
502+ total_changes : totalChanges ,
503+ total_commits_analyzed : totalCommitsAnalyzed ,
320504 contributors : enhancedContributors
321505 } , null , 2 ) ) ;
322506
@@ -325,6 +509,8 @@ const main = async () => {
325509 console . log ( ` 💫 Total impact score: ${ totalImpactScore } ` ) ;
326510 console . log ( ` 📈 Total contributions: ${ totalContributions } ` ) ;
327511 console . log ( ` 🔥 Recent activity: ${ totalRecentActivity } commits (last 6 months)` ) ;
512+ console . log ( ` 📊 Line changes: +${ totalAdditions . toLocaleString ( ) } -${ totalDeletions . toLocaleString ( ) } (${ totalChanges . toLocaleString ( ) } total)` ) ;
513+ console . log ( ` 🔍 Commits analyzed: ${ totalCommitsAnalyzed . toLocaleString ( ) } ` ) ;
328514 console . log ( ` 🏆 Top contributor: ${ enhancedContributors [ 0 ] ?. name } (${ enhancedContributors [ 0 ] ?. impact_score } impact score)` ) ;
329515
330516 } catch ( error ) {
0 commit comments