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 ' ,
4+ trusted : 'Trusted ' ,
5+ familiar : 'Needs Review ' ,
6+ caution : 'Risky ' ,
7+ unknown : 'Unknown ' ,
88} ;
99
1010const TIER_BADGE_COLORS : Record < Tier , string > = {
@@ -14,6 +14,13 @@ const TIER_BADGE_COLORS: Record<Tier, string> = {
1414 unknown : 'red' ,
1515} ;
1616
17+ const TIER_DOTS : Record < Tier , string > = {
18+ trusted : '\ud83d\udfe2' ,
19+ familiar : '\ud83d\udfe1' ,
20+ caution : '\ud83d\udfe0' ,
21+ unknown : '\ud83d\udd34' ,
22+ } ;
23+
1724export const COMMENT_MARKER = '<!-- firstlook-assessment -->' ;
1825
1926function shieldsParam ( text : string ) : string {
@@ -24,7 +31,7 @@ function shieldsParam(text: string): string {
2431 . replace ( / \/ / g, '%2F' ) ;
2532}
2633
27- function badge ( label : string , value : string , color : string , style = 'flat-square' ) : string {
34+ function shieldsBadge ( label : string , value : string , color : string , style = 'flat-square' ) : string {
2835 const l = shieldsParam ( label ) ;
2936 const v = shieldsParam ( value ) ;
3037 return `` ;
@@ -40,92 +47,98 @@ function ageText(days: number): string {
4047 return `${ days } days` ;
4148}
4249
43- function scoreBar ( score : number ) : string {
44- const filled = Math . round ( score / 10 ) ;
45- return '\u2588' . repeat ( filled ) + '\u2591' . repeat ( 10 - filled ) ;
50+ function ageQualifier ( days : number ) : string {
51+ if ( days >= 730 ) return 'Long-term presence' ;
52+ if ( days >= 365 ) return 'Established' ;
53+ if ( days >= 180 ) return 'Growing' ;
54+ return 'New account' ;
4655}
4756
48- function signalBadges ( s : ContributorSignals ) : string [ ] {
49- const badges : string [ ] = [ ] ;
50-
51- badges . push ( badge ( 'account' , ageText ( s . accountAgeDays ) ,
52- s . accountAgeDays >= 365 ? 'blue' : s . accountAgeDays >= 180 ? 'blue' : s . accountAgeDays >= 30 ? 'orange' : 'red' ) ) ;
53-
54- badges . push ( badge ( 'repos' , `${ s . publicRepos } ` ,
55- s . publicRepos >= 3 ? 'blue' : s . publicRepos >= 1 ? 'orange' : 'red' ) ) ;
56-
57- badges . push ( badge ( 'merged' , `${ s . mergedPRs } ` ,
58- s . mergedPRs >= 10 ? 'brightgreen' : s . mergedPRs >= 3 ? 'blue' : s . mergedPRs >= 1 ? 'orange' : 'red' ) ) ;
59-
60- badges . push ( badge ( 'rejected' , `${ s . closedPRs } ` ,
61- s . closedPRs === 0 ? 'blue' : s . closedPRs <= s . mergedPRs ? 'orange' : 'red' ) ) ;
62-
63- badges . push ( badge ( 'unique mergers' , `${ s . uniqueMergers } ` ,
64- s . uniqueMergers >= 3 ? 'brightgreen' : s . uniqueMergers >= 1 ? 'blue' : 'lightgrey' ) ) ;
65-
66- badges . push ( badge ( '100%2B%E2%98%85 repos' , `${ s . highStarRepos } ` ,
67- s . highStarRepos >= 3 ? 'brightgreen' : s . highStarRepos >= 1 ? 'blue' : 'lightgrey' ) ) ;
68-
69- badges . push ( badge ( 'activity' , `${ s . activeMonths } /${ s . totalMonths } mo` ,
70- s . activeMonths >= 6 ? 'blue' : s . activeMonths >= 3 ? 'blue' : s . activeMonths >= 1 ? 'orange' : 'red' ) ) ;
71-
72- badges . push ( badge ( 'followers' , `${ s . followers } ` ,
73- s . followers >= 10 ? 'blue' : s . followers >= 3 ? 'blue' : 'lightgrey' ) ) ;
74-
75- badges . push ( badge ( 'signed' , s . commitsSigned ? 'yes' : 'no' ,
76- s . commitsSigned ? 'brightgreen' : 'orange' ) ) ;
77-
78- if ( s . profile . filledCount > 0 ) {
79- const fields : string [ ] = [ ] ;
80- if ( s . profile . bio ) fields . push ( 'bio' ) ;
81- if ( s . profile . company ) fields . push ( 'co' ) ;
82- if ( s . profile . blog ) fields . push ( 'blog' ) ;
83- if ( s . profile . twitter ) fields . push ( 'tw' ) ;
84- if ( s . profile . email ) fields . push ( 'email' ) ;
85- badges . push ( badge ( 'profile' , fields . join ( ' ' ) , 'blue' ) ) ;
86- }
87-
88- for ( const f of s . securityFiles . slice ( 0 , 3 ) ) {
89- badges . push ( badge ( 'security' , f , 'red' ) ) ;
90- }
91- if ( s . securityFiles . length > 3 ) {
92- badges . push ( badge ( 'security' , `+${ s . securityFiles . length - 3 } more` , 'red' ) ) ;
93- }
57+ function activityLabel ( activeMonths : number ) : string {
58+ if ( activeMonths >= 10 ) return 'Active' ;
59+ if ( activeMonths >= 6 ) return 'Regular' ;
60+ if ( activeMonths >= 3 ) return 'Sporadic' ;
61+ return 'Minimal' ;
62+ }
9463
95- return badges ;
64+ function activityQualifier ( activeMonths : number ) : string {
65+ if ( activeMonths >= 10 ) return 'Consistent activity' ;
66+ if ( activeMonths >= 6 ) return 'Regular activity' ;
67+ if ( activeMonths >= 3 ) return 'Sporadic activity' ;
68+ return 'Minimal activity' ;
9669}
9770
9871export function buildComment ( assessment : Assessment ) : string {
9972 const { signals : s , tier, score, summary, patterns } = assessment ;
10073
101- const tierBadge = badge ( TIER_LABELS [ tier ] , `${ score } %2F100` , TIER_BADGE_COLORS [ tier ] , 'for--the--badge' ) ;
102- const bar = scoreBar ( score ) ;
103- const badges = signalBadges ( s ) ;
74+ const tierBadge = shieldsBadge ( TIER_LABELS [ tier ] , '' , TIER_BADGE_COLORS [ tier ] , 'for--the--badge' ) ;
75+ const scoreBadge = shieldsBadge ( 'Trust_Score' , `${ score } %2F100` , TIER_BADGE_COLORS [ tier ] ) ;
10476
105- for ( const p of patterns ) {
106- const color = p . severity === 'critical' ? 'red' : 'orange' ;
107- badges . push ( badge ( `⚠ ${ p . name } ` , p . detail , color ) ) ;
108- }
77+ const totalPRs = s . mergedPRs + s . closedPRs ;
78+ const mergeRate = totalPRs > 0 ? Math . round ( ( s . mergedPRs / totalPRs ) * 100 ) : - 1 ;
79+ const qualityValue = mergeRate >= 0 ? `${ mergeRate } %` : 'N/A' ;
80+ const qualityDetail = totalPRs > 0
81+ ? `${ s . mergedPRs } merged \u00b7 ${ s . closedPRs } rejected`
82+ : 'No PRs yet' ;
10983
11084 const lines : string [ ] = [
11185 COMMENT_MARKER ,
11286 '' ,
11387 `### firstlook ${ tierBadge } ` ,
11488 '' ,
115- `\` ${ bar } \`` ,
89+ summary ,
11690 '' ,
117- badges . join ( ' ' ) ,
91+ ` ${ scoreBadge } ${ TIER_DOTS [ tier ] } ${ tier === 'trusted' ? 'High' : tier === 'familiar' ? 'Medium' : 'Low' } confidence` ,
11892 '' ,
119- `**${ summary } **` ,
93+ '---' ,
94+ '' ,
95+ '**Key Signals**' ,
96+ '' ,
97+ '| Account age | Contribution quality | Recent activity |' ,
98+ '|:---:|:---:|:---:|' ,
99+ `| **${ ageText ( s . accountAgeDays ) } ** | **${ qualityValue } ** | **${ activityLabel ( s . activeMonths ) } ** |` ,
100+ `| ${ ageQualifier ( s . accountAgeDays ) } | ${ qualityDetail } | ${ s . activeMonths } /${ s . totalMonths } months |` ,
120101 ] ;
121102
103+ if ( patterns . length > 0 ) {
104+ lines . push ( '' , '---' , '' ) ;
105+ for ( const p of patterns ) {
106+ const icon = p . severity === 'critical' ? '\ud83d\udea8' : '\u26a0\ufe0f' ;
107+ lines . push ( `${ icon } **${ p . name } ** -- ${ p . detail } ` ) ;
108+ }
109+ }
110+
111+ if ( s . securityFiles . length > 0 ) {
112+ lines . push ( '' ) ;
113+ for ( const f of s . securityFiles . slice ( 0 , 3 ) ) {
114+ lines . push ( `\ud83d\udd12 Modifying security-critical file: \`${ f } \`` ) ;
115+ }
116+ if ( s . securityFiles . length > 3 ) {
117+ lines . push ( `\ud83d\udd12 +${ s . securityFiles . length - 3 } more security-critical files` ) ;
118+ }
119+ }
120+
121+ lines . push (
122+ '' ,
123+ '| Repos | Followers | Unique mergers | 100+ \u2605 repos | Signed |' ,
124+ '|:---:|:---:|:---:|:---:|:---:|' ,
125+ `| **${ s . publicRepos } ** | **${ s . followers } ** | **${ s . uniqueMergers } ** | **${ s . highStarRepos } ** | **${ s . commitsSigned ? '\u2713' : '\u2717' } ** |` ,
126+ ) ;
127+
122128 const details : string [ ] = [ ] ;
129+ if ( s . profile . filledCount > 0 ) {
130+ const fields : string [ ] = [ ] ;
131+ if ( s . profile . bio ) fields . push ( 'Bio' ) ;
132+ if ( s . profile . company ) fields . push ( 'Company' ) ;
133+ if ( s . profile . blog ) fields . push ( 'Blog' ) ;
134+ if ( s . profile . twitter ) fields . push ( 'Twitter' ) ;
135+ if ( s . profile . email ) fields . push ( 'Email' ) ;
136+ details . push ( `- Profile: ${ fields . join ( ', ' ) } ` ) ;
137+ }
123138 if ( s . codeReviews > 0 ) details . push ( `- Code reviews given: ${ s . codeReviews } ` ) ;
124139 if ( s . selfMergeCount > 0 || s . externalMergeCount > 0 ) {
125140 details . push ( `- Self-merged: ${ s . selfMergeCount } | Externally merged: ${ s . externalMergeCount } ` ) ;
126141 }
127- if ( s . highStarRepos > 0 )
128- details . push ( `- Contributed to ${ s . highStarRepos } repos with 100+ stars` ) ;
129142 const repoTotal = s . repoMergedPRs + s . repoClosedPRs ;
130143 if ( repoTotal > 0 ) {
131144 details . push ( `- This repo: ${ s . repoMergedPRs } merged, ${ s . repoClosedPRs } closed` ) ;
@@ -134,7 +147,7 @@ export function buildComment(assessment: Assessment): string {
134147 }
135148
136149 if ( details . length > 0 ) {
137- lines . push ( '' , '<details>' , '<summary>Details </summary>' , '' , ...details , '' , '</details>' ) ;
150+ lines . push ( '' , '<details>' , '<summary>View full details </summary>' , '' , ...details , '' , '</details>' ) ;
138151 }
139152
140153 lines . push ( '' , '<sub><a href="https://github.com/getagentseal/firstlook">firstlook</a></sub>' ) ;
0 commit comments