mirror of
https://github.com/github/awesome-copilot.git
synced 2026-10-14 07:44:43 +00:00
fix(external-plugins): preserve cards during intake
Include submissions awaiting review or submitter fixes in the review board feed so refreshes preserve their existing state. Keep fast-track and approval gated on a ready intake state, and add regression coverage for card persistence across pending transitions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 821f4796-505e-4fb8-96cf-0b5bbf80a636
This commit is contained in:
co-authored by
Copilot App
parent
a48dab5961
commit
666a521cd5
@@ -1,7 +1,7 @@
|
||||
# External Plugin Review Board
|
||||
|
||||
A maintainer-only Copilot canvas extension for triaging `external-plugin` submissions that are
|
||||
`ready-for-review` in github/awesome-copilot.
|
||||
A maintainer-only Copilot canvas extension for triaging active `external-plugin` submissions in
|
||||
github/awesome-copilot.
|
||||
|
||||
Open it by asking Copilot to "open the plugin review board" (canvas id `external-plugin-review-board`).
|
||||
|
||||
@@ -16,8 +16,10 @@ Open it by asking Copilot to "open the plugin review board" (canvas id `external
|
||||
- **Re-review** (in the details panel) takes optional guidance on what to focus on, then has the agent
|
||||
spin up a separate sub-session to re-assess that one submission. The sub-session reports its result
|
||||
back and it's recorded on the card along with your guidance.
|
||||
- **Refresh** fetches open `external-plugin` + `ready-for-review` issues, adds new ones, and removes any
|
||||
that are closed or no longer ready for review.
|
||||
- **Refresh** fetches open `external-plugin` issues in `ready-for-review`, `awaiting-review`, or
|
||||
`requires-submitter-fixes` states. Pending intake cards stay on the board so their AI reviews,
|
||||
manual buckets, queued re-reviews, and fast-track dismissal state are preserved. Approval and
|
||||
first-party fast-track are unavailable until intake is ready.
|
||||
- **First-party fast-track**: cards labelled `first party` (repository owned by a Microsoft/GitHub
|
||||
organization) skip AI review and go straight to *Accept* with a suggested `/approve`. Only submissions
|
||||
that passed automated intake reach the board, so hard validation failures are still held back by intake.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { joinSession, createCanvas, CanvasError } from "@github/copilot-sdk/extension";
|
||||
import { BoardStore, RECOMMENDATIONS } from "./lib/state.mjs";
|
||||
import { BoardStore, isReadyForApproval, RECOMMENDATIONS } from "./lib/state.mjs";
|
||||
import * as gh from "./lib/github.mjs";
|
||||
import { buildReviewPrompt, buildRereviewPrompt, REVIEW_FIELDS_SCHEMA } from "./lib/review-prompt.mjs";
|
||||
import { startBoardServer, expectedError } from "./lib/server.mjs";
|
||||
@@ -49,7 +49,7 @@ async function refresh() {
|
||||
refreshInFlight ??= (async () => {
|
||||
try {
|
||||
await resolvePendingDecisions();
|
||||
const issues = await gh.listReadyIssues(store.state.repo);
|
||||
const issues = await gh.listBoardIssues(store.state.repo);
|
||||
detailCache.clear();
|
||||
return await store.syncIssues(issues);
|
||||
} finally {
|
||||
@@ -134,10 +134,7 @@ async function postDecision({ number, kind, comment }) {
|
||||
|
||||
const status = await gh.getIssueStatus(store.state.repo, number);
|
||||
if (status.state !== "OPEN") throw new Error(`#${number} is ${status.state.toLowerCase()}. Refresh the board.`);
|
||||
if (
|
||||
kind === "approve" &&
|
||||
(!status.labels.includes("ready-for-review") || status.labels.includes("awaiting-review"))
|
||||
) {
|
||||
if (kind === "approve" && !isReadyForApproval(status.labels)) {
|
||||
throw new Error(`#${number} is pending intake or is no longer ready-for-review, so /approve would be ignored.`);
|
||||
}
|
||||
|
||||
@@ -241,7 +238,7 @@ session = await joinSession({
|
||||
},
|
||||
{
|
||||
name: "refresh",
|
||||
description: "Fetch open ready-for-review external plugin issues; adds new ones and removes closed ones.",
|
||||
description: "Fetch open external plugin submissions in active intake states; adds new ones and removes inactive ones.",
|
||||
handler: wrap(() => refresh()),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -29,7 +29,22 @@ function runGh(args, { input } = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function listReadyIssues(repo) {
|
||||
export function mapBoardIssues(issues) {
|
||||
const boardStates = new Set(["ready-for-review", "awaiting-review", "requires-submitter-fixes"]);
|
||||
return issues
|
||||
.filter((issue) => (issue.labels ?? []).some((label) => boardStates.has(label.name)))
|
||||
.map((issue) => ({
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
author: issue.author?.login ?? "unknown",
|
||||
createdAt: issue.createdAt,
|
||||
updatedAt: issue.updatedAt,
|
||||
url: issue.url,
|
||||
labels: (issue.labels ?? []).map((label) => label.name),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function listBoardIssues(repo) {
|
||||
const out = await runGh([
|
||||
"issue",
|
||||
"list",
|
||||
@@ -37,8 +52,6 @@ export async function listReadyIssues(repo) {
|
||||
repo,
|
||||
"--label",
|
||||
"external-plugin",
|
||||
"--label",
|
||||
"ready-for-review",
|
||||
"--state",
|
||||
"open",
|
||||
"--limit",
|
||||
@@ -46,15 +59,7 @@ export async function listReadyIssues(repo) {
|
||||
"--json",
|
||||
"number,title,author,createdAt,updatedAt,labels,url",
|
||||
]);
|
||||
return JSON.parse(out).map((issue) => ({
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
author: issue.author?.login ?? "unknown",
|
||||
createdAt: issue.createdAt,
|
||||
updatedAt: issue.updatedAt,
|
||||
url: issue.url,
|
||||
labels: (issue.labels ?? []).map((label) => label.name),
|
||||
}));
|
||||
return mapBoardIssues(JSON.parse(out));
|
||||
}
|
||||
|
||||
const FULL_JSON = ["-H", "Accept: application/vnd.github.full+json"];
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { mapBoardIssues } from "./github.mjs";
|
||||
|
||||
test("board feed includes submissions in each active intake state", () => {
|
||||
const issues = ["ready-for-review", "awaiting-review", "requires-submitter-fixes", "rejected"].map(
|
||||
(state, index) => ({
|
||||
number: index + 1,
|
||||
title: `Submission ${index + 1}`,
|
||||
author: { login: "contributor" },
|
||||
labels: [{ name: "external-plugin" }, { name: state }],
|
||||
url: `https://github.com/github/awesome-copilot/issues/${index + 1}`,
|
||||
}),
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
mapBoardIssues(issues).map((issue) => issue.number),
|
||||
[1, 2, 3],
|
||||
);
|
||||
});
|
||||
@@ -16,6 +16,11 @@ export const COLUMNS = [
|
||||
|
||||
const HISTORY_LIMIT = 200;
|
||||
export const FIRST_PARTY_LABEL = "first party";
|
||||
const PENDING_INTAKE_LABELS = ["awaiting-review", "requires-submitter-fixes"];
|
||||
|
||||
export function isReadyForApproval(labels) {
|
||||
return labels.includes("ready-for-review") && !labels.some((label) => PENDING_INTAKE_LABELS.includes(label));
|
||||
}
|
||||
|
||||
// Validated submissions from first-party organizations skip AI triage and land in Accept. Only
|
||||
// cards without an AI review, queued review, or manual bucket are fast-tracked so maintainer
|
||||
@@ -24,7 +29,7 @@ function applyFirstPartyFastTrack(item) {
|
||||
if (
|
||||
!item.labels?.includes(FIRST_PARTY_LABEL) ||
|
||||
!item.labels?.includes("ready-for-review") ||
|
||||
item.labels?.includes("awaiting-review") ||
|
||||
item.labels?.some((label) => PENDING_INTAKE_LABELS.includes(label)) ||
|
||||
item.fastTrackDismissed ||
|
||||
item.decision ||
|
||||
item.review ||
|
||||
@@ -49,7 +54,7 @@ function clearFirstPartyFastTrack(item) {
|
||||
if (
|
||||
(item.labels?.includes(FIRST_PARTY_LABEL) &&
|
||||
item.labels?.includes("ready-for-review") &&
|
||||
!item.labels?.includes("awaiting-review")) ||
|
||||
!item.labels?.some((label) => PENDING_INTAKE_LABELS.includes(label))) ||
|
||||
!item.review?.fastTracked ||
|
||||
item.reviewStatus === "queued"
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user