feat(external-plugins): fast-track first-party reviews and note org ownership

Auto-accept first-party cards on the review board with /approve suggested,
skipping AI review unless the maintainer opts the card back into Unreviewed.
Show ownership and fast-track pills on cards.

Add a "Repository owner" reviewer-signal row to intake comments for
organization-owned repositories; user-owned repositories get no row.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 821f4796-505e-4fb8-96cf-0b5bbf80a636
This commit is contained in:
Aaron Powell
2026-10-13 10:51:42 +11:00
co-authored by Copilot App
parent 36667634be
commit b653c23244
8 changed files with 158 additions and 2 deletions
@@ -18,6 +18,11 @@ Open it by asking Copilot to "open the plugin review board" (canvas id `external
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.
- **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.
Fast-track never overrides an existing review or a bucket you chose; drag a card to *Unreviewed* to opt it
out and have it reviewed normally. Cards show *First party* / *Third party* pills from the intake labels.
- **Details panel** shows the AI review, the rendered issue body, and all comments.
- **Quick actions** post `/approve` (with an optional note) or `/reject <reason>` straight to the issue
after a confirm click. The reject reason is prefilled from the AI's suggested comment.
@@ -15,6 +15,33 @@ export const COLUMNS = [
];
const HISTORY_LIMIT = 200;
export const FIRST_PARTY_LABEL = "first party";
// 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 choices are kept;
// dragging a card back to Unreviewed opts it out so it gets a normal AI review.
function applyFirstPartyFastTrack(item) {
if (
!item.labels?.includes(FIRST_PARTY_LABEL) ||
item.fastTrackDismissed ||
item.decision ||
item.review ||
item.manualColumn ||
item.reviewStatus === "queued"
) {
return false;
}
item.review = {
recommendation: "accept",
fastTracked: true,
rationale:
"First-party submission (repository owned by a Microsoft/GitHub organization) that passed automated intake validation; fast-tracked to approval.",
suggestedComment: "/approve",
reviewedAt: new Date().toISOString(),
};
item.reviewStatus = "reviewed";
return true;
}
function newQueueId() {
return randomBytes(6).toString("base64url");
@@ -101,6 +128,7 @@ export class BoardStore {
const incoming = new Map(issues.map((issue) => [String(issue.number), issue]));
const added = [];
const removed = [];
const fastTracked = [];
for (const [key, issue] of incoming) {
const existing = this.state.items[key];
if (existing) {
@@ -123,6 +151,7 @@ export class BoardStore {
};
added.push(issue.number);
}
if (applyFirstPartyFastTrack(this.state.items[key])) fastTracked.push(issue.number);
}
for (const key of Object.keys(this.state.items)) {
if (!incoming.has(key)) {
@@ -132,7 +161,7 @@ export class BoardStore {
}
this.state.lastRefreshedAt = new Date().toISOString();
await this.commit();
return { added, removed, total: incoming.size };
return { added, removed, fastTracked, total: incoming.size };
}
pendingReview(numbers) {
@@ -218,6 +247,7 @@ export class BoardStore {
const item = this.getItem(number);
if (item.decision) throw new Error(`#${number} has already been actioned.`);
if (column === "unreviewed") {
if (item.review?.fastTracked || item.labels?.includes(FIRST_PARTY_LABEL)) item.fastTrackDismissed = true;
item.review = null;
item.reviewStatus = "unreviewed";
item.manualColumn = null;
@@ -115,8 +115,15 @@ function signalPills(item) {
pills.push(h("span", { class: `pill pill-${level.toLowerCase()}`, title: "AGT contributor risk" }, `AGT ${level}`));
} else if (label === "external-plugin-canvas") {
pills.push(h("span", { class: "pill" }, "canvas"));
} else if (label === "first party") {
pills.push(h("span", { class: "pill pill-first-party", title: "Repository owned by a Microsoft/GitHub organization" }, "First party"));
} else if (label === "third party") {
pills.push(h("span", { class: "pill pill-third-party", title: "Repository owned by a third-party organization" }, "Third party"));
}
}
if (item.review?.fastTracked && !item.decision) {
pills.push(h("span", { class: "pill pill-approve", title: "First-party submission fast-tracked to approval" }, "Fast-tracked"));
}
if (item.decision) {
pills.push(h("span", { class: `pill pill-${item.decision.kind}` }, item.decision.kind === "approve" ? "Approved" : "Rejected"));
} else if (item.pendingDecision) {
@@ -391,7 +398,7 @@ function renderReview(item) {
return wrapper;
}
const rows = [
["Recommendation", RECOMMENDATION_LABELS[review.recommendation] + (item.manualColumn ? ` (you moved it to ${RECOMMENDATION_LABELS[item.manualColumn]})` : "")],
["Recommendation", RECOMMENDATION_LABELS[review.recommendation] + (review.fastTracked ? " (first-party fast-track, no AI review)" : "") + (item.manualColumn ? ` (you moved it to ${RECOMMENDATION_LABELS[item.manualColumn]})` : "")],
["Plugin", review.pluginName],
["Repository", review.repository ? h("a", { href: `https://github.com/${review.repository}`, target: "_blank", rel: "noopener noreferrer" }, review.repository) : null],
["Repo facts", review.repoFacts],
@@ -546,6 +553,7 @@ ui.refresh.addEventListener("click", () =>
const parts = [`${result.total} open`];
if (result.added.length) parts.push(`${result.added.length} new`);
if (result.removed.length) parts.push(`${result.removed.length} removed`);
if (result.fastTracked?.length) parts.push(`${result.fastTracked.length} first-party fast-tracked`);
toast(`Refreshed: ${parts.join(", ")}`);
if (selected != null && activeTab !== "review") loadDetail(selected, { force: true });
} catch (error) {
@@ -376,6 +376,16 @@ h2 {
background: var(--red-muted);
color: var(--red);
}
.pill-first-party {
border-color: transparent;
background: var(--green-muted);
color: var(--green);
}
.pill-third-party {
border-color: transparent;
background: var(--purple-muted);
color: var(--purple);
}
.pill-queued {
border-color: transparent;
background: var(--purple-muted);
@@ -69,3 +69,14 @@ and read the `/approve` / `/reject` comments on the most recent ones.
- `probably-reject` — likely reject, but something specific deserves a quick maintainer look.
- `needs-review` — genuine judgement call.
- `accept` — fits the approval pattern.
## Ownership labels
Intake labels submissions by who owns the plugin repository:
- `first party` — owned by a Microsoft/GitHub organization (Microsoft, GitHub, Azure, dotnet, MicrosoftDocs,
microsoftgraph). The board fast-tracks these straight to `accept` without an AI review. If one is sent for
review anyway, recommend `accept` unless there is a hard failure (broken/missing plugin contents,
security concern, or something that would have failed intake validation).
- `third party` — owned by another organization. This is a signal only; apply the normal decision pattern.
- No ownership label — owned by a user account (or ownership couldn't be determined).