Skip to content

Commit 74991a4

Browse files
committed
Redesign PR comment with structured tables matching mockup layout
Replaces flat badge row with Key Signals table (account age, contribution quality, merge rate) and stats table (repos, followers, unique mergers, star repos, signed). Patterns and security files shown as inline warnings between sections.
1 parent 11c0d92 commit 74991a4

2 files changed

Lines changed: 82 additions & 69 deletions

File tree

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/comment.ts

Lines changed: 81 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
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',
4+
trusted: 'Trusted',
5+
familiar: 'Needs Review',
6+
caution: 'Risky',
7+
unknown: 'Unknown',
88
};
99

1010
const 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+
1724
export const COMMENT_MARKER = '<!-- firstlook-assessment -->';
1825

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

9871
export 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 &nbsp; ${tierBadge}`,
11488
'',
115-
`\`${bar}\``,
89+
summary,
11690
'',
117-
badges.join(' '),
91+
`${scoreBadge} &nbsp; ${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

Comments
 (0)