github-triage.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. /// <reference path="../env.d.ts" />
  2. import { tool } from "@opencode-ai/plugin"
  3. import DESCRIPTION from "./github-triage.txt"
  4. const TEAM = {
  5. desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"],
  6. zen: ["fwang", "MrMushrooooom"],
  7. tui: ["thdxr", "kommander", "rekram1-node"],
  8. core: ["thdxr", "rekram1-node", "jlongster"],
  9. docs: ["R44VC0RP"],
  10. windows: ["Hona"],
  11. } as const
  12. const ASSIGNEES = [...new Set(Object.values(TEAM).flat())]
  13. function pick<T>(items: readonly T[]) {
  14. return items[Math.floor(Math.random() * items.length)]!
  15. }
  16. function getIssueNumber(): number {
  17. const issue = parseInt(process.env.ISSUE_NUMBER ?? "", 10)
  18. if (!issue) throw new Error("ISSUE_NUMBER env var not set")
  19. return issue
  20. }
  21. async function githubFetch(endpoint: string, options: RequestInit = {}) {
  22. const response = await fetch(`https://api.github.com${endpoint}`, {
  23. ...options,
  24. headers: {
  25. Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
  26. Accept: "application/vnd.github+json",
  27. "Content-Type": "application/json",
  28. ...options.headers,
  29. },
  30. })
  31. if (!response.ok) {
  32. throw new Error(`GitHub API error: ${response.status} ${response.statusText}`)
  33. }
  34. return response.json()
  35. }
  36. export default tool({
  37. description: DESCRIPTION,
  38. args: {
  39. assignee: tool.schema
  40. .enum(ASSIGNEES as [string, ...string[]])
  41. .describe("The username of the assignee")
  42. .default("rekram1-node"),
  43. labels: tool.schema
  44. .array(tool.schema.enum(["nix", "opentui", "perf", "web", "desktop", "zen", "docs", "windows", "core"]))
  45. .describe("The labels(s) to add to the issue")
  46. .default([]),
  47. },
  48. async execute(args) {
  49. const issue = getIssueNumber()
  50. const owner = "anomalyco"
  51. const repo = "opencode"
  52. const results: string[] = []
  53. let labels = [...new Set(args.labels.map((x) => (x === "desktop" ? "web" : x)))]
  54. const web = labels.includes("web")
  55. const text = `${process.env.ISSUE_TITLE ?? ""}\n${process.env.ISSUE_BODY ?? ""}`.toLowerCase()
  56. const zen = /\bzen\b/.test(text) || text.includes("opencode black")
  57. const nix = /\bnix(os)?\b/.test(text)
  58. if (labels.includes("nix") && !nix) {
  59. labels = labels.filter((x) => x !== "nix")
  60. results.push("Dropped label: nix (issue does not mention nix)")
  61. }
  62. const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee
  63. if (labels.includes("zen") && !zen) {
  64. throw new Error("Only add the zen label when issue title/body contains 'zen'")
  65. }
  66. if (web && !nix && !(TEAM.desktop as readonly string[]).includes(assignee)) {
  67. throw new Error("Web issues must be assigned to adamdotdevin, iamdavidhill, Brendonovich, or nexxeln")
  68. }
  69. if ((TEAM.zen as readonly string[]).includes(assignee) && !labels.includes("zen")) {
  70. throw new Error("Only zen issues should be assigned to fwang or MrMushrooooom")
  71. }
  72. if (assignee === "Hona" && !labels.includes("windows")) {
  73. throw new Error("Only windows issues should be assigned to Hona")
  74. }
  75. if (assignee === "R44VC0RP" && !labels.includes("docs")) {
  76. throw new Error("Only docs issues should be assigned to R44VC0RP")
  77. }
  78. if (assignee === "kommander" && !labels.includes("opentui")) {
  79. throw new Error("Only opentui issues should be assigned to kommander")
  80. }
  81. await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/assignees`, {
  82. method: "POST",
  83. body: JSON.stringify({ assignees: [assignee] }),
  84. })
  85. results.push(`Assigned @${assignee} to issue #${issue}`)
  86. if (labels.length > 0) {
  87. await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/labels`, {
  88. method: "POST",
  89. body: JSON.stringify({ labels }),
  90. })
  91. results.push(`Added labels: ${labels.join(", ")}`)
  92. }
  93. return results.join("\n")
  94. },
  95. })