Skip to content

Commit 47c98a7

Browse files
Merge branch 'main' into feat/subtasks
2 parents d669426 + 99a2ff0 commit 47c98a7

2 files changed

Lines changed: 70 additions & 24 deletions

File tree

tools/sep-automation/src/processor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export class SEPProcessor {
107107
results.push(...maintainerResults);
108108
for (const result of maintainerResults) {
109109
if (result.success && result.action.targetUser) {
110-
const activity = await this.analyzer.checkMaintainerActivity(
110+
const activity = await this.analyzer.checkUserActivity(
111111
sep,
112112
result.action.targetUser,
113113
);
@@ -185,7 +185,7 @@ export class SEPProcessor {
185185
continue;
186186
}
187187

188-
const activity = await this.analyzer.checkMaintainerActivity(
188+
const activity = await this.analyzer.checkUserActivity(
189189
sep,
190190
assignee,
191191
);

tools/sep-automation/src/sep/analyzer.ts

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import type { Config } from '../config.js';
66
import type { GitHubClient } from '../github/client.js';
7+
import type { GitHubComment, GitHubEvent } from '../github/types.js';
78
import type { SEPItem, StaleAnalysis } from '../types.js';
89
import { BOT_COMMENT_MARKER } from '../types.js';
910
import { daysBetween } from '../utils/index.js';
@@ -22,10 +23,18 @@ export class SEPAnalyzer {
2223
*/
2324
async analyze(item: SEPItem): Promise<StaleAnalysis> {
2425
const now = new Date();
25-
const daysSinceActivity = daysBetween(item.updatedAt, now);
26+
27+
// Fetch comments and events once, share across checks
28+
const comments = await this.github.getComments(item.number);
29+
const events = await this.github.getEvents(item.number);
30+
31+
// Compute days since the responsible person was last active
32+
// (not just any activity, which includes bot pings that reset updated_at)
33+
const responsibleLastActive = this.findResponsiblePersonActivity(item, events, comments);
34+
const daysSinceActivity = daysBetween(responsibleLastActive, now);
2635

2736
// Check cooldown - don't ping if we pinged recently
28-
const lastPingDate = await this.getLastBotPingDate(item.number);
37+
const lastPingDate = this.findLastBotPingDate(comments);
2938
if (lastPingDate) {
3039
const daysSincePing = daysBetween(lastPingDate, now);
3140
if (daysSincePing < this.config.pingCooldownDays) {
@@ -151,20 +160,66 @@ export class SEPAnalyzer {
151160
}
152161

153162
/**
154-
* Check maintainer activity on a SEP
163+
* Check a specific user's activity on a SEP
155164
*/
156-
async checkMaintainerActivity(item: SEPItem, maintainerUsername: string): Promise<{
165+
async checkUserActivity(item: SEPItem, username: string): Promise<{
157166
daysSinceActivity: number;
158167
shouldPing: boolean;
159168
}> {
160169
const events = await this.github.getEvents(item.number);
161170
const comments = await this.github.getComments(item.number);
162171

163-
// Find last activity by this maintainer
172+
const lastActivity = this.findLastUserActivity(username, events, comments);
173+
174+
const daysSinceActivity = daysBetween(lastActivity ?? item.createdAt, new Date());
175+
const shouldPing = daysSinceActivity >= this.config.maintainerInactivityDays;
176+
177+
return { daysSinceActivity, shouldPing };
178+
}
179+
180+
/**
181+
* Find the last activity date of the person responsible for the SEP.
182+
*
183+
* For 'proposal' and 'accepted' states, this is the author.
184+
* For 'draft' state, this is the first assignee (sponsor), falling back to author.
185+
*
186+
* Falls back to item.createdAt when no user-specific activity is found.
187+
*/
188+
private findResponsiblePersonActivity(
189+
item: SEPItem,
190+
events: GitHubEvent[],
191+
comments: GitHubComment[],
192+
): Date {
193+
const username = this.getResponsibleUsername(item);
194+
return this.findLastUserActivity(username, events, comments) ?? item.createdAt;
195+
}
196+
197+
/**
198+
* Determine who the responsible person is for staleness tracking.
199+
*
200+
* For accepted SEPs, this is always the author (awaiting reference implementation).
201+
* For all other states, this is the first assignee (typically the sponsor),
202+
* falling back to the author if there are no assignees.
203+
*/
204+
private getResponsibleUsername(item: SEPItem): string {
205+
if (item.state === 'accepted') {
206+
return item.author;
207+
}
208+
return item.assignees[0] ?? item.author;
209+
}
210+
211+
/**
212+
* Find the most recent activity date for a specific user, excluding bot comments.
213+
*/
214+
private findLastUserActivity(
215+
username: string,
216+
events: GitHubEvent[],
217+
comments: GitHubComment[],
218+
): Date | null {
164219
let lastActivity: Date | null = null;
165220

166221
for (const event of events) {
167-
if (event.actor?.login === maintainerUsername) {
222+
if (event.actor?.login === username) {
168223
const eventDate = new Date(event.created_at);
169224
if (!lastActivity || eventDate > lastActivity) {
170225
lastActivity = eventDate;
@@ -173,39 +228,30 @@ export class SEPAnalyzer {
173228
}
174229

175230
for (const comment of comments) {
176-
if (comment.user?.login === maintainerUsername) {
231+
if (comment?.body.includes(BOT_COMMENT_MARKER)) {
232+
continue;
233+
}
234+
if (comment.user?.login === username) {
177235
const commentDate = new Date(comment.created_at);
178236
if (!lastActivity || commentDate > lastActivity) {
179237
lastActivity = commentDate;
180238
}
181239
}
182240
}
183241

184-
// If no activity found, use assignment date or item update date
185-
if (!lastActivity) {
186-
lastActivity = item.updatedAt;
187-
}
188-
189-
const daysSinceActivityValue = daysBetween(lastActivity, new Date());
190-
const shouldPing = daysSinceActivityValue >= this.config.maintainerInactivityDays;
191-
192-
return { daysSinceActivity: daysSinceActivityValue, shouldPing };
242+
return lastActivity;
193243
}
194244

195245
/**
196-
* Get the date of the last bot ping comment
246+
* Find the date of the last bot ping comment from pre-fetched comments.
197247
*/
198-
private async getLastBotPingDate(issueNumber: number): Promise<Date | null> {
199-
const comments = await this.github.getComments(issueNumber);
200-
201-
// Find most recent bot comment with our marker
248+
private findLastBotPingDate(comments: GitHubComment[]): Date | null {
202249
for (let i = comments.length - 1; i >= 0; i--) {
203250
const comment = comments[i];
204251
if (comment?.body.includes(BOT_COMMENT_MARKER)) {
205252
return new Date(comment.created_at);
206253
}
207254
}
208-
209255
return null;
210256
}
211257
}

0 commit comments

Comments
 (0)