Skip to content

Commit 4b64d57

Browse files
committed
Redesign PR comment: shields.io badges, score bar, GitHub admonitions
Replace plain table with shields.io colored badges per signal, Unicode score bar, and native GitHub admonition blocks for tier assessment. Each badge is contextually colored (green/blue/orange/red) based on signal strength. Suspicious patterns use CAUTION/WARNING admonitions.
1 parent d67badf commit 4b64d57

2 files changed

Lines changed: 97 additions & 49 deletions

File tree

dist/index.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/comment.ts

Lines changed: 94 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,42 @@
11
import { Assessment, ContributorSignals, Tier } from './types';
22

33
const 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

1024
export 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 `![${label}](https://img.shields.io/badge/${l}-${v}-${color}?style=${style})`;
38+
}
39+
1240
function 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

45105
export 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 &nbsp; ${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

Comments
 (0)