Compare commits

...
3 Commits
Author SHA1 Message Date
Daniel SamsonandClaude Opus 4.6 546d2615c7 feat: re-trigger tester when human comments on a PR
Publish Image / publish (push) Successful in 22s
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 SamsonandClaude Opus 4.6 f693d56762 Handle Gitea review webhook type field alongside state
Publish Image / publish (push) Successful in 23s
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 SamsonandClaude Opus 4.6 a72ea9ad1d Fix BullMQ job dedup — remove completed/failed jobs
Publish Image / publish (push) Successful in 21s
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
+26 -3
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,
+22 -3
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;
+6
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) {
+3 -1
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;
}