diff --git a/.github/workflows/collect-corrections.yml b/.github/workflows/collect-corrections.yml index 819e19d15..5284e3342 100644 --- a/.github/workflows/collect-corrections.yml +++ b/.github/workflows/collect-corrections.yml @@ -1,8 +1,18 @@ -name: Collect triage agent corrections +name: Submit triage agent feedback on: repository_dispatch: types: [triage_feedback] + workflow_dispatch: + inputs: + issue_number: + description: "Issue number to submit feedback for" + required: true + type: string + feedback: + description: "Feedback text describing what the triage agent got wrong" + required: true + type: string concurrency: group: collect-corrections diff --git a/scripts/corrections/collect-corrections.js b/scripts/corrections/collect-corrections.js index caeca42b6..a03a1c2ad 100644 --- a/scripts/corrections/collect-corrections.js +++ b/scripts/corrections/collect-corrections.js @@ -82,7 +82,12 @@ function resolveContext(payload, sender) { throw new Error("Missing feedback in payload"); } - return { issueNumber: Number(issueNumber), feedback, sender }; + const parsed = Number(issueNumber); + if (!Number.isFinite(parsed) || parsed < 1 || !Number.isInteger(parsed)) { + throw new Error(`Invalid issue_number: ${issueNumber}`); + } + + return { issueNumber: parsed, feedback, sender }; } /** @@ -203,7 +208,7 @@ async function maybeAssignCCA(github, owner, repo, trackingIssue, correctionCoun */ module.exports = async ({ github, context }) => { const { owner, repo } = context.repo; - const payload = context.payload.client_payload ?? {}; + const payload = context.payload.client_payload ?? context.payload.inputs ?? {}; const sender = context.payload.sender?.login ?? "unknown"; const correction = resolveContext(payload, sender); diff --git a/scripts/corrections/test/collect-corrections.test.ts b/scripts/corrections/test/collect-corrections.test.ts index 939bae188..ade318dd9 100644 --- a/scripts/corrections/test/collect-corrections.test.ts +++ b/scripts/corrections/test/collect-corrections.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; const mod = await import("../collect-corrections.js"); const { @@ -134,6 +134,24 @@ describe("resolveContext", () => { resolveContext({ issue_number: "1" }, "u"), ).toThrow("Missing feedback"); }); + + it("throws on non-numeric issue number", () => { + expect(() => + resolveContext({ issue_number: "abc", feedback: "test" }, "u"), + ).toThrow("Invalid issue_number: abc"); + }); + + it("throws on negative issue number", () => { + expect(() => + resolveContext({ issue_number: "-1", feedback: "test" }, "u"), + ).toThrow("Invalid issue_number: -1"); + }); + + it("throws on decimal issue number", () => { + expect(() => + resolveContext({ issue_number: "1.5", feedback: "test" }, "u"), + ).toThrow("Invalid issue_number: 1.5"); + }); }); // --------------------------------------------------------------------------- @@ -304,6 +322,42 @@ describe("appendCorrection", () => { }); }); +describe("module entrypoint - workflow_dispatch", () => { + it("processes feedback from workflow_dispatch inputs", async () => { + const github = mockGitHub({ + listForRepo: vi.fn().mockResolvedValue({ + data: [{ number: 50, assignees: [], body: trackingBodyForEntrypoint }], + }), + }); + const context = { + repo: { owner: OWNER, repo: REPO }, + payload: { + // workflow_dispatch has no client_payload; inputs carry the data + inputs: { issue_number: "7", feedback: "Should be enhancement" }, + sender: { login: "dispatcher" }, + }, + }; + + await mod.default({ github, context }); + + // Verify the correction was appended referencing the right issue + expect(github.rest.issues.update).toHaveBeenCalledWith( + expect.objectContaining({ + issue_number: 50, + body: expect.stringContaining("[#7]"), + }), + ); + }); +}); + +const trackingBodyForEntrypoint = [ + "# Triage Agent Corrections", + "", + "| Issue | Feedback | Submitted by | Date |", + "|-------|----------|--------------|------|", + "", +].join("\n"); + describe("maybeAssignCCA", () => { it("assigns CCA when threshold is reached", async () => { const github = mockGitHub();