Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
546d2615c7 | ||
|
|
f693d56762 | ||
|
|
a72ea9ad1d | ||
|
|
2cdf68ddff | ||
|
|
81aee2f7fc | ||
|
|
f495ea391f | ||
|
|
d19798dfea | ||
|
|
0812a9be37 |
@@ -128,6 +128,10 @@ data "coder_parameter" "task_type" {
|
|||||||
name = "Rework PR"
|
name = "Rework PR"
|
||||||
value = "rework-pr"
|
value = "rework-pr"
|
||||||
}
|
}
|
||||||
|
option {
|
||||||
|
name = "Rework Spec"
|
||||||
|
value = "rework-spec"
|
||||||
|
}
|
||||||
option {
|
option {
|
||||||
name = "Release"
|
name = "Release"
|
||||||
value = "release"
|
value = "release"
|
||||||
@@ -470,7 +474,7 @@ resource "coder_agent" "main" {
|
|||||||
git fetch origin develop 2>/dev/null && git checkout develop 2>/dev/null || true
|
git fetch origin develop 2>/dev/null && git checkout develop 2>/dev/null || true
|
||||||
|
|
||||||
# Lightweight stages only need the code, not a full build
|
# Lightweight stages only need the code, not a full build
|
||||||
LIGHT_STAGES="analyse architect release maintenance"
|
LIGHT_STAGES="analyse architect rework-spec release maintenance"
|
||||||
if echo "$LIGHT_STAGES" | grep -qw "$TASK_TYPE"; then
|
if echo "$LIGHT_STAGES" | grep -qw "$TASK_TYPE"; then
|
||||||
echo "Lightweight stage ($TASK_TYPE) — skipping build"
|
echo "Lightweight stage ($TASK_TYPE) — skipping build"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import type { TaskQueue } from "../queue.js";
|
|||||||
import type { Config } from "../config.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
|
* Replaces: issue-comment-reply.json
|
||||||
*/
|
*/
|
||||||
@@ -23,6 +25,29 @@ export function issueComment(config: Config, queue: TaskQueue) {
|
|||||||
return c.text("ignored: bot comment", 200);
|
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 labels = event.issue.labels.map((l) => l.name);
|
||||||
const pastAnalysis =
|
const pastAnalysis =
|
||||||
labels.includes("ready-for-architecture") ||
|
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);
|
return c.text("ignored: issue past analysis stage", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [org, repo] = event.repository.full_name.split("/");
|
|
||||||
|
|
||||||
const task: TaskRequest = {
|
const task: TaskRequest = {
|
||||||
taskType: "analyse",
|
taskType: "analyse",
|
||||||
issueNumber: event.issue.number,
|
issueNumber: event.issue.number,
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ export function issueLabel(config: Config, queue: TaskQueue) {
|
|||||||
return async (c: Context) => {
|
return async (c: Context) => {
|
||||||
const event = await c.req.json<IssueEvent>();
|
const event = await c.req.json<IssueEvent>();
|
||||||
|
|
||||||
if (event.action !== "label_updated" && event.action !== "labeled") {
|
console.log(`[issue-label] action="${event.action}" issue=#${event.issue.number} labels=[${event.issue.labels.map((l) => l.name).join(", ")}]`);
|
||||||
|
|
||||||
|
if (!["labeled", "label_updated", "created"].includes(event.action)) {
|
||||||
return c.text("ignored: not a label event", 200);
|
return c.text("ignored: not a label event", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,15 +16,41 @@ export function prRework(config: Config, queue: TaskQueue) {
|
|||||||
return c.text("ignored: not a reviewed event", 200);
|
return c.text("ignored: not a reviewed event", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 [org, repo] = event.repository.full_name.split("/");
|
||||||
const prNumber = event.pull_request.number;
|
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 taskType: string | null = null;
|
||||||
let giteaToken: string;
|
let giteaToken: string;
|
||||||
|
const branch = event.pull_request.head.ref;
|
||||||
|
const isArchitectPR = branch.startsWith("docs/");
|
||||||
|
|
||||||
if (reviewState === "request_changes") {
|
if (reviewState === "request_changes") {
|
||||||
taskType = "rework-pr";
|
taskType = isArchitectPR ? "rework-spec" : "rework-pr";
|
||||||
giteaToken = config.giteaDevToken;
|
giteaToken = config.giteaDevToken;
|
||||||
} else if (reviewState === "approved") {
|
} else if (reviewState === "approved") {
|
||||||
taskType = "test";
|
taskType = "test";
|
||||||
|
|||||||
@@ -161,6 +161,8 @@ export class TaskQueue {
|
|||||||
jobId: `create-${key}`,
|
jobId: `create-${key}`,
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
backoff: { type: "exponential", delay: 5000 },
|
backoff: { type: "exponential", delay: 5000 },
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@@ -201,6 +203,8 @@ export class TaskQueue {
|
|||||||
jobId: `cleanup-${workspaceName}`,
|
jobId: `cleanup-${workspaceName}`,
|
||||||
attempts: 5,
|
attempts: 5,
|
||||||
backoff: { type: "exponential", delay: 10000 },
|
backoff: { type: "exponential", delay: 10000 },
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -311,6 +315,8 @@ export class TaskQueue {
|
|||||||
jobId: `cleanup-stale-${ws.name}-${Date.now()}`,
|
jobId: `cleanup-stale-${ws.name}-${Date.now()}`,
|
||||||
attempts: 5,
|
attempts: 5,
|
||||||
backoff: { type: "exponential", delay: 10000 },
|
backoff: { type: "exponential", delay: 10000 },
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -109,12 +109,14 @@ export class CoderClient {
|
|||||||
|
|
||||||
async deleteWorkspace(workspaceId: string): Promise<void> {
|
async deleteWorkspace(workspaceId: string): Promise<void> {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${this.baseUrl}/api/v2/workspaces/${workspaceId}`,
|
`${this.baseUrl}/api/v2/workspaces/${workspaceId}/builds`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
"Coder-Session-Token": this.token,
|
"Coder-Session-Token": this.token,
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({ transition: "delete" }),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -30,6 +30,7 @@ export interface GiteaIssue {
|
|||||||
state: string;
|
state: string;
|
||||||
user: GiteaUser;
|
user: GiteaUser;
|
||||||
labels: GiteaLabel[];
|
labels: GiteaLabel[];
|
||||||
|
pull_request?: { merged: boolean } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GiteaPullRequest {
|
export interface GiteaPullRequest {
|
||||||
@@ -46,7 +47,8 @@ export interface GiteaPullRequest {
|
|||||||
export interface GiteaReview {
|
export interface GiteaReview {
|
||||||
id: number;
|
id: number;
|
||||||
body: string;
|
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;
|
user: GiteaUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user