CI: add maintainer ping auto-response

This commit is contained in:
Shadow
2026-02-26 13:30:12 -06:00
parent 344f54b84d
commit 03159f3942

View File

@@ -3,6 +3,8 @@ name: Auto response
on: on:
issues: issues:
types: [opened, edited, labeled] types: [opened, edited, labeled]
issue_comment:
types: [created]
pull_request_target: pull_request_target:
types: [labeled] types: [labeled]
@@ -42,6 +44,7 @@ jobs:
{ {
label: "r: testflight", label: "r: testflight",
close: true, close: true,
commentTriggers: ["testflight"],
message: "Not available, build from source.", message: "Not available, build from source.",
}, },
{ {
@@ -55,11 +58,76 @@ jobs:
close: true, close: true,
lock: true, lock: true,
lockReason: "off-topic", lockReason: "off-topic",
commentTriggers: ["moltbook"],
message: message:
"OpenClaw is not affiliated with Moltbook, and issues related to Moltbook should not be submitted here.", "OpenClaw is not affiliated with Moltbook, and issues related to Moltbook should not be submitted here.",
}, },
]; ];
const maintainerTeam = "maintainer";
const pingWarningMessage =
"Please dont spam-ping multiple maintainers at once. Be patient, or join our community Discord for help: https://discord.gg/clawd";
const mentionRegex = /@([A-Za-z0-9-]+)/g;
const maintainerCache = new Map();
const normalizeLogin = (login) => login.toLowerCase();
const isMaintainer = async (login) => {
if (!login) {
return false;
}
const normalized = normalizeLogin(login);
if (maintainerCache.has(normalized)) {
return maintainerCache.get(normalized);
}
let isMember = false;
try {
const membership = await github.rest.teams.getMembershipForUserInOrg({
org: context.repo.owner,
team_slug: maintainerTeam,
username: normalized,
});
isMember = membership?.data?.state === "active";
} catch (error) {
if (error?.status !== 404) {
throw error;
}
}
maintainerCache.set(normalized, isMember);
return isMember;
};
const countMaintainerMentions = async (body, authorLogin) => {
if (!body) {
return 0;
}
const normalizedAuthor = authorLogin ? normalizeLogin(authorLogin) : "";
if (normalizedAuthor && (await isMaintainer(normalizedAuthor))) {
return 0;
}
const haystack = body.toLowerCase();
const teamMention = `@${context.repo.owner.toLowerCase()}/${maintainerTeam}`;
if (haystack.includes(teamMention)) {
return 3;
}
const mentions = new Set();
for (const match of body.matchAll(mentionRegex)) {
mentions.add(normalizeLogin(match[1]));
}
if (normalizedAuthor) {
mentions.delete(normalizedAuthor);
}
let count = 0;
for (const login of mentions) {
if (await isMaintainer(login)) {
count += 1;
}
}
return count;
};
const triggerLabel = "trigger-response"; const triggerLabel = "trigger-response";
const target = context.payload.issue ?? context.payload.pull_request; const target = context.payload.issue ?? context.payload.pull_request;
if (!target) { if (!target) {
@@ -72,6 +140,63 @@ jobs:
.filter((name) => typeof name === "string"), .filter((name) => typeof name === "string"),
); );
const issue = context.payload.issue;
const pullRequest = context.payload.pull_request;
const comment = context.payload.comment;
if (comment) {
const authorLogin = comment.user?.login ?? "";
if (comment.user?.type === "Bot" || authorLogin.endsWith("[bot]")) {
return;
}
const commentBody = comment.body ?? "";
const responses = [];
const mentionCount = await countMaintainerMentions(commentBody, authorLogin);
if (mentionCount >= 3) {
responses.push(pingWarningMessage);
}
const commentHaystack = commentBody.toLowerCase();
const commentRule = rules.find((item) =>
(item.commentTriggers ?? []).some((trigger) =>
commentHaystack.includes(trigger),
),
);
if (commentRule) {
responses.push(commentRule.message);
}
if (responses.length > 0) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: target.number,
body: responses.join("\n\n"),
});
}
return;
}
if (issue) {
const action = context.payload.action;
if (action === "opened" || action === "edited") {
const issueText = `${issue.title ?? ""}\n${issue.body ?? ""}`.trim();
const authorLogin = issue.user?.login ?? "";
const mentionCount = await countMaintainerMentions(
issueText,
authorLogin,
);
if (mentionCount >= 3) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: pingWarningMessage,
});
}
}
}
const hasTriggerLabel = labelSet.has(triggerLabel); const hasTriggerLabel = labelSet.has(triggerLabel);
if (hasTriggerLabel) { if (hasTriggerLabel) {
labelSet.delete(triggerLabel); labelSet.delete(triggerLabel);
@@ -94,7 +219,6 @@ jobs:
return; return;
} }
const issue = context.payload.issue;
if (issue) { if (issue) {
const title = issue.title ?? ""; const title = issue.title ?? "";
const body = issue.body ?? ""; const body = issue.body ?? "";
@@ -136,7 +260,6 @@ jobs:
const noisyPrMessage = const noisyPrMessage =
"Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch."; "Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.";
const pullRequest = context.payload.pull_request;
if (pullRequest) { if (pullRequest) {
if (labelSet.has(dirtyLabel)) { if (labelSet.has(dirtyLabel)) {
await github.rest.issues.createComment({ await github.rest.issues.createComment({