mirror of
https://github.com/github/awesome-copilot.git
synced 2026-05-27 17:11:44 +00:00
Adding a new /rerun-intake command for when updates are required (#1786)
* Adding a new /rerun-intake command for when updates are required Reruns the intake process if feedback is given that will require the submitter to update something about the submittion. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Use rerun command constant in parser regex Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
This commit is contained in:
@@ -118,7 +118,7 @@ jobs:
|
||||
'',
|
||||
'### Required fixes',
|
||||
'',
|
||||
...(errors.length > 0 ? errors.map((error) => `- ${error}`) : ['- Re-run intake validation by updating the issue details.'])
|
||||
...(errors.length > 0 ? errors.map((error) => `- ${error}`) : ['- Edit the issue details and let intake rerun automatically, or comment `/rerun-intake` to trigger it again on demand.'])
|
||||
].join('\n');
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
@@ -493,7 +493,7 @@ jobs:
|
||||
'',
|
||||
reason,
|
||||
'',
|
||||
'If you address the feedback, open a new external plugin submission issue with the updated details.'
|
||||
'If you address the feedback, edit this issue with the updated details and have the issue author or a maintainer comment `/rerun-intake` to re-run automated intake.'
|
||||
].join('\n');
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
|
||||
@@ -4,6 +4,10 @@ on:
|
||||
issues:
|
||||
types: [opened, edited, reopened]
|
||||
|
||||
concurrency:
|
||||
group: external-plugin-intake-${{ github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
@@ -36,81 +40,10 @@ jobs:
|
||||
RESULT_JSON: ${{ steps.evaluation.outputs.result }}
|
||||
with:
|
||||
script: |
|
||||
const managedLabels = {
|
||||
'external-plugin': {
|
||||
color: 'FEF2C0',
|
||||
description: 'Public external plugin submission'
|
||||
},
|
||||
'awaiting-review': {
|
||||
color: 'FBCA04',
|
||||
description: 'Submission is waiting for automated intake validation'
|
||||
},
|
||||
'ready-for-review': {
|
||||
color: '0E8A16',
|
||||
description: 'Submission passed intake validation and is ready for maintainer review'
|
||||
},
|
||||
'approved': {
|
||||
color: '1D76DB',
|
||||
description: 'Submission was approved by a maintainer'
|
||||
},
|
||||
'rejected': {
|
||||
color: 'B60205',
|
||||
description: 'Submission was rejected or failed intake validation'
|
||||
}
|
||||
};
|
||||
const path = require('path');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
async function ensureLabel(name, config) {
|
||||
try {
|
||||
await github.rest.issues.createLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name,
|
||||
color: config.color,
|
||||
description: config.description
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status !== 422) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function syncManagedLabels(issueNumber, desiredLabels) {
|
||||
await Promise.all(Object.entries(managedLabels).map(([name, config]) => ensureLabel(name, config)));
|
||||
|
||||
const managedForSync = ['external-plugin', 'awaiting-review', 'ready-for-review', 'rejected'];
|
||||
const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
const currentManagedLabels = currentLabels
|
||||
.map((label) => label.name)
|
||||
.filter((name) => managedForSync.includes(name));
|
||||
|
||||
const labelsToAdd = [...desiredLabels].filter((name) => !currentManagedLabels.includes(name));
|
||||
const labelsToRemove = currentManagedLabels.filter((name) => !desiredLabels.has(name));
|
||||
|
||||
if (labelsToAdd.length > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
labels: labelsToAdd
|
||||
});
|
||||
}
|
||||
|
||||
for (const name of labelsToRemove) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
name
|
||||
});
|
||||
}
|
||||
}
|
||||
const intakeState = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-intake-state.mjs')).href);
|
||||
|
||||
const result = JSON.parse(process.env.RESULT_JSON);
|
||||
const issueNumber = context.issue.number;
|
||||
@@ -128,40 +61,14 @@ jobs:
|
||||
return;
|
||||
}
|
||||
|
||||
const desiredLabels = result.valid
|
||||
? new Set(['external-plugin', 'ready-for-review'])
|
||||
: new Set(['external-plugin', 'rejected']);
|
||||
|
||||
await syncManagedLabels(issueNumber, desiredLabels);
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
await intakeState.applyExternalPluginIntakeEvaluation({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100
|
||||
issueNumber,
|
||||
evaluation: result
|
||||
});
|
||||
|
||||
const existingComment = comments.find((comment) =>
|
||||
comment.user?.login === 'github-actions[bot]' &&
|
||||
comment.body?.includes(result.commentMarker)
|
||||
);
|
||||
|
||||
if (existingComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existingComment.id,
|
||||
body: result.commentBody
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
body: result.commentBody
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.valid && issueState === 'open') {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
name: External Plugin Rerun Intake Commands
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
concurrency:
|
||||
group: external-plugin-intake-${{ github.event.issue.number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
handle-command:
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
!github.event.issue.pull_request &&
|
||||
startsWith(github.event.comment.body, '/rerun-intake')
|
||||
steps:
|
||||
- name: Checkout staged branch
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
ref: staged
|
||||
|
||||
- name: Re-run external plugin intake
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
script: |
|
||||
const path = require('path');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
const intake = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-intake.mjs')).href);
|
||||
const intakeState = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-intake-state.mjs')).href);
|
||||
|
||||
const commentAuthor = context.payload.comment.user?.login;
|
||||
if (!commentAuthor || context.payload.comment.user?.type === 'Bot' || commentAuthor === 'github-actions[bot]') {
|
||||
core.info('Ignoring /rerun-intake from a bot or unknown actor.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!intake.parseRerunIntakeCommand(context.payload.comment.body)) {
|
||||
core.info('No supported /rerun-intake command was found.');
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: currentIssue } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number
|
||||
});
|
||||
|
||||
const labelNames = new Set((currentIssue.labels || []).map((label) => label.name));
|
||||
const isExternalPluginIssue =
|
||||
labelNames.has('external-plugin') ||
|
||||
String(currentIssue.body || '').includes(intake.ISSUE_FORM_MARKER);
|
||||
if (!isExternalPluginIssue) {
|
||||
core.info('Ignoring /rerun-intake because the issue is not an external plugin submission.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (labelNames.has('approved') || labelNames.has('re-review-due') || labelNames.has('re-review-follow-up')) {
|
||||
core.info('Ignoring /rerun-intake because the issue is already approved or in the six-month re-review flow.');
|
||||
return;
|
||||
}
|
||||
|
||||
const issueAuthor = currentIssue.user?.login;
|
||||
const isIssueAuthor = Boolean(issueAuthor && commentAuthor === issueAuthor);
|
||||
|
||||
let hasWriteAccess = false;
|
||||
if (!isIssueAuthor) {
|
||||
const permission = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
username: commentAuthor
|
||||
});
|
||||
hasWriteAccess = ['admin', 'write', 'maintain'].includes(permission.data.permission);
|
||||
}
|
||||
|
||||
if (!isIssueAuthor && !hasWriteAccess) {
|
||||
core.info(`Ignoring /rerun-intake because ${commentAuthor} is neither the issue author nor a maintainer.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const canRerunFromCurrentState = currentIssue.state === 'open' || labelNames.has('rejected');
|
||||
if (!canRerunFromCurrentState) {
|
||||
core.info('Ignoring /rerun-intake because the issue is closed outside the intake/rejection flow.');
|
||||
return;
|
||||
}
|
||||
|
||||
const evaluation = await intake.evaluateExternalPluginIssue({
|
||||
issue: currentIssue,
|
||||
token: process.env.GITHUB_TOKEN
|
||||
});
|
||||
|
||||
await intakeState.applyExternalPluginIntakeEvaluation({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issueNumber: context.issue.number,
|
||||
evaluation
|
||||
});
|
||||
|
||||
if (evaluation.valid && currentIssue.state === 'closed' && labelNames.has('rejected')) {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state: 'open'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!evaluation.valid && currentIssue.state === 'open') {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state: 'closed'
|
||||
});
|
||||
}
|
||||
@@ -167,10 +167,11 @@ When adding a new agent, instruction, skill, hook, workflow, or plugin:
|
||||
3. In v1, only GitHub-hosted plugins are accepted for public submission, using a public repo plus an immutable `ref`
|
||||
4. The shared validator in `eng/external-plugin-validation.mjs` is the canonical source of truth for external plugin data rules; reuse it instead of duplicating checks in scripts or workflows
|
||||
5. Submission issues move through `external-plugin` + `awaiting-review` -> `ready-for-review` -> `approved` or `rejected`
|
||||
6. Maintainers make the decision with `/approve` or `/reject <reason>` issue comments; approved issues are closed and used as the six-month re-review anchor
|
||||
7. Approval automation creates or updates the PR against `staged`, updates `plugins/external.json`, and regenerates marketplace outputs
|
||||
8. Nightly re-review automation finds closed `external-plugin` + `approved` issues that are at least six months old, applies `re-review-due`, and opens or updates a tracking issue for maintainers
|
||||
9. Maintainers complete re-review on the original approved submission issue with `/re-review-keep`, `/re-review-needs-changes`, or `/re-review-remove`; keep resets the issue `closed_at`, and remove opens a PR against `staged`
|
||||
6. After issue edits, the issue author or a maintainer can comment `/rerun-intake` to re-run automated intake without opening a new submission issue
|
||||
7. Maintainers make the decision with `/approve` or `/reject <reason>` issue comments; approved issues are closed and used as the six-month re-review anchor
|
||||
8. Approval automation creates or updates the PR against `staged`, updates `plugins/external.json`, and regenerates marketplace outputs
|
||||
9. Nightly re-review automation finds closed `external-plugin` + `approved` issues that are at least six months old, applies `re-review-due`, and opens or updates a tracking issue for maintainers
|
||||
10. Maintainers complete re-review on the original approved submission issue with `/re-review-keep`, `/re-review-needs-changes`, or `/re-review-remove`; keep resets the issue `closed_at`, and remove opens a PR against `staged`
|
||||
|
||||
### Testing Instructions
|
||||
|
||||
|
||||
+4
-3
@@ -230,9 +230,10 @@ The public-submission policy builds on those rules and also requires `license` p
|
||||
1. **Open an issue** using the external plugin issue form. Automation applies the `external-plugin` and `awaiting-review` labels.
|
||||
2. **Automated intake validation** checks that the required fields are present and correctly formatted for a GitHub-hosted plugin. Invalid submissions are closed with a comment explaining what must be fixed before resubmitting.
|
||||
3. **Ready for maintainer review**: if the issue passes intake validation, automation removes `awaiting-review` and adds `ready-for-review`.
|
||||
4. **Maintainer decision**: a maintainer with write access performs the manual review, then comments `/approve` or `/reject <reason>` on the issue. Commands from non-maintainers are ignored.
|
||||
5. **Approval path**: on `/approve`, automation removes `ready-for-review`, adds `approved`, closes the issue, and opens or updates a PR against `staged` that updates `plugins/external.json` and generated marketplace outputs.
|
||||
6. **Rejection path**: on `/reject <reason>`, automation removes `ready-for-review`, adds `rejected`, closes the issue, and records the reason in an issue comment. Submitters can open a new issue after addressing the feedback.
|
||||
4. **Requesting another intake pass**: after updating the issue body, the issue author or a maintainer can comment `/rerun-intake` to re-run automated intake on demand. Open issues still re-trigger intake automatically on edit, but closed rejected issues need `/rerun-intake`.
|
||||
5. **Maintainer decision**: a maintainer with write access performs the manual review, then comments `/approve` or `/reject <reason>` on the issue. Commands from non-maintainers are ignored.
|
||||
6. **Approval path**: on `/approve`, automation removes `ready-for-review`, adds `approved`, closes the issue, and opens or updates a PR against `staged` that updates `plugins/external.json` and generated marketplace outputs.
|
||||
7. **Rejection path**: on `/reject <reason>`, automation removes `ready-for-review`, adds `rejected`, closes the issue, and records the reason in an issue comment. After addressing the feedback, update the same issue and use `/rerun-intake` to re-queue intake.
|
||||
|
||||
##### Maintainer review responsibilities
|
||||
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
export const EXTERNAL_PLUGIN_INTAKE_LABELS = Object.freeze({
|
||||
"external-plugin": {
|
||||
color: "FEF2C0",
|
||||
description: "Public external plugin submission",
|
||||
},
|
||||
"awaiting-review": {
|
||||
color: "FBCA04",
|
||||
description: "Submission is waiting for automated intake validation",
|
||||
},
|
||||
"ready-for-review": {
|
||||
color: "0E8A16",
|
||||
description: "Submission passed intake validation and is ready for maintainer review",
|
||||
},
|
||||
approved: {
|
||||
color: "1D76DB",
|
||||
description: "Submission was approved by a maintainer",
|
||||
},
|
||||
rejected: {
|
||||
color: "B60205",
|
||||
description: "Submission was rejected or failed intake validation",
|
||||
},
|
||||
});
|
||||
|
||||
const EXTERNAL_PLUGIN_INTAKE_SYNC_LABELS = Object.freeze([
|
||||
"external-plugin",
|
||||
"awaiting-review",
|
||||
"ready-for-review",
|
||||
"rejected",
|
||||
]);
|
||||
|
||||
async function ensureLabel({ github, owner, repo, name, config }) {
|
||||
try {
|
||||
await github.rest.issues.createLabel({
|
||||
owner,
|
||||
repo,
|
||||
name,
|
||||
color: config.color,
|
||||
description: config.description,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status !== 422) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function removeLabel({ github, owner, repo, issueNumber, name }) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
name,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status !== 404) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function syncExternalPluginIntakeLabels({ github, owner, repo, issueNumber, desiredLabels }) {
|
||||
await Promise.all(
|
||||
Object.entries(EXTERNAL_PLUGIN_INTAKE_LABELS).map(([name, config]) =>
|
||||
ensureLabel({ github, owner, repo, name, config })
|
||||
)
|
||||
);
|
||||
|
||||
const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const currentManagedLabels = currentLabels
|
||||
.map((label) => label.name)
|
||||
.filter((name) => EXTERNAL_PLUGIN_INTAKE_SYNC_LABELS.includes(name));
|
||||
|
||||
const labelsToAdd = [...desiredLabels].filter((name) => !currentManagedLabels.includes(name));
|
||||
const labelsToRemove = currentManagedLabels.filter((name) => !desiredLabels.has(name));
|
||||
|
||||
if (labelsToAdd.length > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
labels: labelsToAdd,
|
||||
});
|
||||
}
|
||||
|
||||
for (const name of labelsToRemove) {
|
||||
await removeLabel({ github, owner, repo, issueNumber, name });
|
||||
}
|
||||
}
|
||||
|
||||
export async function upsertExternalPluginIntakeComment({
|
||||
github,
|
||||
owner,
|
||||
repo,
|
||||
issueNumber,
|
||||
marker,
|
||||
body,
|
||||
}) {
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const existingComment = comments.find(
|
||||
(comment) => comment.user?.login === "github-actions[bot]" && comment.body?.includes(marker)
|
||||
);
|
||||
|
||||
if (existingComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existingComment.id,
|
||||
body,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueNumber,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
export async function applyExternalPluginIntakeEvaluation({
|
||||
github,
|
||||
owner,
|
||||
repo,
|
||||
issueNumber,
|
||||
evaluation,
|
||||
}) {
|
||||
const desiredLabels = evaluation.valid
|
||||
? new Set(["external-plugin", "ready-for-review"])
|
||||
: new Set(["external-plugin", "rejected"]);
|
||||
|
||||
await syncExternalPluginIntakeLabels({
|
||||
github,
|
||||
owner,
|
||||
repo,
|
||||
issueNumber,
|
||||
desiredLabels,
|
||||
});
|
||||
|
||||
await upsertExternalPluginIntakeComment({
|
||||
github,
|
||||
owner,
|
||||
repo,
|
||||
issueNumber,
|
||||
marker: evaluation.commentMarker,
|
||||
body: evaluation.commentBody,
|
||||
});
|
||||
|
||||
return { desiredLabels };
|
||||
}
|
||||
@@ -6,7 +6,13 @@ import { fileURLToPath } from "url";
|
||||
import { ROOT_FOLDER } from "./constants.mjs";
|
||||
import { readExternalPlugins, validateExternalPlugin } from "./external-plugin-validation.mjs";
|
||||
|
||||
const ISSUE_FORM_MARKER = "<!-- external-plugin-submission -->";
|
||||
export const ISSUE_FORM_MARKER = "<!-- external-plugin-submission -->";
|
||||
export const EXTERNAL_PLUGIN_INTAKE_COMMENT_MARKER = "<!-- external-plugin-intake -->";
|
||||
export const RERUN_INTAKE_COMMAND = "/rerun-intake";
|
||||
const RERUN_INTAKE_COMMAND_PATTERN = new RegExp(
|
||||
`^\\s*${RERUN_INTAKE_COMMAND.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`,
|
||||
"m",
|
||||
);
|
||||
const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
|
||||
|
||||
const REQUIRED_CHECKLIST_ITEMS = [
|
||||
@@ -261,6 +267,10 @@ export function parseExternalPluginIssueBody(body) {
|
||||
};
|
||||
}
|
||||
|
||||
export function parseRerunIntakeCommand(body) {
|
||||
return RERUN_INTAKE_COMMAND_PATTERN.test(String(body ?? ""));
|
||||
}
|
||||
|
||||
export async function evaluateExternalPluginIssue({ issue, token } = {}) {
|
||||
const issueBody = issue?.body ?? "";
|
||||
const parsed = parseExternalPluginIssueBody(issueBody);
|
||||
@@ -294,7 +304,7 @@ export async function evaluateExternalPluginIssue({ issue, token } = {}) {
|
||||
const dedupedErrors = [...new Set(errors)];
|
||||
const dedupedWarnings = [...new Set(warnings)];
|
||||
const valid = dedupedErrors.length === 0;
|
||||
const marker = "<!-- external-plugin-intake -->";
|
||||
const marker = EXTERNAL_PLUGIN_INTAKE_COMMENT_MARKER;
|
||||
const normalizedKeywords = parsed.plugin?.keywords?.length ? parsed.plugin.keywords.join(", ") : "_None provided_";
|
||||
const notes = parsed.additionalNotes ?? "_No additional notes provided._";
|
||||
const payload = parsed.plugin
|
||||
@@ -333,7 +343,7 @@ export async function evaluateExternalPluginIssue({ issue, token } = {}) {
|
||||
"## ❌ External plugin intake failed",
|
||||
"",
|
||||
"This submission did not pass automated intake validation, so the issue has been closed.",
|
||||
"Update the issue form, then reopen the issue to run intake validation again.",
|
||||
`Edit the issue form to address the fixes below, then have the issue author or a maintainer comment \`${RERUN_INTAKE_COMMAND}\` to re-run intake for this closed submission.`,
|
||||
"",
|
||||
"### Required fixes",
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user