Compare commits

...

3 Commits
v1.3.6 ... main

Author SHA1 Message Date
Daniel Samson 546d2615c7
feat: re-trigger tester when human comments on a PR
Publish Image / publish (push) Successful in 22s Details
PR comments fire as issue_comment webhooks in Gitea. Previously the
handler only re-triggered the analyst for regular issues. Now it
detects PR comments via the pull_request field and queues a test
workspace using the review bot account.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 20:57:46 +01:00
Daniel Samson f693d56762
Handle Gitea review webhook type field alongside state
Publish Image / publish (push) Successful in 23s Details
Gitea webhook payloads use "type" (e.g. "pull_request_review_rejected")
instead of "state" (e.g. "REQUEST_CHANGES"). Map both formats so
the pr-rework handler works with all Gitea webhook variations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 18:13:51 +01:00
Daniel Samson a72ea9ad1d
Fix BullMQ job dedup — remove completed/failed jobs
Publish Image / publish (push) Successful in 21s Details
Completed jobs with the same jobId block new jobs from being added.
Add removeOnComplete and removeOnFail to all job options so the same
task can be re-triggered after completion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 16:37:52 +01:00
4 changed files with 57 additions and 7 deletions

View File

@ -4,7 +4,9 @@ import type { TaskQueue } from "../queue.js";
import type { Config } from "../config.js";
/**
* Issue comment created or edited re-trigger analyst if still in analysis.
* Issue comment created or edited:
* - On a PR re-trigger tester
* - On an issue still in analysis re-trigger analyst
*
* Replaces: issue-comment-reply.json
*/
@ -23,6 +25,29 @@ export function issueComment(config: Config, queue: TaskQueue) {
return c.text("ignored: bot comment", 200);
}
const [org, repo] = event.repository.full_name.split("/");
const isPullRequest = !!event.issue.pull_request;
if (isPullRequest) {
// Comment on a PR → re-trigger tester
const task: TaskRequest = {
taskType: "test",
issueNumber: event.issue.number,
giteaOrg: org,
giteaRepo: repo,
repoCloneUrl: event.repository.clone_url,
giteaToken: config.giteaReviewToken,
};
const queued = await queue.enqueue(task, {
useReviewAccount: true,
body: "🤖 Re-triggering **test** — workspace queued.",
});
return c.json({ ok: true, queued });
}
// Regular issue comment → re-trigger analyst if still in analysis
const labels = event.issue.labels.map((l) => l.name);
const pastAnalysis =
labels.includes("ready-for-architecture") ||
@ -32,8 +57,6 @@ export function issueComment(config: Config, queue: TaskQueue) {
return c.text("ignored: issue past analysis stage", 200);
}
const [org, repo] = event.repository.full_name.split("/");
const task: TaskRequest = {
taskType: "analyse",
issueNumber: event.issue.number,

View File

@ -16,14 +16,33 @@ export function prRework(config: Config, queue: TaskQueue) {
return c.text("ignored: not a reviewed event", 200);
}
if (!event.review?.state) {
console.log(`[pr-rework] ignored: no review state (review: ${JSON.stringify(event.review)})`);
if (!event.review) {
console.log(`[pr-rework] ignored: no review object`);
return c.text("ignored: no review data", 200);
}
const [org, repo] = event.repository.full_name.split("/");
const prNumber = event.pull_request.number;
const reviewState = event.review.state.toLowerCase();
// Gitea webhook payloads use "state" in the API but "type" in webhooks
// state: "REQUEST_CHANGES" | "APPROVED" | "COMMENT"
// type: "pull_request_review_rejected" | "pull_request_review_approved" | ...
let reviewState: string;
if (event.review.state) {
reviewState = event.review.state.toLowerCase();
} else if (event.review.type) {
const typeMap: Record<string, string> = {
pull_request_review_rejected: "request_changes",
pull_request_review_approved: "approved",
pull_request_review_comment: "comment",
};
reviewState = typeMap[event.review.type] || event.review.type;
} else {
console.log(`[pr-rework] ignored: no review state or type (review: ${JSON.stringify(event.review)})`);
return c.text("ignored: no review state", 200);
}
console.log(`[pr-rework] PR #${prNumber} review: ${reviewState}`);
let taskType: string | null = null;
let giteaToken: string;

View File

@ -161,6 +161,8 @@ export class TaskQueue {
jobId: `create-${key}`,
attempts: 3,
backoff: { type: "exponential", delay: 5000 },
removeOnComplete: true,
removeOnFail: true,
},
);
} catch (err: unknown) {
@ -201,6 +203,8 @@ export class TaskQueue {
jobId: `cleanup-${workspaceName}`,
attempts: 5,
backoff: { type: "exponential", delay: 10000 },
removeOnComplete: true,
removeOnFail: true,
},
);
@ -311,6 +315,8 @@ export class TaskQueue {
jobId: `cleanup-stale-${ws.name}-${Date.now()}`,
attempts: 5,
backoff: { type: "exponential", delay: 10000 },
removeOnComplete: true,
removeOnFail: true,
},
);
} catch (err) {

View File

@ -30,6 +30,7 @@ export interface GiteaIssue {
state: string;
user: GiteaUser;
labels: GiteaLabel[];
pull_request?: { merged: boolean } | null;
}
export interface GiteaPullRequest {
@ -46,7 +47,8 @@ export interface GiteaPullRequest {
export interface GiteaReview {
id: number;
body: string;
state: string; // "approved", "request_changes", "comment"
state?: string; // "approved", "request_changes", "comment" (API response)
type?: string; // "pull_request_review_rejected" | "pull_request_review_approved" (webhook payload)
user: GiteaUser;
}