@@ -11,6 +11,7 @@ import (
1111 "log/slog"
1212 "os"
1313 "os/exec"
14+ "strconv"
1415 "strings"
1516 "time"
1617
@@ -30,8 +31,8 @@ func main() {
3031 // Org/Repo sampling flags
3132 org := flag .String ("org" , "" , "GitHub organization to analyze (optionally with --repo for single repo)" )
3233 repo := flag .String ("repo" , "" , "GitHub repository to analyze (requires --org)" )
33- samples := flag .Int ("samples" , 20 , "Number of PRs to sample for extrapolation" )
34- days := flag .Int ("days" , 90 , "Number of days to look back for PR modifications" )
34+ samples := flag .Int ("samples" , 25 , "Number of PRs to sample for extrapolation (25=fast/±20%, 50=slower/±14%) " )
35+ days := flag .Int ("days" , 60 , "Number of days to look back for PR modifications" )
3536
3637 flag .Usage = func () {
3738 fmt .Fprintf (os .Stderr , "Usage: %s [options] <PR_URL>\n " , os .Args [0 ])
@@ -52,7 +53,7 @@ func main() {
5253 fmt .Fprintf (os .Stderr , " %s --org myorg --repo myrepo --samples 50 --days 30\n \n " , os .Args [0 ])
5354 fmt .Fprint (os .Stderr , " Organization-wide analysis:\n " )
5455 fmt .Fprintf (os .Stderr , " %s --org chainguard-dev\n " , os .Args [0 ])
55- fmt .Fprintf (os .Stderr , " %s --org myorg --samples 100 --days 60\n " , os .Args [0 ])
56+ fmt .Fprintf (os .Stderr , " %s --org myorg --samples 50 --days 60\n " , os .Args [0 ])
5657 }
5758
5859 flag .Parse ()
@@ -101,12 +102,10 @@ func main() {
101102 "salary" , cfg .AnnualSalary ,
102103 "benefits_multiplier" , cfg .BenefitsMultiplier ,
103104 "event_minutes" , * eventMinutes ,
104- "delivery_delay_factor" , cfg .DeliveryDelayFactor ,
105- "coordination_factor" , cfg .CoordinationFactor )
105+ "delivery_delay_factor" , cfg .DeliveryDelayFactor )
106106
107107 // Retrieve GitHub token from gh CLI
108108 ctx := context .Background ()
109- slog .Info ("Retrieving GitHub authentication token" )
110109 token , err := authToken (ctx )
111110 if err != nil {
112111 slog .Error ("Failed to get GitHub token" , "error" , err )
@@ -119,11 +118,6 @@ func main() {
119118 // Org/Repo sampling mode
120119 if * repo != "" {
121120 // Single repository mode
122- slog .Info ("Starting repository analysis" ,
123- "org" , * org ,
124- "repo" , * repo ,
125- "samples" , * samples ,
126- "days" , * days )
127121
128122 err := analyzeRepository (ctx , * org , * repo , * samples , * days , cfg , token , * dataSource )
129123 if err != nil {
@@ -282,7 +276,7 @@ func printHumanReadable(breakdown *cost.Breakdown, prURL string) {
282276 }
283277 // Only show other events if they had non-review events
284278 if p .GitHubHours > 0 {
285- fmt .Printf (" GitHub Events %12s %d sessions • %s\n " ,
279+ fmt .Printf (" GitHub Activity %12s %d sessions • %s\n " ,
286280 formatCurrency (p .GitHubCost ), p .Sessions , formatTimeUnit (p .GitHubHours ))
287281 }
288282 // Always show context switching if there were sessions
@@ -312,6 +306,9 @@ func printHumanReadable(breakdown *cost.Breakdown, prURL string) {
312306 fmt .Printf (" Total %12s %s\n " ,
313307 formatCurrency (breakdown .TotalCost ), formatTimeUnit (totalHours ))
314308 fmt .Println ()
309+
310+ // Print efficiency score
311+ printEfficiency (breakdown )
315312}
316313
317314// printDelayCosts prints delay and future costs section.
@@ -325,25 +322,22 @@ func printDelayCosts(breakdown *cost.Breakdown, formatCurrency func(float64) str
325322 if breakdown .DelayCapped {
326323 cappedSuffix = " (capped)"
327324 }
328- fmt .Printf (" Project %12s %s%s\n " ,
325+ fmt .Printf (" Workstream blockage %12s %s%s\n " ,
329326 formatCurrency (breakdown .DelayCostDetail .DeliveryDelayCost ),
330327 formatTimeUnit (breakdown .DelayCostDetail .DeliveryDelayHours ),
331328 cappedSuffix )
332329 }
333330
334- if breakdown .DelayCostDetail .CoordinationHours > 0 {
335- cappedSuffix := ""
336- if breakdown .DelayCapped {
337- cappedSuffix = " (capped)"
338- }
339- fmt .Printf (" Coordination %12s %s%s\n " ,
340- formatCurrency (breakdown .DelayCostDetail .CoordinationCost ),
341- formatTimeUnit (breakdown .DelayCostDetail .CoordinationHours ),
342- cappedSuffix )
343- }
331+ // Calculate merge delay subtotal (all non-future delay costs)
332+ mergeDelayCost := breakdown .DelayCostDetail .DeliveryDelayCost +
333+ breakdown .DelayCostDetail .CodeChurnCost +
334+ breakdown .DelayCostDetail .AutomatedUpdatesCost +
335+ breakdown .DelayCostDetail .PRTrackingCost
336+ mergeDelayHours := breakdown .DelayCostDetail .DeliveryDelayHours +
337+ breakdown .DelayCostDetail .CodeChurnHours +
338+ breakdown .DelayCostDetail .AutomatedUpdatesHours +
339+ breakdown .DelayCostDetail .PRTrackingHours
344340
345- mergeDelayCost := breakdown .DelayCostDetail .DeliveryDelayCost + breakdown .DelayCostDetail .CoordinationCost
346- mergeDelayHours := breakdown .DelayCostDetail .DeliveryDelayHours + breakdown .DelayCostDetail .CoordinationHours
347341 fmt .Println (" ────────────" )
348342 pct := (mergeDelayCost / breakdown .TotalCost ) * 100
349343 fmt .Printf (" Subtotal %12s %s (%.1f%%)\n " ,
@@ -431,3 +425,144 @@ func formatWithCommas(amount float64) string {
431425
432426 return string (result ) + "." + decPart
433427}
428+
429+ // formatLOC formats lines of code in kilo format with appropriate precision and commas for large values.
430+ func formatLOC (kloc float64 ) string {
431+ // For values >= 100k, add commas (e.g., "1,517k" instead of "1517k")
432+ if kloc >= 100.0 {
433+ intPart := int (kloc )
434+ fracPart := kloc - float64 (intPart )
435+
436+ // Format integer part with commas
437+ intStr := strconv .Itoa (intPart )
438+ var result []rune
439+ for i , r := range intStr {
440+ if i > 0 && (len (intStr )- i )% 3 == 0 {
441+ result = append (result , ',' )
442+ }
443+ result = append (result , r )
444+ }
445+
446+ // Add fractional part if significant
447+ if kloc < 1000.0 && fracPart >= 0.05 {
448+ return fmt .Sprintf ("%s.%dk" , string (result ), int (fracPart * 10 ))
449+ }
450+ return string (result ) + "k"
451+ }
452+
453+ // For values < 100k, use existing precision logic
454+ if kloc < 0.1 && kloc > 0 {
455+ return fmt .Sprintf ("%.2fk" , kloc )
456+ }
457+ if kloc < 1.0 {
458+ return fmt .Sprintf ("%.1fk" , kloc )
459+ }
460+ if kloc < 10.0 {
461+ return fmt .Sprintf ("%.1fk" , kloc )
462+ }
463+ return fmt .Sprintf ("%.0fk" , kloc )
464+ }
465+
466+ // efficiencyGrade returns a letter grade and message based on efficiency percentage (MIT scale).
467+ func efficiencyGrade (efficiencyPct float64 ) (grade , message string ) {
468+ switch {
469+ case efficiencyPct >= 97 :
470+ return "A+" , "Impeccable"
471+ case efficiencyPct >= 93 :
472+ return "A" , "Excellent"
473+ case efficiencyPct >= 90 :
474+ return "A-" , "Nearly excellent"
475+ case efficiencyPct >= 87 :
476+ return "B+" , "Acceptable+"
477+ case efficiencyPct >= 83 :
478+ return "B" , "Acceptable"
479+ case efficiencyPct >= 80 :
480+ return "B-" , "Nearly acceptable"
481+ case efficiencyPct >= 70 :
482+ return "C" , "Average"
483+ case efficiencyPct >= 60 :
484+ return "D" , "Not good my friend."
485+ default :
486+ return "F" , "Failing"
487+ }
488+ }
489+
490+ // mergeVelocityGrade returns a grade based on average PR open time in days.
491+ // A+: 4h, A: 8h, A-: 12h, B+: 18h, B: 24h, B-: 36h, C: 100h, D: 120h, F: 120h+.
492+ func mergeVelocityGrade (avgOpenDays float64 ) (grade , message string ) {
493+ switch {
494+ case avgOpenDays <= 0.1667 : // 4 hours
495+ return "A+" , "Impeccable"
496+ case avgOpenDays <= 0.3333 : // 8 hours
497+ return "A" , "Excellent"
498+ case avgOpenDays <= 0.5 : // 12 hours
499+ return "A-" , "Nearly excellent"
500+ case avgOpenDays <= 0.75 : // 18 hours
501+ return "B+" , "Acceptable+"
502+ case avgOpenDays <= 1.0 : // 24 hours
503+ return "B" , "Acceptable"
504+ case avgOpenDays <= 1.5 : // 36 hours
505+ return "B-" , "Nearly acceptable"
506+ case avgOpenDays <= 4.1667 : // 100 hours
507+ return "C" , "Average"
508+ case avgOpenDays <= 5.0 : // 120 hours
509+ return "D" , "Not good my friend."
510+ default :
511+ return "F" , "Failing"
512+ }
513+ }
514+
515+ // printEfficiency prints the workflow efficiency section for a single PR.
516+ func printEfficiency (breakdown * cost.Breakdown ) {
517+ // Calculate preventable waste: Code Churn + All Delay Costs + Automated Updates + PR Tracking
518+ preventableHours := breakdown .DelayCostDetail .CodeChurnHours +
519+ breakdown .DelayCostDetail .DeliveryDelayHours +
520+ breakdown .DelayCostDetail .AutomatedUpdatesHours +
521+ breakdown .DelayCostDetail .PRTrackingHours
522+ preventableCost := breakdown .DelayCostDetail .CodeChurnCost +
523+ breakdown .DelayCostDetail .DeliveryDelayCost +
524+ breakdown .DelayCostDetail .AutomatedUpdatesCost +
525+ breakdown .DelayCostDetail .PRTrackingCost
526+
527+ // Calculate total hours
528+ totalHours := breakdown .Author .TotalHours + breakdown .DelayCostDetail .TotalDelayHours
529+ for _ , p := range breakdown .Participants {
530+ totalHours += p .TotalHours
531+ }
532+
533+ // Calculate efficiency
534+ var efficiencyPct float64
535+ if totalHours > 0 {
536+ efficiencyPct = 100.0 * (totalHours - preventableHours ) / totalHours
537+ } else {
538+ efficiencyPct = 100.0
539+ }
540+
541+ grade , message := efficiencyGrade (efficiencyPct )
542+
543+ // Calculate merge velocity grade based on PR duration
544+ prDurationDays := breakdown .PRDuration / 24.0
545+ velocityGrade , velocityMessage := mergeVelocityGrade (prDurationDays )
546+
547+ fmt .Println (" ┌─────────────────────────────────────────────────────────────┐" )
548+ headerText := fmt .Sprintf ("DEVELOPMENT EFFICIENCY: %s (%.1f%%) - %s" , grade , efficiencyPct , message )
549+ padding := 60 - len (headerText )
550+ if padding < 0 {
551+ padding = 0
552+ }
553+ fmt .Printf (" │ %s%*s│\n " , headerText , padding , "" )
554+ fmt .Println (" └─────────────────────────────────────────────────────────────┘" )
555+
556+ fmt .Println (" ┌─────────────────────────────────────────────────────────────┐" )
557+ velocityHeader := fmt .Sprintf ("MERGE VELOCITY: %s (%s) - %s" , velocityGrade , formatTimeUnit (breakdown .PRDuration ), velocityMessage )
558+ velPadding := 60 - len (velocityHeader )
559+ if velPadding < 0 {
560+ velPadding = 0
561+ }
562+ fmt .Printf (" │ %s%*s│\n " , velocityHeader , velPadding , "" )
563+ fmt .Println (" └─────────────────────────────────────────────────────────────┘" )
564+
565+ fmt .Printf (" Preventable Waste: $%12s %s\n " ,
566+ formatWithCommas (preventableCost ), formatTimeUnit (preventableHours ))
567+ fmt .Println ()
568+ }
0 commit comments