github-triage.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. /// <reference path="../env.d.ts" />
  2. import { tool } from "@kilocode/plugin"
  3. const TEAM = {
  4. desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"],
  5. zen: ["fwang", "MrMushrooooom"],
  6. tui: ["thdxr", "kommander", "rekram1-node"],
  7. core: ["thdxr", "rekram1-node", "jlongster"],
  8. docs: ["R44VC0RP"],
  9. windows: ["Hona"],
  10. } as const
  11. const ASSIGNEES = [...new Set(Object.values(TEAM).flat())]
  12. function pick<T>(items: readonly T[]) {
  13. return items[Math.floor(Math.random() * items.length)]!
  14. }
  15. function getIssueNumber(): number {
  16. const issue = parseInt(process.env.ISSUE_NUMBER ?? "", 10)
  17. if (!issue) throw new Error("ISSUE_NUMBER env var not set")
  18. return issue
  19. }
  20. async function githubFetch(endpoint: string, options: RequestInit = {}) {
  21. const response = await fetch(`https://api.github.com${endpoint}`, {
  22. ...options,
  23. headers: {
  24. Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
  25. Accept: "application/vnd.github+json",
  26. "Content-Type": "application/json",
  27. ...options.headers,
  28. },
  29. })
  30. if (!response.ok) {
  31. throw new Error(`GitHub API error: ${response.status} ${response.statusText}`)
  32. }
  33. return response.json()
  34. }
  35. export default tool({
  36. description: `Use this tool to assign and/or label a GitHub issue.
  37. Choose labels and assignee using the current triage policy and ownership rules.
  38. Pick the most fitting labels for the issue and assign one owner.
  39. If unsure, choose the team/section with the most overlap with the issue and assign a member from that team at random.`,
  40. args: {
  41. assignee: tool.schema
  42. .enum(["thdxr", "adamdotdevin", "rekram1-node", "fwang", "jayair", "kommander"])
  43. .describe("The username of the assignee")
  44. .default("rekram1-node"),
  45. labels: tool.schema
  46. .array(tool.schema.enum(["nix", "opentui", "perf", "desktop", "zen", "docs", "windows"]))
  47. .describe("The labels(s) to add to the issue")
  48. .default([]),
  49. },
  50. async execute(args) {
  51. const issue = getIssueNumber()
  52. // const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
  53. const owner = "anomalyco"
  54. const repo = "opencode"
  55. const results: string[] = []
  56. if (args.assignee === "adamdotdevin" && !args.labels.includes("desktop")) {
  57. throw new Error("Only desktop issues should be assigned to adamdotdevin")
  58. }
  59. if (args.assignee === "fwang" && !args.labels.includes("zen")) {
  60. throw new Error("Only zen issues should be assigned to fwang")
  61. }
  62. if (args.assignee === "kommander" && !args.labels.includes("opentui")) {
  63. throw new Error("Only opentui issues should be assigned to kommander")
  64. }
  65. // await octokit.rest.issues.addAssignees({
  66. // owner,
  67. // repo,
  68. // issue_number: issue,
  69. // assignees: [args.assignee],
  70. // })
  71. await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/assignees`, {
  72. method: "POST",
  73. body: JSON.stringify({ assignees: [args.assignee] }),
  74. })
  75. results.push(`Assigned @${args.assignee} to issue #${issue}`)
  76. const labels: string[] = args.labels.map((label) => (label === "desktop" ? "web" : label))
  77. if (labels.length > 0) {
  78. // await octokit.rest.issues.addLabels({
  79. // owner,
  80. // repo,
  81. // issue_number: issue,
  82. // labels,
  83. // })
  84. await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/labels`, {
  85. method: "POST",
  86. body: JSON.stringify({ labels }),
  87. })
  88. results.push(`Added labels: ${args.labels.join(", ")}`)
  89. }
  90. return results.join("\n")
  91. },
  92. })