import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { pathToFileURL } from "node:url"; export let routingLabels = [ "triage/agent-candidate", "triage/human-response", "triage/needs-info", "triage/backlog-note", ]; function github(path) { return JSON.parse(execFileSync("gh", ["api", "--paginate", "--slurp", path], { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, })); } function internalAuthor(issue) { return ["OWNER", "MEMBER", "COLLABORATOR"].includes(issue.author_association); } export function selectIssues(repository, event, gh = github) { if (event.issue?.pull_request || event.comment?.user.type === "Bot") return []; let issues = event.issue ? gh(`repos/${repository}/issues/${event.issue.number}`).flat() : gh(`repos/${repository}/issues?state=open&per_page=100`).flat(); let candidates = []; issues.sort((a, b) => { return Number(internalAuthor(a)) - Number(internalAuthor(b)) || a.created_at.localeCompare(b.created_at) || a.number - b.number; }); for (let issue of issues) { if (issue.pull_request || issue.state !== "open" || issue.locked || issue.user.type === "Bot") { continue; } if (issue.labels.some((label) => ["no-triage", "spam"].includes(label.name))) continue; let internal = internalAuthor(issue); // Internal bookmarks need labels, without a bot comment to track each unchanged note. if ( !event.issue && internal && issue.labels.some((label) => routingLabels.includes(label.name)) ) continue; let comments = gh(`repos/${repository}/issues/${issue.number}/comments?per_page=100`).flat(); let humanComments = comments.filter((comment) => comment.user.type !== "Bot"); let fingerprint = createHash("sha256").update(JSON.stringify([ issue.title, issue.body, humanComments.map(({ id, body, updated_at }) => ({ id, body, updated_at })), ])).digest("hex").slice(0, 20); let receipt = `issue-triage:${issue.number}:${fingerprint}`; let recordUrl = process.env.GITHUB_RUN_ID ? `https://github.com/${repository}/actions/runs/${process.env.GITHUB_RUN_ID}` : `https://github.com/${repository}/issues/${issue.number}`; let marker = `[Triage record](${recordUrl}#${receipt})`; if ( comments.some((comment) => comment.user.login === "github-actions[bot]" && comment.user.type === "Bot" && (comment.body.includes(receipt) // The first deployed run lost HTML receipts during safe-output sanitization. || (comment.body.includes("