11import { Assessment , ContributorSignals , Tier } from './types' ;
22
33const TIER_LABELS : Record < Tier , string > = {
4- trusted : 'Trusted' ,
5- familiar : 'Familiar' ,
6- caution : 'Review Suggested' ,
7- unknown : 'Unknown Contributor' ,
4+ trusted : 'TRUSTED' ,
5+ familiar : 'FAMILIAR' ,
6+ caution : 'REVIEW SUGGESTED' ,
7+ unknown : 'UNKNOWN' ,
8+ } ;
9+
10+ const TIER_BADGE_COLORS : Record < Tier , string > = {
11+ trusted : 'brightgreen' ,
12+ familiar : 'yellow' ,
13+ caution : 'orange' ,
14+ unknown : 'red' ,
15+ } ;
16+
17+ const TIER_ALERT : Record < Tier , string > = {
18+ trusted : 'NOTE' ,
19+ familiar : 'TIP' ,
20+ caution : 'WARNING' ,
21+ unknown : 'CAUTION' ,
822} ;
923
1024export const COMMENT_MARKER = '<!-- firstlook-assessment -->' ;
1125
26+ function shieldsParam ( text : string ) : string {
27+ return text
28+ . replace ( / - / g, '--' )
29+ . replace ( / _ / g, '__' )
30+ . replace ( / / g, '_' )
31+ . replace ( / \/ / g, '%2F' ) ;
32+ }
33+
34+ function badge ( label : string , value : string , color : string , style = 'flat-square' ) : string {
35+ const l = shieldsParam ( label ) ;
36+ const v = shieldsParam ( value ) ;
37+ return `` ;
38+ }
39+
1240function ageText ( days : number ) : string {
1341 if ( days >= 365 ) {
1442 const years = Math . floor ( days / 365 ) ;
@@ -19,67 +47,87 @@ function ageText(days: number): string {
1947 return `${ days } days` ;
2048}
2149
22- function indicator ( good : boolean ) : string {
23- return good ? ':white_check_mark:' : ':warning:' ;
50+ function scoreBar ( score : number ) : string {
51+ const filled = Math . round ( score / 10 ) ;
52+ return '\u2588' . repeat ( filled ) + '\u2591' . repeat ( 10 - filled ) ;
2453}
2554
26- function profileText ( profile : ContributorSignals [ 'profile' ] ) : string {
27- const fields : string [ ] = [ ] ;
28- if ( profile . bio ) fields . push ( 'Bio' ) ;
29- if ( profile . company ) fields . push ( 'Company' ) ;
30- if ( profile . blog ) fields . push ( 'Blog' ) ;
31- if ( profile . twitter ) fields . push ( 'Twitter' ) ;
32- if ( profile . email ) fields . push ( 'Email' ) ;
33- return fields . length > 0 ? fields . join ( ', ' ) : 'None provided' ;
34- }
55+ function signalBadges ( s : ContributorSignals ) : string [ ] {
56+ const badges : string [ ] = [ ] ;
57+
58+ badges . push ( badge ( 'account' , ageText ( s . accountAgeDays ) ,
59+ s . accountAgeDays >= 365 ? 'blue' : s . accountAgeDays >= 180 ? 'blue' : s . accountAgeDays >= 30 ? 'orange' : 'red' ) ) ;
60+
61+ badges . push ( badge ( 'repos' , `${ s . publicRepos } ` ,
62+ s . publicRepos >= 3 ? 'blue' : s . publicRepos >= 1 ? 'orange' : 'red' ) ) ;
63+
64+ badges . push ( badge ( 'merged' , `${ s . mergedPRs } ` ,
65+ s . mergedPRs >= 10 ? 'brightgreen' : s . mergedPRs >= 3 ? 'blue' : s . mergedPRs >= 1 ? 'orange' : 'red' ) ) ;
66+
67+ badges . push ( badge ( 'rejected' , `${ s . closedPRs } ` ,
68+ s . closedPRs === 0 ? 'blue' : s . closedPRs <= s . mergedPRs ? 'orange' : 'red' ) ) ;
69+
70+ badges . push ( badge ( 'unique mergers' , `${ s . uniqueMergers } ` ,
71+ s . uniqueMergers >= 3 ? 'brightgreen' : s . uniqueMergers >= 1 ? 'blue' : 'lightgrey' ) ) ;
3572
36- function mergeQualityText ( s : ContributorSignals ) : string {
37- const parts : string [ ] = [ ] ;
38- if ( s . uniqueMergers > 0 ) parts . push ( `${ s . uniqueMergers } unique mergers` ) ;
39- if ( s . highStarRepos > 0 ) parts . push ( `${ s . highStarRepos } repos with 100+ stars` ) ;
40- if ( parts . length > 0 ) return parts . join ( ', ' ) ;
41- if ( s . selfMergeCount > 0 ) return `${ s . selfMergeCount } self-merged only` ;
42- return 'No merge data' ;
73+ badges . push ( badge ( '100%2B%E2%98%85 repos' , `${ s . highStarRepos } ` ,
74+ s . highStarRepos >= 3 ? 'brightgreen' : s . highStarRepos >= 1 ? 'blue' : 'lightgrey' ) ) ;
75+
76+ badges . push ( badge ( 'activity' , `${ s . activeMonths } /${ s . totalMonths } mo` ,
77+ s . activeMonths >= 6 ? 'blue' : s . activeMonths >= 3 ? 'blue' : s . activeMonths >= 1 ? 'orange' : 'red' ) ) ;
78+
79+ badges . push ( badge ( 'followers' , `${ s . followers } ` ,
80+ s . followers >= 10 ? 'blue' : s . followers >= 3 ? 'blue' : 'lightgrey' ) ) ;
81+
82+ badges . push ( badge ( 'signed' , s . commitsSigned ? 'yes' : 'no' ,
83+ s . commitsSigned ? 'brightgreen' : 'orange' ) ) ;
84+
85+ if ( s . profile . filledCount > 0 ) {
86+ const fields : string [ ] = [ ] ;
87+ if ( s . profile . bio ) fields . push ( 'bio' ) ;
88+ if ( s . profile . company ) fields . push ( 'co' ) ;
89+ if ( s . profile . blog ) fields . push ( 'blog' ) ;
90+ if ( s . profile . twitter ) fields . push ( 'tw' ) ;
91+ if ( s . profile . email ) fields . push ( 'email' ) ;
92+ badges . push ( badge ( 'profile' , fields . join ( ' ' ) , 'blue' ) ) ;
93+ }
94+
95+ for ( const f of s . securityFiles . slice ( 0 , 3 ) ) {
96+ badges . push ( badge ( 'security' , f , 'red' ) ) ;
97+ }
98+ if ( s . securityFiles . length > 3 ) {
99+ badges . push ( badge ( 'security' , `+${ s . securityFiles . length - 3 } more` , 'red' ) ) ;
100+ }
101+
102+ return badges ;
43103}
44104
45105export function buildComment ( assessment : Assessment ) : string {
46106 const { signals : s , tier, score, summary, patterns } = assessment ;
47107
48- const rows = [
49- `| **Account** | Created ${ ageText ( s . accountAgeDays ) } ago | ${ indicator ( s . accountAgeDays >= 180 ) } |` ,
50- `| **Repos** | ${ s . publicRepos } public | ${ indicator ( s . publicRepos >= 3 ) } |` ,
51- `| **Profile** | ${ profileText ( s . profile ) } | ${ indicator ( s . profile . filledCount >= 1 ) } |` ,
52- `| **History** | ${ s . mergedPRs } merged, ${ s . closedPRs } rejected elsewhere | ${ indicator ( s . mergedPRs >= 3 && s . closedPRs <= s . mergedPRs ) } |` ,
53- `| **Merge quality** | ${ mergeQualityText ( s ) } | ${ indicator ( s . uniqueMergers >= 1 || s . highStarRepos >= 1 ) } |` ,
54- `| **Activity** | ${ s . activeMonths } /${ s . totalMonths } months active | ${ indicator ( s . activeMonths >= 3 ) } |` ,
55- `| **Followers** | ${ s . followers } | ${ indicator ( s . followers >= 3 ) } |` ,
56- `| **Signed** | ${ s . commitsSigned ? 'Yes' : 'No' } | ${ indicator ( s . commitsSigned ) } |` ,
57- ] ;
108+ const tierBadge = badge ( TIER_LABELS [ tier ] , `${ score } %2F100` , TIER_BADGE_COLORS [ tier ] , 'for--the--badge' ) ;
109+ const bar = scoreBar ( score ) ;
110+ const badges = signalBadges ( s ) ;
58111
59- if ( s . securityFiles . length > 0 ) {
60- const fileList = s . securityFiles . slice ( 0 , 5 ) . map ( f => `\`${ f } \`` ) . join ( ', ' ) ;
61- const extra = s . securityFiles . length > 5 ? ` (+${ s . securityFiles . length - 5 } more)` : '' ;
62- rows . push ( `| **Security paths** | ${ fileList } ${ extra } | :rotating_light: |` ) ;
63- }
64-
65- const lines = [
112+ const lines : string [ ] = [
66113 COMMENT_MARKER ,
67- '### firstlook' ,
68114 '' ,
69- '| Signal | Detail | |' ,
70- '|--------|--------|---|' ,
71- ...rows ,
115+ `### firstlook ${ tierBadge } ` ,
116+ '' ,
117+ `\`${ bar } \`` ,
118+ '' ,
119+ badges . join ( ' ' ) ,
72120 ] ;
73121
74122 if ( patterns . length > 0 ) {
75123 lines . push ( '' ) ;
76124 for ( const p of patterns ) {
77- const icon = p . severity === 'critical' ? ':rotating_light: ' : ':warning: ' ;
78- lines . push ( `${ icon } **${ p . name } ** -- ${ p . detail } ` ) ;
125+ const alertType = p . severity === 'critical' ? 'CAUTION ' : 'WARNING ' ;
126+ lines . push ( `> [! ${ alertType } ]` , `> **${ p . name } ** -- ${ p . detail } `, '' ) ;
79127 }
80128 }
81129
82- lines . push ( '' , `> ** ${ TIER_LABELS [ tier ] } ** (score: ${ score } /100) -- ${ summary } ` ) ;
130+ lines . push ( '' , `> [! ${ TIER_ALERT [ tier ] } ]` , `> ${ summary } `) ;
83131
84132 const details : string [ ] = [ ] ;
85133 if ( s . codeReviews > 0 ) details . push ( `- Code reviews given: ${ s . codeReviews } ` ) ;
0 commit comments